Beim Googeln werden viele Antworten zu diesem Thema angezeigt. Ich habe jedoch nicht das Gefühl, dass einer von ihnen den Unterschied zwischen diesen beiden Merkmalen gut veranschaulicht. Also würde ich es gerne noch einmal versuchen, speziell ...
Was kann mit Selbsttypen und nicht mit Vererbung getan werden und umgekehrt?
Für mich sollte es einen quantifizierbaren physikalischen Unterschied zwischen den beiden geben, sonst sind sie nur nominell unterschiedlich.
Wenn Merkmal A B oder Selbsttyp B erweitert, veranschaulichen beide nicht, dass es eine Voraussetzung ist, von B zu sein? Wo ist der Unterschied?
design-patterns
object-oriented-design
scala
Mark Canlas
quelle
quelle
Antworten:
Wenn Merkmal A B erweitert, erhalten Sie durch Einmischen von A genau B plus alles, was A hinzufügt oder erweitert. Wenn im Gegensatz dazu Merkmal A eine Selbstreferenz hat, die explizit als B eingegeben wird, muss die ultimative übergeordnete Klasse auch B oder einen Nachkommen-Typ von B einmischen (und diese zuerst einmischen , was wichtig ist).
Das ist der wichtigste Unterschied. Im ersten Fall kristallisiert der genaue Typ von B an dem Punkt, an dem A ihn erweitert. Im zweiten Fall kann der Designer der übergeordneten Klasse entscheiden, welche Version von B an dem Punkt verwendet wird, an dem die übergeordnete Klasse zusammengesetzt ist.
Ein weiterer Unterschied besteht darin, dass A und B gleichnamige Methoden bereitstellen. Wenn A B erweitert, überschreibt die Methode von A die von B. Wenn A nach B eingemischt wird, gewinnt einfach die Methode von A.
Die getippte Selbstreferenz gibt Ihnen viel mehr Freiheit; Die Kopplung zwischen A und B ist locker.
AKTUALISIEREN:
Da Sie sich über den Nutzen dieser Unterschiede nicht im Klaren sind ...
Wenn Sie die direkte Vererbung verwenden, erstellen Sie das Merkmal A, das B + A ist. Sie haben die Beziehung in Stein gemeißelt.
Wenn Sie eine typisierte Selbstreferenz verwenden, kann dies jeder, der Ihr Merkmal A in Klasse C verwenden möchte
Und dies ist nicht die Grenze ihrer Optionen, da Sie mit Scala ein Merkmal direkt mit einem Codeblock als Konstruktor instanziieren können.
Betrachten Sie den Unterschied zwischen dem Gewinn der Methode von A , da A zuletzt gemischt wird, im Vergleich zu A, das B erweitert, Folgendes:
Wenn Sie eine Folge von Merkmalen
foo()
einmischen, geht der Compiler bei jedem Aufruf der Methode zum zuletzt eingemischten Merkmal, umfoo()
danach zu suchen , und durchläuft (falls nicht gefunden) die Folge nach links, bis er ein Merkmal findet, das implementiertfoo()
und verwendet wird Das. A hat auch die Option zum Aufrufensuper.foo()
, wodurch auch die Sequenz nach links durchlaufen wird, bis eine Implementierung gefunden wird, und so weiter.Wenn also A eine typisierte Selbstreferenz zu B hat und der Verfasser von A weiß, dass B implementiert
foo()
, kann A anrufen undsuper.foo()
wissen, dass B es tun wird, wenn nichts anderes vorsiehtfoo()
. Der Ersteller der Klasse C hat jedoch die Möglichkeit, jedes andere Merkmal, in dem implementiert wirdfoo()
, zu löschen , und A erhält dies stattdessen.Auch dies ist viel leistungsfähiger und weniger einschränkend als A, das B erweitert und direkt die Version von B aufruft
foo()
.quelle
Ich habe einen Code, der einige der Unterschiede zwischen Sichtbarkeit und "Standard" -Implementierungen beim Erweitern und Festlegen eines Selbsttyps veranschaulicht. Es zeigt keinen der bereits besprochenen Teile darüber, wie tatsächliche Namenskollisionen gelöst werden, sondern konzentriert sich auf das, was möglich und was nicht möglich ist.
Ein wichtiger Unterschied ist, dass IMO
A1
nicht aussetzt, dass esB
für irgendetwas benötigt wird , das es lediglich alsA1
(wie in den hochgearbeiteten Teilen dargestellt) betrachtet. Der einzige Code, der tatsächlich erkennt, dass eine bestimmte Spezialisierung vonB
verwendet wird, ist Code, der den zusammengesetzten Typ (wieThink*{X,Y}
) explizit kennt .Ein weiterer Punkt ist, dass
A2
(mit Erweiterung) tatsächlich verwendet wird,B
wenn nichts anderes angegeben ist, währendA1
(Selbsttyp) nicht sagt, dass es verwendet wird,B
wenn es nicht überschrieben wird. Ein konkretes B muss explizit angegeben werden, wenn Objekte instanziiert werden.quelle