我有两个并行的继承链:
Vehicle <- Car
<- Truck <- etc.
VehicleXMLFormatter <- CarXMLFormatter
<- TruckXMLFormatter <- etc.
我的经验是,并行继承层次结构随着它们的增长会成为一种维护问题。
即。不要将toXML(), toSoap(), toYAML()
方法添加到我的主要类中。
如何在不违反关注点分离概念的情况下避免并行继承层次结构?
答案 0 :(得分: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-classes分开。
当然,当你创建一个新的车辆时,你将不得不向Formatter接口添加另一个方法(并在格式化程序界面的所有实现中实现这个新方法)。
但是,我认为这比创建一个新的Vehicle类更好,并且对于你拥有的每个IVehicleFormatter,创建一个可以处理这种新型车辆的新类。
答案 1 :(得分:8)
另一种方法是采用推模型而不是拉模型。通常你需要不同的格式化程序,因为你打破了封装,并且有类似的东西:
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或其他严格格式时才有意义)。
答案 2 :(得分:2)
您可以尝试避免格式化程序的继承。只需制作一个可以处理VehicleXmlFormatter
s,Car
s的Truck
,......通过切断方法之间的责任并通过确定一个好的调度,可以很容易地实现重用 - 战略。避免超载魔法;在格式化程序的命名方法中尽可能具体(例如formatTruck(Truck ...)
而不是format(Truck ...)
)。
如果您需要双重调度,则仅使用“访问者”:当您拥有类型为Vehicle
的对象并且希望在不知道实际具体类型的情况下将其格式化为XML。访问者本身并不能解决在格式化程序中实现重用的基本问题,并且可能会引入您可能不需要的额外复杂性。上述通过方法重复使用的规则(切断和发送)也适用于您的访客实施。
答案 3 :(得分:1)
为什么不使IXMLFormatter成为与toXML(),toSoap(),YAML()方法的接口,并让Vehicle,Car和Truck都实现这一点?这种方法有什么问题?
答案 4 :(得分:1)
您可以使用Bridge_pattern
桥接模式将抽象与其实现分离,以便两者可以独立变化。
两个正交的类层次结构( 抽象 层次结构和 实现 层次结构)使用组合进行链接(而不是继承)。这种组合有助于两个层次结构独立变化。
实施永远不会引用 抽象 。抽象包含 实施 接口作为成员(通过合成)。
回到你的榜样:
Vehicle
抽象
Car
和Truck
RefinedAbstraction
Formatter
是 执行者
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?
答案 5 :(得分:0)
我想在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
}