Wann sollten Merkmale im Gegensatz zu Vererbung und Zusammensetzung verwendet werden?

9

AFAIK bietet drei gängige Methoden, um die Wiederverwendbarkeit von OOP zu implementieren

  1. Vererbung: Normalerweise ist es eine Beziehung (eine Ente ist ein Vogel).
  2. Zusammensetzung: in der Regel zur Darstellung hat eine Beziehung (ein Auto hat einen Motor)
  3. Eigenschaften (zB das Schlüsselwort trait in PHP): ... da bin ich mir nicht sicher

Obwohl es für mich so aussieht, als könnten Merkmale sowohl Has-A- als auch Is-A-Beziehungen implementieren, bin ich mir nicht sicher, für welche Art von Modellierung es gedacht war. Für welche Situationen wurden Merkmale entwickelt?

Extrakun
quelle
Sie erklären vielleicht etwas mehr über Eigenschaften, aber könnten Eigenschaften nur ein anderes Wort für Kompositionen sein?
Trilarion
1
Ich würde wirklich gerne wissen warum auch. Ich habe sie nicht einmal benutzt.
Andy

Antworten:

6

Eigenschaften sind ein weiterer Weg, um Komposition zu machen. Stellen Sie sich diese als eine Möglichkeit vor, alle Teile einer Klasse zur Kompilierungszeit (oder JIT-Kompilierungszeit) zusammenzustellen und die konkreten Implementierungen der Teile zusammenzustellen, die Sie benötigen.

Grundsätzlich möchten Sie Merkmale verwenden, wenn Sie Klassen mit unterschiedlichen Merkmalskombinationen erstellen. Diese Situation tritt am häufigsten bei Personen auf, die flexible Bibliotheken schreiben, die andere nutzen können. Hier ist zum Beispiel die Deklaration einer Unit-Test-Klasse, die ich kürzlich mit ScalaTest geschrieben habe :

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

Unit-Test-Frameworks bieten eine Vielzahl unterschiedlicher Konfigurationsoptionen, und jedes Team hat unterschiedliche Vorlieben, wie es Dinge tun möchte. Durch Einfügen der Optionen in Merkmale (die within Scala gemischt verwendet werden ) kann ScalaTest all diese Optionen anbieten, ohne Klassennamen wie WordSpecLikeWithMatchersAndFuturesoder eine Menge boolescher Laufzeitflags wie erstellen zu müssen WordSpecLike(enableFutures, enableMatchers, ...). Dies macht es einfach, dem Open / Closed-Prinzip zu folgen . Sie können neue Funktionen und neue Kombinationen von Funktionen hinzufügen, indem Sie einfach ein neues Merkmal hinzufügen. Dies erleichtert auch das Befolgen des Interface-Segregation-Prinzips , da Sie Funktionen, die nicht allgemein benötigt werden, einfach in ein Merkmal einfügen können.

Merkmale sind auch eine gute Möglichkeit, gemeinsamen Code in mehrere Klassen einzuteilen, die für die gemeinsame Nutzung einer Vererbungshierarchie nicht sinnvoll sind. Vererbung ist eine sehr eng gekoppelte Beziehung, und Sie sollten diese Kosten nicht bezahlen, wenn Sie helfen können. Merkmale sind eine viel lockerer gekoppelte Beziehung. In meinem obigen Beispiel habe ich MyCustomTraiteine Scheindatenbankimplementierung problemlos für mehrere ansonsten nicht verwandte Testklassen freigegeben.

Die Abhängigkeitsinjektion erreicht viele der gleichen Ziele, jedoch zur Laufzeit basierend auf Benutzereingaben anstatt zur Kompilierungszeit basierend auf Programmierereingaben. Merkmale sind auch eher für Abhängigkeiten gedacht, die semantisch Teil derselben Klasse sind. Sie setzen die Teile einer Klasse zusammen, anstatt andere Klassen mit anderen Verantwortlichkeiten anzurufen.

Frameworks für die Abhängigkeitsinjektion erreichen viele der gleichen Ziele zur Kompilierungszeit basierend auf den Eingaben des Programmierers, sind jedoch weitgehend eine Problemumgehung für Programmiersprachen ohne angemessene Unterstützung von Merkmalen. Merkmale bringen diese Abhängigkeiten in den Bereich der Typprüfung des Compilers, mit einer saubereren Syntax und einem einfacheren Erstellungsprozess, der eine klarere Unterscheidung zwischen Abhängigkeiten zur Kompilierungszeit und zur Laufzeit ermöglicht.

Karl Bielefeldt
quelle
Hallo, das ist eine großartige Antwort. Darf ich fragen, wie Sie entscheiden sollen, wann Merkmale und wann die Abhängigkeitsinjektion verwendet werden sollen. das scheint das gleiche zu erreichen.
Extrakun
Kluge Beobachtung. Siehe meine Bearbeitung.
Karl Bielefeldt
Danke, dass du das geschrieben hast. Ich bin gespannt auf Ihre Antwort, dass Merkmale wie Komposition @KarlBielefeldt sind. Ihre Klasse kann überall dort verwendet werden, wo diese Eigenschaft benötigt wird, und Sie würden immer noch unter der gleichen Fragilität leiden wie bei einer normalen Benutzeroberfläche. Es scheint also näher an Subtypisierung und Vererbung zu sein, nein? Ich bin mir auch nicht sicher, wie es DI ähnelt ... Wenn Sie verschiedene Klassen definiert haben, die Merkmale erweitern, sind diese konkreten Abhängigkeiten nicht überall in der Hierarchie definiert, in der diese Klasse definiert ist, und nicht an einem oberen / Einstiegspunkt der Code? Vielen Dank!
allstar vor
Merkmale können aufgrund ihrer Vererbungseigenschaften genau wie Schnittstellen verwendet werden, aber das macht sie nicht einzigartig. Der withOperator unterscheidet sie und withist eine Kompositionsoperation. Und ja, Merkmale ersetzen DI, ohne dass sie am Einstiegspunkt des Codes definiert werden müssen. Das ist eines der Dinge, die sie meiner Meinung nach vorzuziehen machen.
Karl Bielefeldt vor