2009-03-30 4 views
32

У меня есть две параллельные цепочки наследования:избежать параллельного наследования иерархий

Vehicle <- Car 
     <- Truck <- etc. 

VehicleXMLFormatter <- CarXMLFormatter 
        <- TruckXMLFormatter <- etc. 

Мой опыт показывает, что параллельные иерархии наследования может стать поддержание головной боли, как они растут.

i.e. НЕ добавляет toXML(), toSoap(), toYAML() методов моим основным классам.

Как избежать параллельной иерархии наследования без нарушения концепции разделения проблем?

ответ

12

Я думаю использовать шаблон посетителя.

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

При этом вы избегаете дополнительного дерева наследования и сохраняете логику форматирования отдельно от классов Vehicle. Если вы создаете новый автомобиль, вам придется добавить еще один способ к интерфейсу Formatter (и реализовать этот новый метод во всех реализациях интерфейса форматирования).
Но я думаю, что это лучше, чем создание нового класса Vehicle, и для каждого вашего IVehicleFormatter, который у вас есть, создайте новый класс, который может обрабатывать этот новый вид транспортного средства.

+0

Возможно, лучше переименовать IVehicleFormatterVisitor только в IVehicleVisitor, поскольку это более общий механизм, чем просто для форматирования. – Richard

+0

вы абсолютно правы. –

+0

Удобное решение. +1 –

1

Почему бы не сделать IXMLFormatter интерфейсом с методами toXML(), toSoap(), YAML() и сделать все это для транспортного средства, автомобиля и грузовика? Что не так с этим подходом?

+4

Иногда ничего. В других случаях, когда вы не хотите, чтобы ваш класс транспортных средств должен был знать о XML/SOAP/YAML, вы хотите сосредоточиться на моделировании транспортного средства и сохранить разметку. –

+4

Это нарушает представление о том, что класс должен иметь одну ответственность. – parkr

+1

Это все о * сплоченности *. Часто существует несколько аспектов или признаков объекта, которые тянут его дизайн в разных направлениях, например. «Один факт в одном месте» против «Принципа единой ответственности». Ваша работа в качестве дизайнера заключается в том, чтобы решить, какой аспект «выигрывает», максимизируя сплоченность. Иногда toXML(), toSOAP() и т. Д. Будет иметь смысл, но в более крупных многоуровневых системах лучше оставить логику презентации отдельно от модели, то есть аспект представления в определенном формате (XML, SOAP, и т. д.) более сплоченно * через сущности * как уровень, а не привязан к специфике каждого объекта. –

2

Вы можете попытаться избежать наследования для своих форматировщиков. Просто сделайте VehicleXmlFormatter, который может иметь дело с Car s, Truck s, ... Повторное использование должно быть легко достигнуто путем измельчения обязанностей между методами и определения хорошей стратегии отправки. Избегайте перегрузки магии; быть как можно более конкретными в методах именования в вашем форматировании (например, formatTruck(Truck ...) вместо format(Truck ...)).

Используйте только посетителя, если вам нужна двойная отправка: если у вас есть объекты типа Vehicle, и вы хотите отформатировать их в XML, не зная конкретного конкретного типа. Сам посетитель не решает базовую проблему достижения повторного использования в вашем форматировании и может ввести дополнительную сложность, которая вам может не понадобиться. Вышеупомянутые правила для повторного использования методами (измельчение и отправка) применимы и к вашей реализации Visitor.

8

Другим подходом является принятие модели push, а не модели pull. Как правило, вам нужны различные форматтеры, потому что вы нарушаете инкапсуляцию, и есть что-то вроде:

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

где вы вытягивать данные из определенного типа в форматер.

Вместо создания формата агностик мойку данных и инвертировать поток таким образом, конкретный тип выталкивает данные в раковине

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

Это означает, что вы все еще получили инкапсулированные данные, и вы просто кормления помеченные данные в раковину. После этого XML-приемник может игнорировать определенные части данных, возможно, переупорядочить некоторые из них и написать XML. Он мог бы даже делегировать другую стратегию раковины внутри страны. Но раковина не обязательно должна заботиться о типе транспортного средства, а только о том, как представлять данные в некотором формате. Использование интернированных глобальных идентификаторов, а не встроенных строк помогает снизить стоимость вычислений (имеет значение только при написании ASN.1 или других жестких форматов).

+1

+1 Эта раковина для меня выглядит как Строитель. –

1

Вы можете использовать Bridge_pattern

шаблон Bridge отвязать абстракцию от ее реализации так, что два могут изменяться независимо друг от друга.

enter image description here

Два ортогональных иерархии класса (абстракция иерархии и Реализации иерархии) связаны с использованием композиции (а не наследования) .Это композиция помогает как иерархиям изменять независимо друг от друга.

Внедрение никогда не упоминается Абстракция. Абстракция содержит Реализация интерфейс в качестве участника (через композицию).

Возвращаясь к вашему примеру:

Vehicle является Абстракция

Car и Truck являются RefinedAbstraction

Formatter является Implementor

XMLFormatter, POJOFormatter являются ConcreteImplementor

Псевдо код:

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

связанная почта:

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

0

Я хочу добавить дженерики к ответу Фредерикса.

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 
}