避免并行继承层次结构

发布于 2024-07-16 05:38:56 字数 321 浏览 4 评论 0原文

我有两个并行继承链:

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

我的经验是,随着并行继承层次结构的增长,它们可能会成为维护难题。

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

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

I have two parallel inheritance chains:

Vehicle <- Car
        <- Truck <- etc.

VehicleXMLFormatter <- CarXMLFormatter
                    <- TruckXMLFormatter <- etc.

My experience has been that parallel inheritance hierarchies can become a maintenance headache as they grow.

i.e. NOT adding toXML(), toSoap(), toYAML() methods to my principal classes.

How do I avoid a parallel inheritance hierarchy without breaking the concept of separation of concerns?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

悲歌长辞 2024-07-23 05:38:56

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

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

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

I am thinking of using the Visitor pattern.

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

With this, you avoid an extra inheritance tree, and keep the formatting logic separated from your Vehicle-classes.
Offcourse, when you create a new vehicle, you'll have to add another method to the Formatter interface (and implement this new method in all the implementations of the formatter interface).
But, I think that this is better then creating a new Vehicle class, and for every IVehicleFormatter you have, create a new class that can handle this new kind of vehicle.

昇り龍 2024-07-23 05:38:56

另一种方法是采用推模型而不是拉模型。 通常,您需要不同的格式化程序,因为您破坏了封装,并且有类似的情况:

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 或其他严格格式时才重要)。

Another approach is to adopt a push model rather than a pull model. Typically you need different formatters because you're breaking encapsulation, and have something like:

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

where you're pulling data from the specific type into the formatter.

Instead, create a format-agnostic data sink and invert the flow so the specific type pushes data to the sink

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

That means you've still got the data encapsulated, and you're just feeding tagged data to the sink. An XML sink might then ignore certain parts of the data, maybe reorder some of it, and write the XML. It could even delegate to different sink strategy internally. But the sink doesn't necessarily need to care about the type of the vehicle, only how to represent the data in some format. Using interned global IDs rather than inline strings helps keep the computation cost down (only matters if you're writing ASN.1 or other tight formats).

萌无敌 2024-07-23 05:38:56

您可以尝试避免格式化程序的继承。 只需制作一个可以处理 CarTruck 等的 VehicleXmlFormatter 即可,通过划分之间的职责,可以轻松实现重用方法并找出良好的调度策略。 避免魔法过载; 格式化程序中的命名方法应尽可能具体(例如 formatTruck(Truck ...) 而不是 format(Truck ...))。

仅当需要双重分派时才使用 Visitor:当您拥有 Vehicle 类型的对象并且希望将它们格式化为 XML 而不知道实际的具体类型时。 访问者本身并不能解决在格式化程序中实现重用的基本问题,并且可能会引入您可能不需要的额外复杂性。 上述方法重用的规则(切碎和分派)也适用于您的 Visitor 实现。

You could try to avoid inheritance for your formatters. Simply make a VehicleXmlFormatter that can deal with Cars, Trucks, ... Reuse should be easy to achieve by chopping up the responsibilities between methods and by figuring out a good dispatch-strategy. Avoid overloading magic; be as specific as possible in naming methods in your formatter (e.g. formatTruck(Truck ...) instead of format(Truck ...)).

Only use Visitor if you need the double dispatch: when you have objects of type Vehicle and you want to format them into XML without knowing the actual concrete type. Visitor itself doesn't solve the base problem of achieving reuse in your formatter, and may introduce extra complexity you may not need. The rules above for reuse by methods (chopping up and dispatch) would apply to your Visitor implementation as well.

一身骄傲 2024-07-23 05:38:56

您可以使用 Bridge_pattern

桥接模式将抽象与其实现分离,以便两者可以独立变化。

输入图片此处描述

两个正交类层次结构(抽象层次结构和实现层次结构)相互链接使用组合(而不是继承)。这种组合有助于两个层次结构独立变化。

实现从来不涉及抽象。 抽象包含实现接口作为成员(通过组合)。

回到您的示例:

Vehicle抽象

CarTruck >RefinedAbstraction

Formatter实现者

XMLFormatterPOJOFormatterConcreteImplementor

伪代码:

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

相关帖子:

什么时候使用桥接模式? 它与适配器模式有何不同?

You can use Bridge_pattern

Bridge pattern decouple an abstraction from its implementation so that the two can vary independently.

enter image description here

Two orthogonal class hierarchies (The Abstraction hierarchy and Implementation hierarchy) are linked using composition (and not inheritance).This composition helps both hierarchies to vary independently.

Implementation never refers Abstraction. Abstraction contains Implementation interface as a member (through composition).

Coming back to your example:

Vehicle is Abstraction

Car and Truck are RefinedAbstraction

Formatter is Implementor

XMLFormatter, POJOFormatter are ConcreteImplementor

Pseudo code:

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

related post:

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

牛↙奶布丁 2024-07-23 05:38:56

为什么不让 IXMLFormatter 成为一个带有 toXML()、toSoap()、to YAML() 方法的接口,并让 Vehicle、Car 和 Truck 都实现它呢? 这种方法有什么问题吗?

Why not make IXMLFormatter an interface with toXML(), toSoap(), to YAML() methods and make the Vehicle, Car and Truck all implement that? What is wrong with that approach?

顾冷 2024-07-23 05:38:56

我想在 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
}

I want to add generics to Frederiks answer.

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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文