避免并行继承层次结构

2022-09-01 06:59:59

我有两个并行继承链:

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

我的经验是,并行继承层次结构随着其增长而成为维护难题。

即不向我的主要类添加方法。toXML(), toSoap(), toYAML()

如何在不破坏关注点分离概念的情况下避免并行继承层次结构?


答案 1

我正在考虑使用访客模式。

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

这样,您可以避免额外的继承树,并将格式化逻辑与车辆类分开。当然,当您创建新车辆时,您必须向格式化程序接口添加另一种方法(并在格式化程序接口的所有实现中实现此新方法)。
但是,我认为这比创建一个新的车辆类要好,并且对于您拥有的每个IVehicleFormatter,创建一个可以处理这种新型车辆的新类。


答案 2

另一种方法是采用推送模型而不是拉动模型。通常,您需要不同的格式化程序,因为您正在破坏封装,并且具有如下内容:

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。它甚至可以在内部委托给不同的汇策略。但接收器并不一定需要关心车辆的类型,只需要关心如何以某种格式表示数据。使用内部全局 ID 而不是内联字符串有助于降低计算成本(只有在编写 ASN.1 或其他紧密格式时才重要)。