2009-03-30 10 views
32

Tôi có hai chuỗi thừa kế song song:Tránh thừa kế song song phân cấp

Vehicle <- Car 
     <- Truck <- etc. 

VehicleXMLFormatter <- CarXMLFormatter 
        <- TruckXMLFormatter <- etc. 

Kinh nghiệm của tôi đã được rằng phân cấp thừa kế song song có thể trở thành một cơn đau đầu bảo trì khi chúng lớn.

tức là KHÔNG thêm toXML(), toSoap(), toYAML() phương pháp vào các lớp học chính của tôi.

Làm cách nào để tránh phân cấp thừa kế song song mà không vi phạm khái niệm tách mối quan tâm?

Trả lời

12

Tôi đang nghĩ đến việc sử dụng mẫu Khách truy cập.

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

Với điều này, bạn tránh một cây thừa kế thừa và giữ logic định dạng tách biệt với lớp Xe của bạn. Offcourse, khi bạn tạo một chiếc xe mới, bạn sẽ phải thêm một phương thức khác vào giao diện Formatter (và thực hiện phương thức mới này trong tất cả các triển khai của giao diện định dạng).
Nhưng, tôi nghĩ rằng điều này là tốt hơn sau đó tạo ra một lớp xe mới, và cho mỗi IVehicleFormatter bạn có, tạo ra một lớp mới có thể xử lý loại xe mới này.

+0

Nó có thể là tốt hơn để đổi tên IVehicleFormatterVisitor chỉ IVehicleVisitor, vì nó là một cơ chế chung nhiều hơn chỉ là để định dạng. – Richard

+0

bạn hoàn toàn đúng. –

+0

Giải pháp apt. +1 –

1

Tại sao không làm cho IXMLFormatter giao diện với các phương thức toXML(), toSoap(), thành YAML() và làm cho Xe, Xe và Xe tải đều thực hiện điều đó? Có gì sai với cách tiếp cận đó?

+4

Đôi khi không có gì. Những lần khác bạn không muốn lớp xe của bạn phải biết về XML/SOAP/YAML - bạn muốn nó tập trung vào việc mô hình hóa một chiếc xe, và để giữ riêng biệt biểu tượng đánh dấu. –

+4

Nó phá vỡ khái niệm rằng một lớp nên có một trách nhiệm duy nhất. – parkr

+1

Đó là tất cả về * sự gắn kết *. Thường có nhiều khía cạnh hoặc đặc điểm của một thực thể kéo thiết kế của nó theo các hướng khác nhau, ví dụ: "Một thực tế ở một nơi" và "Nguyên tắc trách nhiệm duy nhất". Công việc của bạn như một nhà thiết kế là quyết định khía cạnh nào "thắng" bằng cách tối đa hóa sự gắn kết. Đôi khi, toXML(), toSOAP(), vv sẽ có ý nghĩa, nhưng trong các hệ thống nhiều tầng, tốt hơn là giữ logic trình bày riêng biệt với mô hình, tức là khía cạnh của bản trình bày ở định dạng cụ thể (XML, SOAP, v.v.) được gắn kết nhiều hơn * trên các thực thể * dưới dạng một cấp, thay vì gắn với các chi tiết cụ thể của mỗi thực thể. –

2

Bạn có thể cố gắng tránh kế thừa cho các trình định dạng của mình. Chỉ cần thực hiện một số VehicleXmlFormatter có thể xử lý với Car s, Truck s, ... Việc tái sử dụng phải dễ dàng đạt được bằng cách cắt bỏ trách nhiệm giữa các phương pháp và bằng cách tìm ra một chiến lược điều phối tốt. Tránh quá tải phép thuật; càng cụ thể càng tốt trong các phương pháp đặt tên trong trình định dạng của bạn (ví dụ: formatTruck(Truck ...) thay vì format(Truck ...)).

Chỉ sử dụng khách truy cập nếu bạn cần công văn kép: khi bạn có đối tượng thuộc loại Vehicle và bạn muốn định dạng chúng thành XML mà không biết loại bê tông thực tế. Bản thân khách truy cập không giải quyết vấn đề cơ bản về việc sử dụng lại trong trình định dạng của bạn và có thể giới thiệu thêm sự phức tạp mà bạn có thể không cần. Các quy tắc trên để tái sử dụng bằng các phương thức (cắt và gửi) cũng sẽ áp dụng cho việc thực hiện Khách truy cập của bạn.

8

Cách tiếp cận khác là áp dụng mô hình đẩy thay vì mô hình kéo. Thông thường, bạn cần các trình định dạng khác nhau vì bạn đang phá vỡ đóng gói và có một cái gì đó như:

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

nơi bạn đang lấy dữ liệu từ loại cụ thể vào trình định dạng.

Thay vào đó, tạo ra một bồn rửa dữ liệu định dạng-agnostic và đảo ngược dòng chảy để loại cụ thể đẩy dữ liệu vào bồn rửa chén

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

Điều đó có nghĩa bạn vẫn bị các dữ liệu đóng gói, và bạn chỉ cần cho ăn đã gắn thẻ dữ liệu vào bồn rửa chén. Sau đó, một bồn rửa XML có thể bỏ qua các phần nhất định của dữ liệu, có thể sắp xếp lại một số phần của nó và viết XML. Nó thậm chí có thể ủy thác cho chiến lược chìm khác nhau trong nội bộ. Nhưng bồn rửa không nhất thiết phải quan tâm đến loại xe, chỉ cách trình bày dữ liệu ở một số định dạng. Việc sử dụng các ID toàn cục thay vì các chuỗi nội tuyến giúp giữ cho chi phí tính toán giảm xuống (chỉ có vấn đề nếu bạn đang viết ASN.1 hoặc các định dạng chặt chẽ khác).

+1

+1 Bồn rửa này trông giống như một Builder cho tôi. –

1

Bạn có thể sử dụng Bridge_pattern

mẫu cầu tách một sự trừu tượng từ việc thực hiện vì vậy mà hai có thể khác nhau một cách độc lập.

enter image description here

Hai phân cấp lớp trực giao (The Abstraction hệ thống cấp bậc và Thực hiện hệ thống phân cấp) được liên kết sử dụng thành phần (chứ không phải thừa kế) .Đây thành phần giúp cả hai hệ thống phân cấp thay đổi một cách độc lập.

Triển khai không bao giờ đề cập đến Abstraction. Trừu tượng chứa Thực hiện giao diện như một thành viên (thông qua thành phần).

Trở lại với ví dụ của bạn:

VehicleAbstraction

CarTruckRefinedAbstraction

Formatterimplementor

XMLFormatter, POJOFormatterConcreteImplementor

Pseudo mã:

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

bài liên quan:

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

0

Tôi muốn thêm generics vào Frederiks trả lời.

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 
}