developer-blog:$

oop

OOP - Save Your Business Logic

Was ist das Ziel eines Entwicklers? Richtig, sich so viel Arbeit wie möglich ersparen. Aber wo spart man sich die meiste Arbeit? Wenn eine neue Anwendung geschrieben wird, möchte man schnell Ergebnisse erzielen. Das erreicht man unter anderem, indem so wenig Code wie möglich geschrieben wird. In so einer Anwendung sind die einzelnen Komponenten oft miteinander fest vertratet. Gehen wir mal davon aus das es sich nicht um einen Prototyp handelt, der später weggeworfen wird. In der Regel wirst du mehr Zeit mit der Wartung und Weiterentwicklung, als der Neuentwicklung verbringen. Macht es dann nicht mehr Sinn sich die Wartung und Weiterentwicklung so leicht wie möglich zu machen? Je umfangreicher das Projekt wird, desto häufiger wird der Chef bei einem Änderungswunsch die Aussage hören:

"Die Änderung ist sehr aufwendig"

Was führt dazu das ein Projekt schnell neu entwickelt werden kann, aber schwer zu erweitern ist? Dem wollen wir mal auf den Grund gehen.

Stellen wir uns folgendes Szenario vor. Als Entwickler hast du die Aufgaben für ein Musikladen eine Anwendung zu schreiben. Wir fangen mit der Klasse für das Musikalbum an.

class MusicAlbum
{
    private int $id;
    private string $title;
    private array $songs;

    public function __construct(int $id, string $title, array $songs)
    {
        $this->id = $id;
        $this->title = $title;
        $this->songs = $songs;
    }

    public function songs(): array
    {
        return $this->songs;
    }
}

Mit dieser Klasse können wir alle Songs im Musikalbum anzeigen. Unsere Anwendung ist damit fertig. Aber nein, der Chef hat eine tolle neue Idee für ein weiteres Feature, was die Kunden umhauen wird. Die Kunden sollen die Möglichkeit bekommen die Songs im MusikAlbum zu sortieren. Wir erweitern unsere Musikalbum-Klasse nun um eine Sortiermöglichkeit.

class MusicAlbum
{
    ...
    songs(string $order)
    {
        if($order === 'asc') {
            sort($this->songs)
        } else if ($order === 'desc') {
            rsort($this->songs)
        }
        ...
    }
}

Die Änderung ist in der Klasse schnell gemacht. Doch halt, die Klasse MusicAlbum gehört zu unser Business Logic, sprich Kernlogik auf die alles andere aufbaut. Wenn wir etwas an der Kernlogik ändern, sind wir gezwungen alles anzupassen, was von der Kernlogik anhängig ist. Weißt du wie viele Klassen dafür geändert werden müssen? Der Chef möchte es garniert genau wissen wie lange du dafür brauchst. Nehmen wir an du bist Speedy Gonzales und hast in Windeseile alle darüber liegenden Klassen angepasst. Kannst du nach all den Änderungen noch garantieren das deine Anwendung ohne Fehler noch laufen wird? Ich sage nur, viel Freude beim Bugfixing. Dein Chef wird sich bestimmt gleich mit freuen und jegliche Terminplanung ist über den Haufen geworfen.

Jetzt schauen wir uns eine Möglichkeit an, die Kernlogik unberührt zu lassen. Dazu machen wir uns das Open–closed principle zu eigen. Wir möchten die Funktionalität der Klasse MusicAlbum erweitern, ohne sie zu verändern. Als allererstes benötigen wir dafür ein Interface;

interface MusicAlbumInterface
{
    public function songs();
}

class MusicAlbum implements MusicAlbumInterface
{
    ...
}

Nun erstellen wird hieraus ein Objekt.

$musicAlbum = new MusicAlbum(1, 'My new Album Title', ['My Song 1', 'My Song 2'])

Jetzt erweitern wir das Album mit einer Sortiermöglichkeit durch das Decorator Pattern.

$musicAlbum =
    new MusicAlbumSortAsc(
        new MusicAlbum(1, 'My new Album Title', ['My Song 1', 'My Song 2'])
    );

So könnte der Decorator mit der Sortierfunktion im Detail aussehen.

class MusicAlbumSortAsc implements MusicAlbumInterface
{
    private MusicAlbumInterface $musicAlbum;

    public function __construct(MusicAlbumInterface $musicAlbum)
    {
        $this->musicAlbum = $musicAlbum;
    }

    public function songs()
    {
        return sort($this->musicAlbum->songs());
    }
}

Weil es uns richtig Spaß macht die Kunden mit neuen Features zu begeistern, hauen wir gleich noch eins raus. Die Songs im Album sollen nicht nur sortiert werden, sondern mit einer Top10 ausgegeben werden. Wir müssen nichts anderes machen, als einen weiteren Decorator drüber zu legen.

$musicAlbum =
    new MusicAlbumTop10(
        new MusicAlbumSortAsc(
            new MusicAlbum(1, 'My new Album Title', ['My Song 1', 'My Song 2'])
        )
    );

Mit dem Decorator mussten wir nicht einmal die Kernlogik mit der Klasse MusicAlbum anpassen und der Aufwand für weitere Änderungen haben wir auf ein minimum reduzieren können. Das Vorgehen ist auch als Composition Over Inheritance Pattern bekannt. Ein weiterer Vorteil ist, das wir hier genau nachvollziehen können was wann passiert, ohne in die einzelnen Klassen springen zu müssen. Wir können ohne großen Aufwand auch die Reihenfolge der Logik ändern. Vielleicht möchten wir vom Album erst die Top 10 haben, und danach mit Asc sortieren.

Fazit

In der Regel verbringen wir mehr Zeit mit der Wartung und Weiterentwicklung einer Anwendung, als mit der Neuentwicklung. Den Aufwand für die Wartung und Weiterentwicklung können wir minimieren, wenn die Anwendung leicht zu erweitern ist. Das erreichen wir dadurch das die Business Logic im Nachhinein so gut wie nicht mehr verändert werden muss und der Code in wenigen Zeilen wie ein Satz in einem schnell verständlich zu lesen ist.