2009-03-30 20 views
32

Mam dwa równoległe łańcuchy dziedziczenia:Unikanie równoległego dziedziczenie hierarchie

Vehicle <- Car 
     <- Truck <- etc. 

VehicleXMLFormatter <- CarXMLFormatter 
        <- TruckXMLFormatter <- etc. 

Moje doświadczenie jest takie, że równoległe hierarchie dziedziczenia może stać się ból głowy konserwacji, jak rosną.

tj. NIE dodając metod toXML(), toSoap(), toYAML() do moich głównych klas.

Jak uniknąć równoległej hierarchii dziedziczenia bez przerywania koncepcji rozdzielania obaw?

Odpowiedz

12

Mam na myśli wykorzystanie wzoru Odwiedzającego.

public class Car : Vehicle 
{ 
    public void Accept(IVehicleFormatter v) 
    { 
     v.Visit (this); 
    } 
} 

public class Truck : Vehicle 
{ 
    public void Accept(IVehicleFormatter v) 
    { 
     v.Visit (this); 
    } 
} 

public interface IVehicleFormatter 
{ 
    public void Visit(Car c); 
    public void Visit(Truck t); 
} 

public class VehicleXmlFormatter : IVehicleFormatter 
{ 
} 

public class VehicleSoapFormatter : IVehicleFormatter 
{ 
} 

Dzięki temu można uniknąć dodatkowego drzewa dziedziczenia i zachować logikę formatowania oddzieloną od klas pojazdów. Jeśli podczas tworzenia nowego pojazdu musisz dodać inną metodę do interfejsu Formatter (i zaimplementuj tę nową metodę we wszystkich implementacjach interfejsu formatyzatora).
Ale myślę, że to jest lepsze niż stworzenie nowej klasy Pojazdów, a dla każdego posiadanego przez Ciebie IVehicleFormatter, stwórz nową klasę, która poradzi sobie z tym nowym typem pojazdu.

+0

Lepiej może zmienić nazwę IVehicleFormatterVisitor na tylko IVehicleVisitor, ponieważ jest to mechanizm bardziej ogólny niż formatowanie. – Richard

+0

masz absolutną rację. –

+0

Właściwe rozwiązanie. +1 –

1

Dlaczego nie uczynić IXMLFormatter interfejsem z toXML(), toSoap(), metodami YAML() i sprawić, że pojazd, samochód i ciężarówka będą zaimplementowane? Co jest nie tak z tym podejściem?

+4

Czasami nic. Innym razem nie chcesz, aby twoja klasa pojazdów wiedziała o XML/SOAP/YAML - chcesz skoncentrować się na modelowaniu pojazdu i zachować oddzielną reprezentację znaczników. –

+4

To łamie pojęcie, że klasa powinna ponosić jedną odpowiedzialność. – parkr

+1

Chodzi o * spójność *. Często istnieje wiele aspektów lub cech jednostki, która ściąga swój projekt w różnych kierunkach, np. "Jeden fakt w jednym miejscu" a "Zasada odpowiedzialności pojedynczej". Twoja praca jako projektanta polega na decydowaniu, który aspekt "wygrywa" poprzez maksymalizację spójności. Czasami toXML(), toSOAP() itd. Będą miały sens, ale w większych, wielopoziomowych systemach lepiej jest zachować logikę prezentacji od modelu, tj. Aspekt prezentacji w określonym formacie (XML, SOAP, itp.) jest bardziej spójny * w poprzek jednostek * jako warstwa, niż związana ze specyfiką każdej jednostki. –

2

Możesz spróbować uniknąć dziedziczenia swoich formaterów. Po prostu zrób VehicleXmlFormatter, który może zajmować się Car s, Truck s, ... Ponowne użycie powinno być łatwe do osiągnięcia poprzez przerwanie odpowiedzialności między metodami i przez opracowanie dobrej strategii wysyłki. Unikaj przeładowania magią; być jak najbardziej konkretnym w nazewnictwie metod w twoim formatyzatorze (np. formatTruck(Truck ...) zamiast format(Truck ...)).

Używaj tylko Gościa, jeśli potrzebujesz podwójnej wysyłki: gdy masz obiekty typu Vehicle i chcesz sformatować je w formacie XML, nie znając faktycznego rodzaju betonu. Sam gość nie rozwiązuje podstawowego problemu związanego z ponownym wykorzystaniem w programie formatującym i może wprowadzić dodatkową złożoność, której może nie potrzebować. Powyższe zasady dotyczące ponownego użycia metodami (krojenie i wysyłka) miałyby zastosowanie również do implementacji użytkownika.

8

Innym podejściem jest zastosowanie modelu push, a nie modelu pull. Zazwyczaj trzeba różne formatek bo łamiesz hermetyzacji i mieć coś takiego:

class TruckXMLFormatter implements VehicleXMLFormatter { 
    public void format (XMLStream xml, Vehicle vehicle) { 
     Truck truck = (Truck)vehicle; 

     xml.beginElement("truck", NS). 
      attribute("name", truck.getName()). 
      attribute("cost", truck.getCost()). 
      endElement(); 
... 

gdzie jesteś ciągnięcie danych z określonego typu do formatyzatora.

Zamiast tworzyć umywalkę danych format-agnostyk i odwrócić przepływ tak specyficzny rodzaj wypycha dane do zlewu

class Truck implements Vehicle { 
    public DataSink inspect (DataSink out) { 
     if (out.begin("truck", this)) { 
      // begin returns boolean to let the sink ignore this object 
      // allowing for cyclic graphs. 
      out.property("name", name). 
       property("cost", cost). 
       end(this); 
     } 

     return out; 
    } 
... 

Oznacza to, że wciąż masz dane obudowane, a ty po prostu karmienia otagowano dane do zlewu. Umywalka XML może wtedy zignorować określone części danych, może zmienić ich kolejność i zapisać kod XML. Może nawet przekazać wewnętrznie inną strategię umywalkową. Ale umywalka niekoniecznie musi dbać o typ pojazdu, tylko o to, jak przedstawiać dane w jakimś formacie. Używanie internowanych identyfikatorów globalnych zamiast ciągów wbudowanych pomaga obniżyć koszty obliczeń (ma to znaczenie tylko w przypadku pisania ASN.1 lub innych wąskich formatów).

+1

+1 Ten zlew wygląda dla mnie jak budowniczy. –

1

Można użyć Bridge_pattern

Most wzór oddzielić abstrakcję z jego realizacji tak, że dwa mogą się zmieniać niezależnie.

enter image description here

dwóch prostopadłych klas hierarchii (The abstrakcji hierarchii Wykonanie hierarchii) są połączone za pomocą kompozycji (a nie spadku) .Ta kompozycja pomaga zarówno hierarchii zmieniać niezależnie.

Implementacja nigdy nie dotyczy Abstrakcja. Abstrakcja zawiera Implementacja interfejsu jako członek (poprzez kompozycję).

Wracając do przykładu:

Vehicle jest Abstraction

Car i TruckRefinedAbstraction

Formatter jest implementor

XMLFormatter, POJOFormatterConcreteImplementor

Pseudo kod:

Formatter formatter = new XMLFormatter(); 
Vehicle vehicle = new Car(formatter); 
vehicle.applyFormat(); 

formatter = new XMLFormatter(); 
vehicle = new Truck(formatter); 
vehicle.applyFormat(); 

formatter = new POJOFormatter(); 
vehicle = new Truck(formatter); 
vehicle.applyFormat(); 

związane postu:

When do you use the Bridge Pattern? How is it different from Adapter pattern?

0

Chcę dodać generycznych do odpowiedzi Frederiksów.

public class Car extends Vehicle 
{ 
    public void Accept(VehicleFormatter v) 
    { 
     v.Visit (this); 
    } 
} 

public class Truck extends Vehicle 
{ 
    public void Accept(VehicleFormatter v) 
    { 
     v.Visit (this); 
    } 
} 

public interface VehicleFormatter<T extends Vehicle> 
{ 
    public void Visit(T v); 
} 

public class CarXmlFormatter implements VehicleFormatter<Car> 
{ 
    //TODO: implementation 
} 

public class TruckXmlFormatter implements VehicleFormatter<Truck> 
{ 
    //TODO: implementation 
}