2013-04-14 8 views
7

Tôi có một chương trình đơn giản vẽ số liệu hình học dựa trên dữ liệu chuột do người dùng cung cấp. Tôi có một lớp học xử lý theo dõi chuột (nó nhận được Danh sách có lịch sử di chuyển chuột) và một lớp học trừu tượng có tên là Hình dạng là . Từ lớp này, tôi nhận được một số hình dạng bổ sung, như Circle, Rectangle, vv - và mỗi một trong số chúng sẽ ghi đè lên hàm Draw() trừu tượng.Lấy nguyên tắc đóng mở trong quá khứ

Tất cả đều hoạt động độc đáo, nhưng sự cố xảy ra khi tôi muốn người dùng có thể chuyển đổi hình dạng mong muốn Hình theo cách thủ công. Tôi nhận được dữ liệu chuột và tôi biết tôi nên vẽ hình dạng nào. Vấn đề là làm thế nào để làm cho mã để "biết" mà đối tượng nó nên tạo và vượt qua thông số appropiate cho constructor. Cũng không thể vào thời điểm này để thêm các dẫn xuất hình dạng mới, mà là sai lầm một cách không đúng.

tôi obiously không muốn đi ra với mã như:

List<Shape> Shapes = new List<Shape>(); 
// somwhere later 

if(CurrentShape == "polyline"){ 
    Shapes.Add(new Polyline(Points)); 
} 
else if (CurrentShape == "rectangle"){ 
    Shapes.Add(new Rectangle(BeginPoint, EndPoint)); 
} 
// and so on. 

Đoạn mã trên rõ ràng vilates Open-Closed Nguyên tắc. Vấn đề là tôi không có bất kỳ ý tưởng tốt nào để vượt qua nó. Vấn đề chính là các hình dạng khác nhau có các hàm tạo với các tham số khác nhau, khiến cho nó trở nên rắc rối hơn nhiều.

Tôi khá chắc chắn rằng đây là một vấn đề phổ biến, nhưng tôi không biết làm thế nào để vượt qua nó. Bạn có ý tưởng không?

+2

Đó không phải là "Nguyên tắc đóng mở". Đó chỉ là đa hình –

+1

Vâng, tôi muốn mã lớp Hình dạng được đóng cho phiên bản và mở cho các phần mở rộng, vì vậy tôi nghĩ rằng nó phù hợp với vấn đề OCP. –

+0

Nhiều như bạn muốn nó là đúng, nó không phải! –

Trả lời

3

Nó cầu xin một nhà máy chứ không chỉ nhà máy mà nhà máy có công nhân tiêm.

public class Context { 
    public Point BeginPoint; 
    public Point EndPoint; 
    public List Points; 

    whatever else 
} 

public class ShapeFactory { 

    List<FactoryWorker> workers; 

    public Shape CreateShape(string ShapeName, Context context) 
    { 
     foreach (FactoryWorker worker in workers) 
     if (worker.Accepts(ShapeName)) 
      return worker.CreateShape(context); 
    } 

    public void AddWorker(FactoryWorker worker) { 
     workers.Add(worker); 
    } 
} 

public abstract class FactortWorker { 
    public abstract bool Accepts(string ShapeName); 
    puboic Shape CreateShape(Context context); 
} 

public class PolyLineFactoryWorker : FactoryWorker { 

    public override bool Accepts(string ShapeName) { 
     return ShapeName == "polyline"; 
    } 

    public Shape CreateShape(Context context) { ... } 

} 

Cách này mã mở cho tiện ích - công nhân nhà máy mới có thể được tạo tự do và thêm vào nhà máy.

+0

Xin chào. Tôi muốn biết, làm thế nào là tốt hơn so với câu trả lời khác mở rộng 'ShapeFactory' thay vì tiêm công nhân, nếu câu trả lời khác được sử dụng một tham số' string ShapeName' là tốt? – Blueriver

+0

@Blueriver: Điều này sẽ làm cho cả hai câu trả lời tương đương. Nhưng, không có nhà máy mở rộng trong câu trả lời khác. Các nhà sản xuất từ ​​đó là những công nhân xấu từ đây. Bản thân nhà máy không bao giờ được sửa đổi vì nó sẽ phá vỡ phần Đóng của OCP. –

+0

Tôi hiểu. Cảm ơn bạn! – Blueriver

6

Khi bạn cần tạo các đối tượng có nguồn gốc từ một lớp đơn lẻ hoặc triển khai cùng một giao diện, một cách tiếp cận chung là sử dụng factory. Trong trường hợp của bạn, tuy nhiên, một nhà máy đơn giản có thể không đủ, bởi vì bản thân nhà máy cần phải được mở rộng.

Một cách để thực hiện nó là như sau:

interface IShapeMaker { 
    IShape Make(IList<Point> points); 
} 
class RectMaker : IShapeMaker { 
    public Make(IList<Point> points) { 
     // Check if the points are good to make a rectangle 
     ... 
     if (pointsAreGoodForRectangle) { 
      return new Rectangle(...); 
     } 
     return null; // Cannot make a rectangle 
    } 
} 
class PolylineMaker : IShapeMaker { 
    public Make(IList<Point> points) { 
     // Check if the points are good to make a polyline 
     ... 
     if (pointsAreGoodForPolyline) { 
      return new Polyline(...); 
     } 
     return null; // Cannot make a polyline 
    } 
} 

Với những Maker lớp trong tay, bạn có thể làm cho một registry của nhà sản xuất (một đơn giản List<IShapeMaker>) đi qua các nhà sản xuất truyền cho họ những điểm, và dừng khi bạn lấy lại hình dạng không rỗng.

Hệ thống này còn có thể mở rộng, bởi vì bạn có thể thêm một cặp NewShapeNewShapeMaker, và "cắm chúng trong" vào khuôn khổ hiện có: một lần NewShapeMaker được trong registry, phần còn lại của hệ thống ngay lập tức trở nên sẵn sàng để nhận biết và sử dụng NewShape của bạn.

+2

Có một nhược điểm của việc thực hiện này - vì cùng một tập hợp các điểm có thể được chấp nhận bởi nhiều nhà sản xuất, đột nhiên thứ tự của các nhà sản xuất quan trọng - thay đổi thứ tự sẽ làm cho nhà máy trở lại một hình dạng khác. Việc thực hiện ban đầu là miễn phí của vấn đề này, như các khách hàng nhà máy làm cho nó rõ ràng mà hình dạng nó muốn. Điều này sẽ dễ dàng được cố định bằng cách chuyển tham số bổ sung này cho phương thức nhà máy (xem câu trả lời của tôi). –

+0

@WiktorZychla Tham số bổ sung cho phép điều khiển rõ ràng cho người gọi, nhưng cũng cần phải sửa đổi người gọi mỗi khi bạn mở rộng hệ thống bằng cách thêm hình dạng mới (tức là người gọi cần "tìm hiểu" để chuyển tên của hình dạng mới mỗi lần một hình dạng mới trở nên có sẵn). Tôi đọc câu hỏi là OP muốn tránh tham số thêm đó. – dasblinkenlight

+0

Anh ấy viết "Tôi biết dữ liệu chuột và hình dạng tôi nên vẽ". Từ đó, tôi sẽ nói người gọi biết hình dạng chính xác. Tôi tin rằng anh ta có một hộp công cụ của tất cả các hình dạng có sẵn hoặc một cái gì đó tương tự. –