Was ist Favour Composition over Inheritance?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
MikeDee
User
Beiträge: 31
Registriert: Samstag 5. November 2011, 12:41

Kann mir jemand Favour Composition over Inheritance in "einfachen" Worten erklären? So das es auch jemand ohne OOP-/Klassenerfahrung es versteht? http://clean-code-developer.de/Roter-Gr ... nce_FCoI_3
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Es dürfte schwierig sein, etwas jemandem zu erklären, wenn derjenige die Grundlagen nicht beherrscht ;-) Wenn Du noch keine Ahnung von OOP hast und nicht verstehst, was Vererbung bedeutet, dann wird es auch schwer zu erklären, wieso man oftmals besser damit bedient ist, Verhalten über Komposition zu steuern.

Also sage uns doch erst einmal, was so Deine Erfahrungen sind... je nach Kenntnisstand, kann man Dir dann Empfehlungen geben :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
MikeDee
User
Beiträge: 31
Registriert: Samstag 5. November 2011, 12:41

Also, ich weiß das Klassen von Basis-/Elternklassen die Methoden und Attribute(?) übernehmen, das die Kinderklasse die Elternklasse erweitern oder Methoden ändern kann und das Methoden Funktionen von Klassen sind.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich meinte eher, ob Du auch schon *praktische* Erfahrungen mit Klassen gesammelt hast? Das ganze rein von der theoretischen Seite anzugehen kann klappen, ist aber sicherlich nicht so eingängig. (Offensichtlich ist Dir das Prinzip ja so nicht klar - obwohl es imho recht einfach und Grundlage für einen simplen Design Pattern, den Strategy-Pattern, ist)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
MikeDee
User
Beiträge: 31
Registriert: Samstag 5. November 2011, 12:41

Oh, nein, praktische Erfahrung habe ich mit Klassen noch nicht gesammelt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ok, versuchen wir es mal einfach und simpel.

Stelle Dir vor, wir sollen einen Simulator für ein Wettspiel schreiben. Dafür sollen wir verschiedene Verhaltensweisen von Spielern implementieren, also einen, der aggressiv und immer hoch bietet und einen, der eher zurückhaltend bietet.

Das könnte man nun so implementieren:

Code: Alles auswählen

class Player(object):

    def __init__(self, name, money):
        self.name = name
        self.money = money
        
    def __str__(self):
        return "%s: %d" % (self.name, self.money)
        
    def bet(self, minimum):
        raise NotImplementedError()
        

class ReservedPlayer(Player):

    def __init__(self, name, money):
        Player.__init__(self, name, money)
        
    def bet(self, minimum):
        self.money -= minimum
        return minimum


class AggressivePlayer(Player):

    def __init__(self, name, money):
        Player.__init__(self, name, money)
        
    def bet(self, minimum):
        self.money = 0
        return self.money
Ok, wir starten klassisch, indem wir mal eine Basisklasse implementieren, die die gemeinsamen Attribute ``name`` und ``money`` besitzt. Die ``bet``-Methode implementieren wir nicht, als Hinweis, dass man von dieser Klasse ableiten soll.

Gesagt getan haben wir zwei Klassen für den jeweiligen Spielstil erstellt. Der verhaltene Spieler setzt nur so viel, wie er muss, der aggressive Spieler setzt immer alles. (Ziemlich primitiv, aber es soll ja nur verdeutlichen... :-D )

Ok, nun kann ich mal einen Spieler erstellen und bieten:

Code: Alles auswählen

In [42]: p = ReservedPlayer("Chris", 1000)

In [43]: print p
Chris: 1000

In [44]: p.bet(200)
Out[44]: 200

In [45]: print p
Chris: 800
Aha... das klappt so weit.

Nun will ich aber, dass ein Spieler sein Verhalten im Laufe des Spiels ändern kann!

Oha... wie das? Tja... unser Modell hat damit ziemliche Schwierigkeiten. Wir müssten alle relevanten Daten aus dem ``ReservedPlayer`` herausziehen und ein neues Objekt vom Typ ``AggressivePlayer`` erstellen und mit den Attributwerten füllen. Das klingt nicht nur umständlich, das ist es auch :-D

Ich habe die Spielweise zu stark mit dem Spieler-Modell an sich verknüpft, eben durch die "Vererbung"; das ist hier mein Problem.

Nun kommt's... ich könnte doch das Verhalten auslagern, etwa in eine separate Klasse oder Funktion! Damit wäre das Verhalten unabhängig von einem Spieler.

Z.B. könnte das so aussehen:

Code: Alles auswählen

class Player(object):

    def __init__(self, name, money, bet_strategy):
        self.name = name
        self.money = money
        self.bet_strategy = bet_strategy
        
    def __str__(self):
        return "%s: %d" % (self.name, self.money)
        
    def bet(self, minimum):
        amount = self.bet_strategy(self, minimum)
        self.money -= amount
        return amount
        

def bet_minimum(player, minimum):
    return minimum
    
def bet_maximum(player, minimum):
    return player.money
So, ich gebe dem Spieler einfach ein Attribut ``bet_strategy`` mit und überlasse die Abwicklung des Bietens diesem "Objekt". In diesem Falle habe ich einfach zwei Funktionen gewählt; ich könnte natürlich auch Klassen implementieren und diese mit der ``__call__``-Methode aufrufbar machen. (Oder ich zwinge einen Entwickler dazu, eine Strategy-Klasse mit einem festgelegten Methodennamen zu implementieren, oder oder...) Wichtig ist nur, dass die Art und Weise der Strategie nun vom Spieler entkoppelt ist. Ich kann mich zur Laufzeit festlegen, wie mein Spielerobjekt sich verhalten soll:

Code: Alles auswählen

In [47]: p = Player("Chris", 1000, bet_minimum)

In [48]: print p
Chris: 1000

In [49]: p.bet(200)
Out[49]: 200

In [50]: print p
Chris: 800
Cool! :-) Ich übergebe meinem ``Player``-Objekt bei der Initialisierung einfach die ``bet_minimum``-Funktion und schon verhält er sich "reserviert".

Nun kommt aber der Clou. Ich kann nun zur Laufzeit sein Verhalten ändern! Ich weise ihm einfach eine andere Strategie zu:

Code: Alles auswählen

In [51]: p.bet_strategy = bet_maximum

In [52]: p.bet(300)
Out[52]: 800

In [54]: print p
Chris: 0
Super. Das klappt schon mal :-)

Ich kann aber noch mehr. Ich kann sehr einfach eine neue Strategie zu den bestehenden hinzufügen, *ohne* etwas an meinem *Spielerobjekt* ändern zu müssen:

Code: Alles auswählen

from random import randint

def bet_randomly(player, minimum):
    return randint(minimum, player.money)
Hier bietet der Spieler also zufällig zwischen dem Minimum und seinem noch vorhandenem Geldbetrag.

Mal gucken:

Code: Alles auswählen

In [55]: p.money = 1000

In [56]: p.bet_strategy = bet_randomly

In [57]: p.bet(300)
Out[57]: 838

In [58]: print p
Chris: 162
Zunächst gebe ich dem Spieler (das ist immer noch dasselbe ``p``-Objekt!) wieder ein wenig Geld, danach weise ich ihm die neue Strategie zu. Und voila, es klappt :-)

Das war jetzt hier sogar mehr als nur ein Beispiel für "Komposition über Vererbung", sondern sogar schon der sogenannte "Strategy Pattern". Ich hoffe es ist klar geworden, worin hier die Vorteile liegen.

Es gibt noch andere Beispiele, bei denen Vererbung zu Schwierigkeiten führt. Man denke z.B. an die umstrittene Mehrfachvererbung - in manchen Sprachen (z.B. Java) gibt es diese gar nicht, da muss man eh nach anderen Wegen suchen.

Vielleicht fallen anderen Leuten ja hier noch andere schöne Beispiele ein :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
MikeDee
User
Beiträge: 31
Registriert: Samstag 5. November 2011, 12:41

Also anstatt verschiedene Spieler mit eigenen Bietverhalten wird jedem Spielerobjekt(?) das Bietverhalten mitgeteilt?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

MikeDee hat geschrieben:Also anstatt verschiedene Spieler mit eigenen Bietverhalten wird jedem Spielerobjekt(?) das Bietverhalten mitgeteilt?
Exakt! Somit sind Spieler und das Bietverhalten so stark wie möglich voneinander entkoppelt.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten