Как избежать параллельных иерархий наследования

StackOverflow https://stackoverflow.com/questions/696350

Вопрос

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

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

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

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

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

Это было полезно?

Решение

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

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, и для каждого имеющегося у вас IVehicleFormatter создавать новый класс, который может обрабатывать этот новый тип транспортного средства.

Другие советы

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

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 или другие жесткие форматы).

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

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

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

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

enter image description here

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

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

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

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

Car и Truck являются ИзысканнаяАбстракция

Formatter является Реализатор

XMLFormatter, POJOFormatter являются БетонРеализатор

Псевдокод:

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

связанный пост:

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

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

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

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
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top