2009-03-30 16 views
32

ho due catene di ereditarietà paralleli:Evitare l'ereditarietà parallelo gerarchie

Vehicle <- Car 
     <- Truck <- etc. 

VehicleXMLFormatter <- CarXMLFormatter 
        <- TruckXMLFormatter <- etc. 

La mia esperienza è stata che gerarchie di ereditarietà parallelo può diventare un mal di testa di manutenzione man mano che crescono.

, ovvero NON aggiunge i metodi toXML(), toSoap(), toYAML() alle mie classi principali.

Come evitare una gerarchia di ereditarietà parallela senza rompere il concetto di separazione delle preoccupazioni?

risposta

12

Sto pensando di utilizzare il pattern Visitor.

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 
{ 
} 

Con questo, si evita un albero di eredità in più, e mantenere la logica di formattazione separati dal vostro veicolo-classi. Offcourse, quando crei un nuovo veicolo, dovrai aggiungere un altro metodo all'interfaccia Formatter (e implementare questo nuovo metodo in tutte le implementazioni dell'interfaccia del formattatore).
Ma, penso che questo sia meglio di creare una nuova classe Veicolo, e per ogni IVehicleFormatter che hai, creare una nuova classe in grado di gestire questo nuovo tipo di veicolo.

+0

Potrebbe essere meglio rinominare IVehicleFormatterVisitor solo su IVehicleVisitor, in quanto è un meccanismo più generico rispetto alla semplice formattazione. – Richard

+0

hai assolutamente ragione. –

+0

Una soluzione apt. +1 –

1

Perché non rendere IXMLFormatter un'interfaccia con toXML(), toSoap(), con i metodi YAML() e fare in modo che Vehicle, Car e Truck lo implementino? Cosa c'è di sbagliato in questo approccio?

+4

A volte niente. Altre volte non vuoi che la tua classe veicolo debba sapere su XML/SOAP/YAML - vuoi che si concentri sulla modellazione di un veicolo e per mantenere separata la rappresentazione del markup. –

+4

Rompe la nozione che una classe dovrebbe avere una singola responsabilità. – parkr

+1

Si tratta di * coesione *. Vi sono spesso molteplici aspetti o tratti di un'entità che ne estrae il design in diverse direzioni, ad es. "One Fact In One Place" vs "Single Responsibility Principle". Il tuo lavoro come designer è decidere quale aspetto "vince" massimizzando la coesione. A volte, toXML(), toSOAP(), ecc. Ha senso, ma nei sistemi a più livelli, è meglio mantenere la logica di presentazione separata dal modello, cioè l'aspetto della presentazione in un formato specifico (XML, SOAP, ecc.) è più coeso * tra entità * come livello, piuttosto che legato alle specifiche di ogni entità. –

2

Si potrebbe provare a evitare l'ereditarietà per i propri formattatori. Basta fare un VehicleXmlFormatter che può occuparsi di Car s, Truck s, ... Il riutilizzo dovrebbe essere facile da raggiungere riducendo le responsabilità tra i metodi e calcolando una buona strategia di spedizione. Evitare di sovraccaricare la magia; sii il più specifico possibile nei metodi di denominazione nel tuo formattatore (ad esempio formatTruck(Truck ...) anziché format(Truck ...)).

Utilizzare solo Visitor se è necessario il doppio invio: quando si dispone di oggetti di tipo Vehicle e si desidera formattare in XML senza conoscere il tipo concreto concreto. Il visitatore stesso non risolve il problema di base del riutilizzo nel proprio programma di formattazione e può introdurre una complessità extra che potrebbe non essere necessaria. Le regole di cui sopra per il riutilizzo con metodi (taglio e spedizione) si applicherebbero anche all'implementazione del visitatore.

8

Un altro approccio consiste nell'adottare un modello push anziché un modello pull. In genere è necessario diverse formattatori perché si sta rompendo l'incapsulamento, e avere qualcosa di simile:

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(); 
... 

dove si sta tirando i dati dal tipo specifico nel formattatore.

Invece, creare un lavandino di dati in formato-agnostico e invertire il flusso in modo che il tipo specifico di dati spinge al lavandino

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; 
    } 
... 

Ciò significa che hai ancora i dati incapsulati, e si sta solo alimentando dati taggati al lavandino. Un sink XML potrebbe quindi ignorare determinate parti dei dati, magari riordinarne alcune e scrivere l'XML. Potrebbe persino delegare a diverse strategie di sink internamente. Ma il dissipatore non ha necessariamente bisogno di preoccuparsi del tipo di veicolo, solo come rappresentare i dati in qualche formato. L'utilizzo di ID globali internati anziché di stringhe inline consente di ridurre i costi di calcolo (solo se si scrive ASN.1 o altri formati stretti).

+1

+1 Questo lavello sembra un Costruttore per me. –

1

È possibile utilizzare Bridge_pattern

modello Ponte disaccoppiare un'astrazione dalla sua applicazione in modo che i due possono variare indipendentemente.

enter image description here

Due gerarchie di classi ortogonali (Il astrazione gerarchia e Attuazione gerarchia) sono collegati con la composizione (e non l'ereditarietà) .Questo composizione aiuta entrambe le gerarchie di variare in modo indipendente.

L'implementazione non si riferisce mai Astrazione. L'astrazione contiene Implementazione interfaccia come membro (tramite composizione).

Tornando al tuo esempio:

Vehicle è Astrazione

Car e Truck sono RefinedAbstraction

Formatter è Implementor

XMLFormatter, POJOFormatter sono ConcreteImplementor

Pseudo codice:

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(); 

post correlati:

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

0

Voglio aggiungere i generici alla risposta di Frederiks.

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 
}