Избежание параллельных иерархий наследования - PullRequest
33 голосов
/ 30 марта 2009

У меня есть две параллельные цепочки наследования:

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

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

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

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

Ответы [ 6 ]

12 голосов
/ 30 марта 2009

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

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 (и внедрить этот новый метод во всех реализациях интерфейса Formatter).
Но я думаю, что это лучше, чем создавать новый класс Vehicle, и для каждого имеющегося у вас IVehicleFormatter создайте новый класс, который сможет обрабатывать этот новый тип транспортного средства.

8 голосов
/ 30 марта 2009

Другой подход заключается в использовании модели 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 или другие жесткие форматы).

2 голосов
/ 18 сентября 2016

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

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

enter image description here

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

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

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

Vehicle is Абстракция

Car и Truck равны Уточненная абстракция

Formatter is Реализатор

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

похожий пост:

Когда вы используете шаблон моста? Чем он отличается от шаблона адаптера?

2 голосов
/ 30 марта 2009

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

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

1 голос
/ 30 марта 2009

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

0 голосов
/ 18 сентября 2016

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

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