Gdy Googled, pojawia się wiele odpowiedzi na ten temat. Nie wydaje mi się jednak, aby któryś z nich dobrze ilustrował różnicę między tymi dwiema funkcjami. Chciałbym więc spróbować jeszcze raz, a konkretnie ...
Co można zrobić z typami własnymi, a nie z dziedziczeniem i odwrotnie?
Dla mnie powinna istnieć jakaś wymierna, fizyczna różnica między nimi, w przeciwnym razie są one tylko nominalnie różne.
Jeśli Cecha A rozciąga się na B lub typu B, to czy oba nie pokazują, że bycie B jest wymogiem? Jaka jest różnica?
design-patterns
object-oriented-design
scala
Mark Canlas
źródło
źródło
Odpowiedzi:
Jeśli cecha A rozszerza B, wówczas mieszanie w A daje dokładnie B plus to, co A dodaje lub rozszerza. W przeciwieństwie do tego, jeśli cecha A ma własne odwołanie, które jest jawnie wpisane jako B, wówczas najwyższa klasa nadrzędna musi również mieszać B lub potomek B (i mieszać najpierw , co jest ważne).
To najważniejsza różnica. W pierwszym przypadku dokładny typ B krystalizuje się w punkcie A, który go wydłuża. W drugim przypadku projektant klasy nadrzędnej decyduje, która wersja B zostanie użyta w punkcie, w którym klasa macierzysta jest utworzona.
Inna różnica polega na tym, że A i B zapewniają metody o tej samej nazwie. Gdzie A rozszerza B, metoda A zastępuje B. Tam, gdzie A miesza się po B, metoda A po prostu wygrywa.
Wpisane na maszynie odnośniki dają znacznie więcej swobody; połączenie między A i B jest luźne.
AKTUALIZACJA:
Ponieważ nie masz pewności co do korzyści wynikających z tych różnic ...
Jeśli korzystasz z dziedziczenia bezpośredniego, tworzysz cechę A, którą jest B + A. Ułożyłeś związek w kamień.
Jeśli użyjesz samouczenia na maszynie, każdy, kto chce użyć twojej cechy A w klasie C., może
I to nie jest granica ich opcji, biorąc pod uwagę sposób, w jaki Scala pozwala na tworzenie instancji cechy bezpośrednio z blokiem kodu jako konstruktorem.
Jeśli chodzi o różnicę między wygraną metody A , ponieważ A jest pomieszane na końcu, w porównaniu do A przedłużającego B, rozważ to ...
Kiedy łączysz sekwencję cech, za każdym razem, gdy
foo()
wywoływana jest metoda , kompilator przechodzi do ostatniej wymieszanej cechy w celu jej wyszukaniafoo()
, a następnie (jeśli nie została znaleziona), przechodzi przez sekwencję w lewo, aż znajdzie cechę, która implementujefoo()
i wykorzystuje że. A ma również opcję wywoływaniasuper.foo()
, która również przechodzi przez sekwencję w lewo, aż znajdzie implementację i tak dalej.Więc jeśli A ma wpisane na maszynie odniesienie do B, a pisarz A wie, że B implementuje
foo()
, A może zadzwonićsuper.foo()
wiedząc, że jeśli nic innego nie zapewnifoo()
, B to zrobi. Jednak twórca klasy C ma możliwość upuszczenia dowolnej innej cechy, w której implementujefoo()
, a zamiast tego dostanie to.Ponownie, jest to o wiele bardziej wydajne i mniej ograniczający niż rozciągający B i bezpośrednio dzwoniąc wersja B z użytkownikiem
foo()
.źródło
Mam kod, który ilustruje niektóre różnice w widoczności wrt i „domyślne” implementacje przy rozszerzaniu vs ustawianie własnego typu. Nie ilustruje żadnej z omawianych już części na temat sposobu rozwiązywania rzeczywistych kolizji nazw, ale skupia się na tym, co jest możliwe i niemożliwe.
Jedną ważną różnicą jest to, że IMO
A1
nie ujawnia, że potrzebujeB
wszystkiego, co tylko ją widziA1
(jak pokazano w części up-cast). Jedynym kodem, który faktycznie zobaczy, żeB
używana jest szczególna specjalizacja , jest kod, który wyraźnie wie o typie złożonym (podobnymThink*{X,Y}
).Inną kwestią jest to, że
A2
(z rozszerzeniem) będzie faktycznie używane,B
jeśli nic innego nie zostanie określone, podczas gdyA1
(własny typ) nie powie, że będzie używał,B
chyba że zostanie zastąpione, konkretne B musi zostać wyraźnie podane, gdy obiekty są tworzone.źródło