2009-06-12 33 views
44

Tôi đang tìm kiếm một giải pháp thay thế cho mẫu khách truy cập. Hãy để tôi chỉ tập trung vào một vài khía cạnh thích hợp của mô hình, trong khi bỏ qua các chi tiết không quan trọng. Tôi sẽ sử dụng một ví dụ Shape (xin lỗi!):Thay thế cho mẫu khách truy cập?

  1. Bạn có một hệ thống phân cấp các đối tượng thực hiện các giao diện IShape
  2. Bạn có một số hoạt động toàn cầu đó sẽ được thực hiện trên tất cả các đối tượng trong hệ thống phân cấp , ví dụ Vẽ, WriteToXml vv ...
  3. Thật hấp dẫn để đi sâu vào và thêm phương thức Draw() và WriteToXml() vào giao diện IShape. Điều này không nhất thiết phải là một điều tốt - bất cứ khi nào bạn muốn thêm một hoạt động mới sẽ được thực hiện trên tất cả các hình dạng, mỗi lớp có nguồn gốc từ IShape phải được thay đổi
  4. Thực hiện một khách truy cập cho mỗi hoạt động tức là khách truy cập Draw hoặc khách truy cập WirteToXml đóng gói tất cả mã cho hoạt động đó trong một lớp. Thêm một hoạt động mới sau đó là vấn đề tạo lớp khách truy cập mới thực hiện thao tác trên tất cả các loại IShape
  5. Khi bạn cần thêm lớp mới có nguồn gốc từ IShape, về cơ bản bạn có cùng một vấn đề giống như bạn đã làm trong 3 - tất cả các lớp khách truy cập phải được thay đổi để thêm một phương thức để xử lý loại có nguồn gốc IShape

Hầu hết các địa điểm mà bạn đọc về trạng thái mẫu khách truy cập mà điểm 5 là tiêu chí chính cho mô hình hoạt động và tôi hoàn toàn đồng ý. Nếu số lượng các lớp có nguồn gốc từ IShape được cố định, thì đây có thể là một cách tiếp cận khá thanh lịch.

Vì vậy, vấn đề là khi một lớp mới có nguồn gốc từ IShape được thêm vào - mỗi lần triển khai khách truy cập cần phải thêm một phương thức mới để xử lý lớp đó. Đây là, tốt nhất, khó chịu và, lúc tồi tệ nhất, không thể và cho thấy rằng mô hình này không thực sự được thiết kế để đối phó với những thay đổi như vậy.

Vì vậy, câu hỏi đặt ra là có ai đi qua các phương pháp thay thế để xử lý tình huống này không?

+0

Chỉ cần một lưu ý sang một bên, vì không chắc bạn có thể thay đổi ngôn ngữ của mình: Có các ngôn ngữ hỗ trợ trực tiếp cho nhiều hàm chung của công văn. – Svante

+6

Câu hỏi hay. Tôi chỉ muốn cung cấp một điểm đối kháng. Đôi khi vấn đề của bạn với (5) có thể là một điều tốt. Tôi sử dụng mẫu khách truy cập khi tôi có một số chức năng phải được cập nhật khi loại phụ IShape mới được xác định. Tôi có một giao diện IShapeVisitor xác định những phương thức nào là cần thiết. Miễn là giao diện đó được cập nhật với loại phụ mới, mã của tôi không xây dựng cho đến khi chức năng quan trọng được cập nhật. Đối với một số trường hợp, điều này có thể rất hữu ích. – oillio

+1

Tôi đồng ý với @oillio, nhưng sau đó bạn cũng có thể thực thi nó như một phương pháp trừu tượng trên IShape. Mẫu khách truy cập mua bạn bằng ngôn ngữ OO thuần túy là địa phương của hàm (so với địa phương của lớp học) và do đó có sự phân tách các mối quan tâm.Trong bất kỳ trường hợp nào, việc sử dụng mẫu khách truy cập phải rõ ràng phá vỡ thời gian biên dịch khi bạn muốn buộc thêm các loại mới để được xem xét cẩn thận! –

Trả lời

13

Bạn có thể muốn xem qua số Strategy pattern. Điều này vẫn cung cấp cho bạn một sự phân tách các mối quan tâm trong khi vẫn có thể thêm chức năng mới mà không phải thay đổi từng lớp trong hệ thống phân cấp của bạn.

class AbstractShape 
{ 
    IXmlWriter _xmlWriter = null; 
    IShapeDrawer _shapeDrawer = null; 

    public AbstractShape(IXmlWriter xmlWriter, 
       IShapeDrawer drawer) 
    { 
     _xmlWriter = xmlWriter; 
     _shapeDrawer = drawer; 
    } 

    //... 
    public void WriteToXml(IStream stream) 
    { 
     _xmlWriter.Write(this, stream); 

    } 

    public void Draw() 
    { 
     _drawer.Draw(this); 
    } 

    // any operation could easily be injected and executed 
    // on this object at run-time 
    public void Execute(IGeneralStrategy generalOperation) 
    { 
     generalOperation.Execute(this); 
    } 
} 

Thông tin thêm là trong cuộc thảo luận này có liên quan:

Should an object write itself out to a file, or should another object act on it to perform I/O?

+0

Tôi đã đánh dấu câu trả lời này là câu trả lời cho câu hỏi của mình khi tôi nghĩ điều này hoặc một số biến thể nhỏ trên đó, có thể phù hợp với những gì tôi muốn làm. Đối với bất kỳ ai quan tâm, tôi đã thêm "câu trả lời" mô tả một số suy nghĩ của tôi về vấn đề – Steg

+0

ok - thay đổi ý kiến ​​của tôi về câu trả lời - Tôi sẽ cố gắng chia nhỏ nó thành một bình luận (sau) – Steg

+2

Tôi nghĩ có là một xung đột cơ bản ở đây - nếu bạn có nhiều thứ và một loạt các hành động có thể được thực hiện trên những thứ này thì thêm một điều mới có nghĩa là bạn phải xác định tác dụng của mọi hành động trên đó và ngược lại - không có lối thoát điều này. Khách truy cập cung cấp một cách rất đơn giản, thanh lịch để thêm hành động mới với chi phí làm cho việc thêm nội dung mới trở nên khó khăn. Nếu hạn chế này phải được nới lỏng, bạn phải trả tiền. Tôi đã hy vọng có thể có giải pháp có sự sang trọng và đơn giản của khách truy cập, nhưng như tôi nghi ngờ, tôi không nghĩ rằng một tồn tại ... tiếp theo ... – Steg

13

Có "Pattern khách Với Default", trong đó bạn làm mẫu người truy cập như bình thường nhưng sau đó xác định một lớp trừu tượng triển khai lớp IShapeVisitor của bạn bằng cách ủy quyền mọi thứ cho một phương thức trừu tượng với chữ ký visitDefault(IShape).

Sau đó, khi bạn xác định khách truy cập, hãy mở rộng lớp trừu tượng này thay vì triển khai trực tiếp giao diện. Bạn có thể ghi đè lên các phương thức visit * mà bạn biết vào thời điểm đó và cung cấp cho một mặc định hợp lý. Tuy nhiên, nếu thực sự không có cách nào để tìm ra hành vi mặc định hợp lý trước thời hạn, bạn chỉ nên thực hiện giao diện trực tiếp.

Khi bạn thêm một lớp con mới IShape, thì bạn sửa lớp trừu tượng để ủy quyền cho phương thức visitDefault và mọi khách truy cập đã chỉ định hành vi mặc định sẽ nhận hành vi đó cho IShape mới.

Một biến thể về điều này nếu các lớp IShape của bạn tự nhiên rơi vào một hệ thống phân cấp là làm cho lớp trừu tượng ủy nhiệm thông qua một số phương pháp khác nhau; ví dụ, một DefaultAnimalVisitor có thể làm:

public abstract class DefaultAnimalVisitor implements IAnimalVisitor { 
    // The concrete animal classes we have so far: Lion, Tiger, Bear, Snake 
    public void visitLion(Lion l) { visitFeline(l); } 
    public void visitTiger(Tiger t) { visitFeline(t); } 
    public void visitBear(Bear b) { visitMammal(b); } 
    public void visitSnake(Snake s) { visitDefault(s); } 

    // Up the class hierarchy 
    public void visitFeline(Feline f) { visitMammal(f); } 
    public void visitMammal(Mammal m) { visitDefault(m); } 

    public abstract void visitDefault(Animal a); 
} 

này cho phép bạn xác định khách mà xác định hành vi của họ ở bất cứ mức độ đặc hiệu mà bạn muốn.

Thật không may, không có cách nào để tránh làm điều gì đó để chỉ định cách khách truy cập sẽ hành xử với một lớp mới - bạn có thể thiết lập mặc định trước hoặc bạn không thể. (Xem thêm bảng điều khiển thứ hai của this cartoon)

6

Tôi duy trì phần mềm CAD/CAM cho máy cắt kim loại. Vì vậy, tôi có một số kinh nghiệm với các vấn đề này.

Khi lần đầu tiên chúng tôi chuyển đổi phần mềm của mình (lần đầu tiên được phát hành vào năm 1985!) Cho một đối tượng được thiết kế theo định dạng, tôi đã làm những gì bạn không thích. Các đối tượng và giao diện có Draw, WriteToFile, vv Khám phá và đọc về các mẫu thiết kế ở giữa quá trình chuyển đổi đã giúp rất nhiều nhưng vẫn còn rất nhiều mùi mã xấu.

Cuối cùng tôi nhận ra rằng không có loại hoạt động nào trong số này thực sự là mối quan tâm của đối tượng. Nhưng thay vì các hệ thống phụ khác nhau cần thiết để thực hiện các hoạt động khác nhau. Tôi xử lý điều này bằng cách sử dụng những gì bây giờ được gọi là một đối tượng lệnh Passive View, và được xác định rõ Giao diện giữa các lớp phần mềm.

phần mềm của chúng tôi được cấu trúc cơ bản như thế này

  • Các hình thức thực hiện khác nhau Mẫu Interface. Các biểu mẫu này là một sự kiện truyền qua các lớp sự kiện đến Tầng giao diện người dùng.
  • Lớp giao diện người dùng nhận Sự kiện và thao tác biểu mẫu thông qua giao diện Biểu mẫu.
  • Lớp giao diện người dùng sẽ thực thi các lệnh mà tất cả triển khai giao diện Lệnh
  • Đối tượng giao diện người dùng có giao diện riêng mà lệnh có thể tương tác.
  • Lệnh nhận thông tin mà họ cần, xử lý nó, thao tác mô hình và sau đó báo cáo lại cho các đối tượng giao diện người dùng mà sau đó thực hiện mọi thứ cần thiết với biểu mẫu.
  • Cuối cùng, các mô hình có chứa các đối tượng khác nhau của hệ thống của chúng tôi. Giống như các chương trình hình dạng, đường cắt, bàn cắt và tờ kim loại.

Bản vẽ được xử lý trong Lớp giao diện người dùng. Chúng tôi có phần mềm khác nhau cho các máy khác nhau. Vì vậy, trong khi tất cả các phần mềm của chúng tôi chia sẻ cùng một mô hình và sử dụng lại nhiều lệnh giống nhau. Họ xử lý những thứ như vẽ rất khác nhau. Ví dụ một bàn cắt được vẽ khác nhau cho một máy router so với một máy bằng cách sử dụng một ngọn đuốc plasma mặc dù cả hai đều là esstentially một bàn phẳng X-Y khổng lồ. Điều này bởi vì giống như xe hơi, hai máy này được chế tạo đủ khác nhau để có sự khác biệt thị giác cho khách hàng.

Đối với hình dạng, chúng tôi thực hiện như sau

Chúng tôi có các chương trình hình dạng tạo đường cắt thông qua các thông số đã nhập. Con đường cắt biết được chương trình hình dạng nào được tạo ra. Tuy nhiên một con đường cắt không phải là một hình dạng. Nó chỉ là thông tin cần thiết để vẽ trên màn hình và cắt hình. Một lý do cho thiết kế này là các đường cắt có thể được tạo mà không có chương trình hình dạng khi chúng được nhập từ một ứng dụng bên ngoài.

Thiết kế này cho phép chúng tôi tách thiết kế đường cắt khỏi thiết kế hình dạng không phải lúc nào cũng giống nhau. Trong trường hợp của bạn, tất cả những gì bạn cần để đóng gói là thông tin cần thiết để vẽ hình dạng.

Mỗi chương trình hình dạng có một số chế độ xem triển khai Giao diện IShapeView. Thông qua giao diện IShapeView, chương trình hình dạng có thể cho biết dạng hình dạng chung chúng ta có cách thiết lập chính nó để hiển thị các tham số của hình dạng đó. Dạng hình dạng chung thực hiện một giao diện IShapeForm và đăng ký chính nó với đối tượng ShapeScreen. Đối tượng ShapeScreen đăng ký chính nó với đối tượng ứng dụng của chúng ta. Các khung nhìn hình dạng sử dụng bất kỳ khung hình nào tự đăng ký với ứng dụng.

Lý do cho nhiều lượt xem mà chúng tôi có khách hàng muốn nhập hình dạng theo nhiều cách khác nhau. Cơ sở khách hàng của chúng tôi được phân chia bằng một nửa giữa những người muốn nhập thông số hình dạng trong biểu mẫu bảng và những người muốn nhập bằng biểu diễn đồ họa của hình dạng trước mặt họ. Chúng tôi cũng cần phải truy cập vào các thông số vào các thời điểm thông qua một hộp thoại tối thiểu thay vì màn hình nhập hình dạng đầy đủ của chúng tôi. Do đó nhiều lượt xem.

Lệnh điều khiển hình dạng rơi vào một trong hai loại hình ảnh. Hoặc là họ thao tác đường cắt hoặc họ thao tác các thông số hình dạng. Để thao tác các thông số hình dạng chung, chúng tôi hoặc ném chúng trở lại màn hình nhập hình hoặc hiển thị hộp thoại tối thiểu. Tính toán lại hình dạng và hiển thị nó trong cùng một vị trí.

Đối với đường cắt, chúng tôi gộp từng hoạt động vào một đối tượng lệnh riêng biệt. Ví dụ: chúng tôi có các đối tượng lệnh

ResizePath RotatePath MovePath SplitPath v.v.

Khi chúng ta cần thêm chức năng mới, chúng tôi thêm đối tượng lệnh khác, tìm một nút menu, bàn phím ngắn hoặc nút thanh công cụ trong màn hình giao diện người dùng phù hợp và thiết lập đối tượng UI để thực thi lệnh đó.

Ví dụ

CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath 

hoặc

CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath 

Trong cả hai trường hợp đối tượng MirrorPath lệnh đang được liên kết với một yếu tố giao diện người dùng mong muốn. Trong phương thức thực hiện của MirrorPath là tất cả mã cần thiết để nhân bản đường dẫn trong một trục cụ thể. Có khả năng lệnh sẽ có hộp thoại riêng của nó hoặc sử dụng một trong các thành phần giao diện người dùng để yêu cầu người dùng có trục phản chiếu. Không ai trong số này đang tạo khách truy cập hoặc thêm phương thức vào đường dẫn.

Bạn sẽ thấy rằng rất nhiều thứ có thể được xử lý thông qua các hành động đóng gói thành các lệnh. Tuy nhiên, tôi cảnh cáo đó không phải là một tình huống màu đen hoặc trắng. Bạn sẽ vẫn thấy rằng những thứ nhất định hoạt động tốt hơn như các phương thức trên đối tượng gốc. Trong kinh nghiệm có thể tôi thấy rằng có lẽ 80% những gì tôi sử dụng để làm trong các phương pháp đã có thể được chuyển vào lệnh. 20% cuối cùng chỉ đơn giản là làm việc tốt hơn trên đối tượng.

Hiện tại một số có thể không thích điều này vì dường như vi phạm đóng gói. Từ việc duy trì phần mềm của chúng tôi như một hệ thống hướng đối tượng trong thập kỷ qua, tôi phải nói rằng điều quan trọng nhất trong dài hạn mà bạn có thể làm là ghi rõ sự tương tác giữa các lớp khác nhau của phần mềm và giữa các đối tượng khác nhau.

Hành động đóng gói vào các đối tượng Command giúp với mục tiêu này tốt hơn so với một sự tận tụy sâu sắc đối với các lý tưởng đóng gói.Tất cả mọi thứ cần được thực hiện để Mirror a Path được đóng gói trong Mirror Path Command Object.

+0

Giải pháp có vẻ thú vị, nhưng tôi sẽ đánh giá cao nếu bạn có thể giới thiệu tôi đến mã ví dụ cho giải pháp như vậy để hiểu rõ hơn về khái niệm. –

2

Bất kể bạn đi đường nào, việc triển khai chức năng thay thế hiện được cung cấp bởi mẫu Khách truy cập sẽ phải 'biết' điều gì đó về việc triển khai cụ thể giao diện đang hoạt động. Vì vậy, không có nhận được xung quanh thực tế là bạn sẽ phải viết bổ sung 'khách truy cập' chức năng cho mỗi bổ sung thực hiện. Điều đó nói rằng những gì bạn đang tìm kiếm là một cách tiếp cận linh hoạt và có cấu trúc hơn để tạo ra chức năng này.

Bạn cần tách riêng chức năng khách truy cập khỏi giao diện của hình dạng.

Điều tôi muốn đề xuất là phương pháp sáng tạo thông qua nhà máy trừu tượng để tạo triển khai thay thế cho chức năng của khách truy cập.

public interface IShape { 
    // .. common shape interfaces 
} 

// 
// This is an interface of a factory product that performs 'work' on the shape. 
// 
public interface IShapeWorker { 
    void process(IShape shape); 
} 

// 
// This is the abstract factory that caters for all implementations of 
// shape. 
// 
public interface IShapeWorkerFactory { 
    IShapeWorker build(IShape shape); 
    ... 
} 

// 
// In order to assemble a correct worker we need to create 
// and implementation of the factory that links the Class of 
// shape to an IShapeWorker implementation. 
// To do this we implement an abstract class that implements IShapeWorkerFactory 
// 
public AbsractWorkerFactory implements IShapeWorkerFactory { 

    protected Hashtable map_ = null; 

    protected AbstractWorkerFactory() { 
      map_ = new Hashtable(); 
      CreateWorkerMappings(); 
    } 

    protected void AddMapping(Class c, IShapeWorker worker) { 
      map_.put(c, worker); 
    } 

    // 
    // Implement this method to add IShape implementations to IShapeWorker 
    // implementations. 
    // 
    protected abstract void CreateWorkerMappings(); 

    public IShapeWorker build(IShape shape) { 
     return (IShapeWorker)map_.get(shape.getClass()) 
    } 
} 

// 
// An implementation that draws circles on graphics 
// 
public GraphicsCircleWorker implements IShapeWorker { 

    Graphics graphics_ = null; 

    public GraphicsCircleWorker(Graphics g) { 
     graphics_ = g; 
    } 

    public void process(IShape s) { 
     Circle circle = (Circle)s; 
     if(circle != null) { 
      // do something with it. 
      graphics_.doSomething(); 
     } 
    } 

} 

// 
// To replace the previous graphics visitor you create 
// a GraphicsWorkderFactory that implements AbstractShapeFactory 
// Adding mappings for those implementations of IShape that you are interested in. 
// 
public class GraphicsWorkerFactory implements AbstractShapeFactory { 

    Graphics graphics_ = null; 
    public GraphicsWorkerFactory(Graphics g) { 
     graphics_ = g; 
    } 

    protected void CreateWorkerMappings() { 
     AddMapping(Circle.class, new GraphicCircleWorker(graphics_)); 
    } 
} 


// 
// Now in your code you could do the following. 
// 
IShapeWorkerFactory factory = SelectAppropriateFactory(); 

// 
// for each IShape in the heirarchy 
// 
for(IShape shape : shapeTreeFlattened) { 
    IShapeWorker worker = factory.build(shape); 
    if(worker != null) 
     worker.process(shape); 
} 

Nó vẫn có nghĩa là bạn phải viết triển khai cụ thể để làm việc trên các phiên bản mới của 'hình dạng' nhưng vì nó hoàn toàn được tách ra khỏi giao diện của hình dạng, bạn có thể trang bị thêm giải pháp này mà không vi phạm các giao diện gốc và phần mềm tương tác với nó. Nó hoạt động như một loại giàn giáo xung quanh việc triển khai IShape.

+0

trong AbstractWorkerFactory bạn vẫn phải làm instanceof –

1

Nếu bạn đang sử dụng Java: Có, nó được gọi là instanceof. Mọi người quá sợ hãi để sử dụng nó. So với mẫu khách truy cập, nó thường nhanh hơn, đơn giản hơn và không bị cản trở bởi điểm số 5.

+0

nhanh hơn? Kiểm tra [this] (http://alexshabanov.com/2011/12/03/instanceof-vs-visitor/). – ntohl

+0

@ntohl Trong các thử nghiệm tôi đã thực hiện (trên Java 8, lưu ý rằng thử nghiệm được sử dụng Java 6) instanceof nhanh hơn, vì vậy tôi đoán tốc độ của tốc độ tương đối của hai phải thay đổi dựa trên các chi tiết tinh tế. – Andy

1

Nếu bạn có n IShape hoạt động s và m hoạt động khác nhau đối với mỗi hình dạng, thì bạn yêu cầu n * m chức năng riêng lẻ. Đặt tất cả những thứ này trong cùng một lớp có vẻ như là một ý tưởng khủng khiếp đối với tôi, cho bạn một số loại đối tượng của Thiên Chúa. Vì vậy, chúng nên được nhóm theo IShape, bằng cách đặt các hàm m, một cho mỗi thao tác, trong giao diện IShape hoặc được nhóm theo hoạt động (bằng cách sử dụng mẫu khách truy cập), bằng cách đặt n hàm, một cho mỗi IShape trong mỗi hoạt động/khách truy cập lớp học.

Bạn phải cập nhật nhiều lớp học khi bạn thêm IShape mới hoặc khi bạn thêm một thao tác mới, không có cách nào xung quanh nó.


Nếu bạn đang tìm kiếm cho mỗi hoạt động để thực hiện một chức năng mặc định IShape, sau đó sẽ giải quyết vấn đề của bạn, như trong Daniel Martin câu trả lời: https://stackoverflow.com/a/986034/1969638, mặc dù tôi có lẽ sẽ sử dụng quá tải:

interface IVisitor 
{ 
    void visit(IShape shape); 
    void visit(Rectangle shape); 
    void visit(Circle shape); 
} 

interface IShape 
{ 
    //... 
    void accept(IVisitor visitor); 
} 
3

Mẫu thiết kế của khách truy cập là giải pháp thay thế, không phải là giải pháp cho vấn đề. Câu trả lời ngắn gọn là pattern matching.

Các vấn đề liên quan