我有两个并行的继承链:
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-classes分开.当然,当你创建一个新的车辆时,你将不得不向Formatter接口添加另一个方法(并在formatter接口的所有实现中实现这个新方法).
但是,我认为这比创建一个新的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.它甚至可以在内部委托不同的汇策略.但是接收器不一定需要关心车辆的类型,只需要如何以某种格式表示数据.使用实体化的全局ID而不是内联字符串有助于降低计算成本(仅在编写ASN.1或其他严格格式时才有意义).