Istnieje wiele podobnych pytań 1 ,2 ,3 ,4 , ale nie wydaje się dokładnie tak w tym pytaniu, ani rozwiązania nie wydają się optymalne.
To jest ogólne pytanie OOP, przy założeniu, że polimorfizm, leki generyczne i miksy są dostępne. Rzeczywistym używanym językiem jest OOP Javascript (Typescript), ale ten sam problem występuje w Javie lub C ++.
Mam równoległe hierarchie klas, które czasem dzielą to samo zachowanie (interfejs i implementacja), ale czasami każde ma swoje „chronione” zachowanie. Zilustrowane tak:
Jest to wyłącznie w celach ilustracyjnych ; to nie jest rzeczywisty schemat klas. Aby to przeczytać:
- Wszystko we wspólnej hierarchii (w środku) jest dzielone między hierarchiami Canvas (po lewej) i SVG (po prawej). Przez share rozumiem zarówno interfejs, jak i implementację.
- Wszystko tylko w lewej lub prawej kolumnie oznacza zachowanie (metody i elementy) specyficzne dla tej hierarchii. Na przykład:
- Zarówno lewa, jak i prawa hierarchia używają dokładnie tych samych mechanizmów sprawdzania poprawności, pokazanych jako jedna metoda (
Viewee.validate()
) we wspólnej hierarchii. - Tylko hierarchia obszaru roboczego ma metodę
paint()
. Ta metoda wywołuje metodę malowania u wszystkich dzieci. - Hierarchia SVG musi zastąpić
addChild()
metodęComposite
, ale nie jest tak w przypadku hierarchii obszaru roboczego.
- Zarówno lewa, jak i prawa hierarchia używają dokładnie tych samych mechanizmów sprawdzania poprawności, pokazanych jako jedna metoda (
- Konstruktów z dwóch hierarchii bocznych nie można mieszać. Fabryka to zapewnia.
Rozwiązanie I - Tease Apart Inheritance
Wydaje się, że Fowler Tease Apart Inheritance nie wykonuje tego zadania, ponieważ istnieje pewna rozbieżność między tymi dwoma podobieństwami.
Rozwiązanie II - Mixiny
To jedyny, o którym mogę teraz myśleć. Te dwie hierarchie są opracowywane osobno, ale na każdym poziomie klasy mieszają wspólną klasę, która nie jest częścią hierarchii klas. Pominięcie structural
widelca wygląda następująco:
Zauważ, że każda kolumna będzie miała własną przestrzeń nazw, więc nazwy klas nie będą powodować konfliktów.
Pytanie
Czy ktoś może zobaczyć błędy w tym podejściu? Czy ktoś może wymyślić lepsze rozwiązanie?
Uzupełnienie
Oto przykładowy kod, w jaki sposób należy go użyć. Przestrzeń nazw svg
można zastąpić canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Zasadniczo w czasie wykonywania klienci tworzą hierarchię instancji podklas Viewee; tak:
Powiedzmy, że wszystkie te osoby pochodzą z hierarchii obszaru roboczego, są one renderowane przez przejście przez hierarchię, która może wywoływać paint()
każdego użytkownika. Jeśli pochodzą z hierarchii svg, widzowie wiedzą, jak dodać się do DOM, ale nie ma możliwości paint()
przejścia.
Odpowiedzi:
Drugie podejście lepiej segreguje interfejsy, zgodnie z zasadą segregacji interfejsów.
Dodałbym jednak interfejs do malowania.
Zmieniłbym także niektóre imiona. Nie musisz wprowadzać zamieszania:
źródło
To wcale nie jest prawda. Maszynopis ma znaczącą przewagę nad Javą - mianowicie pisanie strukturalne. Możesz zrobić coś podobnego w C ++ z szablonami typu duck, ale to o wiele więcej wysiłku.
Zasadniczo zdefiniuj swoje klasy, ale nie zawracaj sobie głowy rozszerzaniem lub definiowaniem interfejsów. Następnie wystarczy zdefiniować potrzebny interfejs i przyjąć go jako parametr. Następnie obiekty mogą pasować do tego interfejsu - nie muszą wiedzieć, aby go wcześniej rozszerzyć. Każda funkcja może dokładnie zadeklarować i tylko te bity, które dają gówno, a kompilator da ci przepustkę, jeśli końcowy typ ją spełnia, nawet jeśli klasy nie rozszerzają wyraźnie tych interfejsów.
To uwalnia cię od konieczności faktycznego definiowania hierarchii interfejsów i definiowania, które klasy powinny rozszerzać, które interfejsy.
Po prostu zdefiniuj każdą klasę i zapomnij o interfejsach - zajmą się nią strukturalne typy.
Na przykład:
Jest to całkowicie uzasadniony maszynopis. Zauważ, że konsumujące funkcje nie znają ani nie gównią na temat klas podstawowych lub interfejsów używanych w definicji klas.
Nie ma znaczenia, czy klasy są powiązane, czy nie przez dziedziczenie. Nie ma znaczenia, czy rozszerzyli twój interfejs. Wystarczy zdefiniować interfejs jako parametr i gotowe - wszystkie klasy, które go spełniają, są akceptowane.
źródło
validate()
metody (której implementacja jest identyczna dla obu); wtedy tylko svg.Viewee musi zastąpić metodę addChild () , a tylko canvas.Viewee potrzebuje paint () (który wywołuje paint () na wszystkich elementach potomnych - które są członkami podstawowej klasy Composite ). Więc nie mogę sobie tego wyobrazić za pomocą pisania strukturalnego.SVGViewee.addChild()
, aleCanvasViewee
potrzebujesz również dokładnie tej samej funkcji. Wydaje mi się więc, że oba są nieodłączne od Composite?szybki przegląd
Rozwiązanie 3: Wzorzec projektowania oprogramowania „Parallel Class Hierarchy” jest Twoim przyjacielem.
Długa rozszerzona odpowiedź
Twój projekt ZACZĄŁ SIĘ PRAWO. Można go zoptymalizować, niektóre klasy lub elementy mogą zostać usunięte, ale pomysł „równoległej hierarchii”, który stosuje się w celu rozwiązania problemu, JEST PRAWIDŁOWY.
Kilka razy radziłem sobie z tą samą koncepcją, zwykle w hierarchiach kontroli.
Po pewnym czasie ZAKOŃCZYŁEM SIĘ ROBIĆ TO SAME ROZWIĄZANIE NIŻ INNI DEWELOPERZY, co nazywa się czasem Wzorem Projektu „Równoległa Hierarchia” lub Wzorem Projektu „Podwójna Hierarchia”.
(1) Czy kiedykolwiek podzieliłeś jedną klasę na jedną hierarchię klas?
(2) Czy kiedykolwiek podzieliłeś jedną klasę na kilka klas bez hierarchii?
Jeśli zastosowałeś poprzednie rozwiązania osobno, są one sposobem na rozwiązanie niektórych problemów.
Ale co, jeśli połączymy te dwa rozwiązania jednocześnie?
Połącz je, a otrzymasz „Wzorzec projektowy”.
Realizacja
Teraz zastosujmy wzorzec projektowania oprogramowania „Parallel Class Hierarchy” w twoim przypadku.
Obecnie masz 2 lub więcej niezależnych hierarchii klas, które są bardzo podobne, mają podobne powiązania lub cele, mają podobne właściwości lub metody.
Chcesz uniknąć powielania kodu lub elementów („spójności”), jednak nie możesz łączyć tych klas bezpośrednio w jedną, ze względu na różnice między nimi.
Więc twoje hierarchie są bardzo podobne do tej liczby, ale jednak istnieje więcej niż jedna:
W tym, jeszcze nie certyfikowanym, Wzorze Projektowym, KILKU PODOBNYCH HIERARCHII, SĄ POŁĄCZONE, W JEDEN HIERARCHIA, a każda wspólna lub wspólna klasa jest rozszerzana o podklasę.
Zauważ, że to rozwiązanie jest złożone, ponieważ masz już do czynienia z kilkoma hierarchiami, dlatego jest złożonym scenariuszem.
1 Klasa główna
W każdej hierarchii jest wspólna klasa „root”.
W twoim przypadku istnieje niezależna klasa „Composite” dla każdej hierarchii, która może mieć podobne właściwości i podobne metody.
Niektórych z tych członków można scalić, niektórych nie można scalić.
Zatem programista może zrobić, aby stworzyć podstawową klasę root i podklasować równoważne przypadki dla każdej hierarchii.
Na rycinie 2 możesz zobaczyć schemat tylko dla tej klasy, w którym każda klasa zachowuje przestrzeń nazw.
Członkowie są już pomijani.
Jak można zauważyć, każda klasa „Composite” nie znajduje się już w osobnej hierarchii, ale jest scalona w jedną wspólną lub wspólną hierarchię.
Następnie dodajmy członków, którzy są tacy sami, mogą zostać przeniesieni do nadklasy, a ci, którzy są inni, do każdej klasy podstawowej.
Jak już wiesz, metody „wirtualne” lub „przeciążone” są zdefiniowane w klasie podstawowej, ale zastąpione w podklasach. Jak na rysunku 3.
Zauważ, że niektóre klasy mogą nie mieć członków i możesz ulec pokusie ich usunięcia, NIE DOTUJ. Nazywa się je „Pustymi klasami”, „Liczbowymi” i innymi nazwami.
2 Podklasy
Wróćmy do pierwszego schematu. Każda klasa „Composite” miała podklasę „Viewee” w każdej hierarchii.
Proces powtarza się dla każdej klasy. Uwaga niż na rysunku 4, klasa „Common :: Viewee” wywodzi się z „Common :: Composite”, ale dla uproszczenia klasa „Common :: Composite” została pominięta na schemacie.
Zauważysz, że „Canvas :: Viewee” i „SVG :: Viewee” NIE ZNALEZI już schodzić z odpowiedniego „Composite”, ale zamiast wspólnego „Common :: Viewee”.
Możesz teraz dodać członków.
3 Powtórz proces
Proces będzie kontynuowany, dla każdej klasy „Canvas :: Visual” nie zejdzie z „Canvas :: Viewee”, buit z „Commons :: Visual”, „Canvas :: Structural” nie zejdzie z „Canvas :: Viewee” ”, buit z„ Commons :: Structural ”i tak dalej.
4 Diagram hierarchii 3D
Zakończysz otrzymywanie diagramu 3D z kilkoma warstwami, górna warstwa ma hierarchię „Wspólną”, a dolna warstwa ma każdą dodatkową hierarchię.
Twoje oryginalne niezależne hierarchie klas, w których coś podobnego do tego (Rysunek 6):
Zauważ, że niektóre klasy zostały pominięte, a cała hierarchia „Canvas” jest pominięta.
Ostateczna zintegrowana hierarchia klas może być podobna do tej:
Zauważ, że niektóre klasy są pomijane, a całe klasy „Canvas” są pomijane, dla uproszczenia, ale będą podobne do klas „SVG”.
Klasy „Common” mogą być reprezentowane jako pojedyncza warstwa diagramu 3D, klasy „SVG” w innej warstwie, a klasy „Canvas” w trzeciej warstwie.
Sprawdź, czy każda warstwa jest powiązana z pierwszą, w której każda klasa ma klasę nadrzędną w hierarchii „Common”.
Implementacja kodu może wymagać użycia dziedziczenia interfejsu, dziedziczenia klas lub „miksów”, w zależności od obsługiwanego języka programowania.
streszczenie
Jak każde rozwiązanie programistyczne, nie spiesz się z optymalizacją, optymalizacja jest bardzo ważna, ale zła optymalizacja może stać się większym problemem niż problem pierwotny.
Nie zalecam stosowania „Rozwiązania 1” ani „Rozwiązania 2”.
W „Rozwiązaniu 1” nie ma zastosowania, ponieważ w każdym przypadku wymagane jest dziedziczenie.
Można zastosować „Rozwiązanie 2”, „Mixiny”, ale po zaprojektowaniu klas i hierarchii.
Mixiny stanowią alternatywę dla dziedziczenia opartego na interfejsie lub wieloskładnikowego opartego na klasach.
Moje zaproponowane rozwiązanie 3 nazywane jest czasem „Wzorem projektowym„ równoległej hierarchii ”lub„ Wzorem projektowym podwójnej hierarchii ”.
Wielu programistów / projektantów nie zgodzi się z tym i uważa, że nie powinno istnieć. Ale użyłem miself i innych programistów, jako wspólnego rozwiązania problemów, takich jak jedno z twoich pytań.
Kolejna brakująca rzecz. W twoich poprzednich rozwiązaniach głównym problemem nie było użycie „miksów” lub „interfejsów”, ale udoskonalenie, po pierwsze, modelu twoich klas, a później użycia istniejącej funkcji języka programowania.
źródło
canvas.viewee
i wszyscy jego potomkowie potrzebują metody zwanejpaint()
. Ani klasy,common
anisvg
klasy tego nie potrzebują. Ale w roztworze, hierarchia jest wcommon
niecanvas
lubsvg
tak jak w moim roztworu 2. Tak jak dokładniepaint()
kończy się we wszystkich podklascanvas.viewee
, jeśli nie ma tam dziedziczenia?paint()
należy przenieść lub zadeklarować w „canvas :: viewee”. Ogólna idea wzoru pozostaje, ale niektórzy członkowie mogą wymagać przeniesienia lub zmiany.canvas::viewee
?W artykule zatytułowanym Wzory projektowe do radzenia sobie z hierarchiami podwójnego dziedziczenia w C ++ , wujek Bob przedstawia rozwiązanie o nazwie Stairway to Heaven . Stwierdzono zamiar:
I schemat pod warunkiem:
Chociaż w rozwiązaniu 2 nie ma wirtualnego dziedziczenia, jest ono w dużej mierze zgodne ze wzorem Stairway to Heaven . Zatem rozwiązanie 2 wydaje się uzasadnione w przypadku tego problemu.
źródło