2010-04-08 21 views
63

Tôi thực sự bối rối về mẫu khách truy cập và cách sử dụng của nó. Tôi thực sự không thể hình dung được lợi ích của việc sử dụng mẫu này hay mục đích của nó. Nếu ai đó có thể giải thích với các ví dụ nếu có thể điều đó sẽ tuyệt vời.Mục đích của mẫu khách truy cập với các ví dụ

+6

Bạn đã kiểm tra Wikipedia về chủ đề chưa? http://en.wikipedia.org/wiki/Visitor_pattern Những phần nào chính xác không rõ ràng? Hay bạn đang tìm kiếm các ví dụ thế giới thực? – BalusC

Trả lời

51

Ngày xửa ngày xưa ...

class MusicLibrary { 
    private Set<Music> collection ... 
    public Set<Music> getPopMusic() { ... } 
    public Set<Music> getRockMusic() { ... } 
    public Set<Music> getElectronicaMusic() { ... } 
} 

Sau đó, bạn nhận ra rằng bạn muốn để có thể lọc bộ sưu tập của thư viện bằng các thể loại khác. Bạn có thể tiếp tục thêm các phương thức getter mới. Hoặc bạn có thể sử dụng Khách truy cập.

interface Visitor<T> { 
    visit(Set<T> items); 
} 

interface MusicVisitor extends Visitor<Music>; 

class MusicLibrary { 
    private Set<Music> collection ... 
    public void accept(MusicVisitor visitor) { 
     visitor.visit(this.collection); 
    } 
} 

class RockMusicVisitor implements MusicVisitor { 
    private final Set<Music> picks = ... 
    public visit(Set<Music> items) { ... } 
    public Set<Music> getRockMusic() { return this.picks; } 
} 
class AmbientMusicVisitor implements MusicVisitor { 
    private final Set<Music> picks = ... 
    public visit(Set<Music> items) { ... } 
    public Set<Music> getAmbientMusic() { return this.picks; } 
} 

Bạn tách dữ liệu khỏi thuật toán. Bạn tải xuống thuật toán để triển khai khách truy cập. Bạn thêm chức năng bằng cách tạo nhiều hơn khách truy cập, thay vì liên tục sửa đổi (và đầy hơi) lớp học chứa dữ liệu.

+29

Xin lỗi, đây không phải là một ví dụ điển hình cho mẫu Khách truy cập quá đơn giản. Một trong những cơ chế chính của mô hình khách truy cập, lựa chọn chức năng thông qua kiểu (công văn kép) của phần tử truy cập không được hiển thị -1 –

+0

Sau khi tham gia khóa học Trình biên dịch, tôi cũng đã nhận ra ví dụ này vô nghĩa như thế nào. –

+3

@HaraldScheirich Khách truy cập có thể hoặc không thể chọn chức năng theo loại.Tôi đã tìm thấy khách truy cập vô cùng hữu ích ngay cả khi không có điều đó. – DJClayworth

6

Nó cung cấp một lớp trừu tượng khác. Giảm độ phức tạp của một đối tượng và làm cho nó trở nên mô đun hơn. Sorta như sử dụng một giao diện (thực hiện hoàn toàn độc lập và không ai quan tâm nó được thực hiện như thế nào mà nó được thực hiện.)

Bây giờ tôi chưa bao giờ sử dụng nó, nhưng nó sẽ hữu ích cho việc: Thực hiện một chức năng cụ thể cần phải được thực hiện trong các lớp con khác nhau, vì mỗi lớp con cần thực hiện nó theo các cách khác nhau mà một lớp khác sẽ thực hiện tất cả các hàm. Kinda giống như một mô-đun nhưng chỉ cho một bộ sưu tập các lớp học. Wikipedia có một lời giải thích khá tốt: http://en.wikipedia.org/wiki/Visitor_pattern Và ví dụ của họ giúp giải thích những gì tôi đang cố gắng nói.

Hy vọng rằng sẽ giúp xóa nó một chút.

EDIT ** Xin lỗi tôi đã liên kết với wikipedia cho câu trả lời của bạn nhưng họ thực sự có một ví dụ tốt :) Không cố gắng để được rằng anh chàng mà nói đi tìm thấy nó cho mình.

+0

Loại giải thích này về âm thanh như mẫu Chiến lược – Waclock

1

Để tách thao tác dữ liệu khỏi dữ liệu thực tế. Là một phần thưởng, bạn có thể tái sử dụng cùng một lớp khách truy cập cho toàn bộ hệ thống phân cấp của các lớp của bạn, điều này sẽ giúp bạn tránh khỏi việc thực hiện các thuật toán thao tác dữ liệu không liên quan đến các đối tượng thực tế của bạn.

158

Vì vậy, bạn có thể đã đọc một loạt các giải thích khác nhau về mẫu khách truy cập, và bạn có thể vẫn đang nói "nhưng khi nào bạn sẽ sử dụng nó!"

Theo truyền thống, khách truy cập được sử dụng để triển khai thử nghiệm loại mà không phải hy sinh loại an toàn, miễn là loại của bạn được xác định rõ trước và được biết trước. Hãy nói rằng chúng tôi có một vài lớp học như sau:

abstract class Fruit { } 
class Orange : Fruit { } 
class Apple : Fruit { } 
class Banana : Fruit { } 

Và giả sử chúng ta tạo ra một Fruit[]:

var fruits = new Fruit[] 
    { new Orange(), new Apple(), new Banana(), 
     new Banana(), new Banana(), new Orange() }; 

Tôi muốn phân vùng danh sách trong ba danh sách, mỗi chứa cam, táo, hoặc chuối. Bạn sẽ làm điều này như thế nào? Vâng, giải pháp dễ dàng sẽ là một loại kiểm tra:

List<Orange> oranges = new List<Orange>(); 
List<Apple> apples = new List<Apple>(); 
List<Banana> bananas = new List<Banana>(); 
foreach (Fruit fruit in fruits) 
{ 
    if (fruit is Orange) 
     oranges.Add((Orange)fruit); 
    else if (fruit is Apple) 
     apples.Add((Apple)fruit); 
    else if (fruit is Banana) 
     bananas.Add((Banana)fruit); 
} 

Nó hoạt động, nhưng có rất nhiều vấn đề với mã này:

  • Đối với một sự khởi đầu, xấu xí của nó.
  • Không an toàn kiểu của chúng, chúng tôi sẽ không phát hiện lỗi loại cho đến khi chạy.
  • Không thể bảo trì. Nếu chúng ta thêm một thể hiện có nguồn gốc mới của Fruit, chúng ta cần phải thực hiện tìm kiếm toàn cầu cho mọi nơi thực hiện kiểm tra kiểu trái cây, nếu không chúng ta có thể bỏ lỡ các kiểu.

Mẫu khách truy cập giải quyết vấn đề một cách thanh lịch. Bắt đầu bằng cách thay đổi lớp Fruit cơ sở của chúng tôi:

interface IFruitVisitor 
{ 
    void Visit(Orange fruit); 
    void Visit(Apple fruit); 
    void Visit(Banana fruit); 
} 

abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); } 
class Orange : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 
class Apple : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 
class Banana : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 

Dường như chúng tôi sao chép dán mã, nhưng lưu ý các lớp học có nguồn gốc đều quá tải gọi khác nhau (Apple cuộc gọi Visit(Apple), các cuộc gọi BananaVisit(Banana), và vân vân) .

Thực hiện các lượt truy cập:

class FruitPartitioner : IFruitVisitor 
{ 
    public List<Orange> Oranges { get; private set; } 
    public List<Apple> Apples { get; private set; } 
    public List<Banana> Bananas { get; private set; } 

    public FruitPartitioner() 
    { 
     Oranges = new List<Orange>(); 
     Apples = new List<Apple>(); 
     Bananas = new List<Banana>(); 
    } 

    public void Visit(Orange fruit) { Oranges.Add(fruit); } 
    public void Visit(Apple fruit) { Apples.Add(fruit); } 
    public void Visit(Banana fruit) { Bananas.Add(fruit); } 
} 

Bây giờ bạn có thể phân vùng các loại trái cây của bạn mà không một loại kiểm tra:

FruitPartitioner partitioner = new FruitPartitioner(); 
foreach (Fruit fruit in fruits) 
{ 
    fruit.Accept(partitioner); 
} 
Console.WriteLine("Oranges.Count: {0}", partitioner.Oranges.Count); 
Console.WriteLine("Apples.Count: {0}", partitioner.Apples.Count); 
Console.WriteLine("Bananas.Count: {0}", partitioner.Bananas.Count); 

này có những ưu điểm của:

  • Là tương đối sạch sẽ, dễ đọc mã.
  • Loại an toàn, lỗi loại được phát hiện tại thời gian biên dịch.
  • Tính bảo trì. Nếu tôi thêm một lớp trái cây cụ thể, tôi có thể sửa đổi giao diện IFruitVisitor của mình để xử lý kiểu phù hợp, và trình biên dịch sẽ ngay lập tức tìm thấy tất cả những nơi chúng ta thực hiện giao diện để chúng ta có thể thực hiện các sửa đổi thích hợp.

Với những gì đã nói, du khách thường quá mức cần thiết, và họ có xu hướng hiển nhiên phức tạp API, và nó có thể rất cồng kềnh để xác định một người truy cập mới cho mỗi loại mới của hành vi.

Thông thường, các mẫu đơn giản như kế thừa phải được sử dụng thay cho khách truy cập. Ví dụ, về nguyên tắc tôi có thể viết một lớp học như:

class FruitPricer : IFruitVisitor 
{ 
    public double Price { get; private set; } 
    public void Visit(Orange fruit) { Price = 0.69; } 
    public void Visit(Apple fruit) { Price = 0.89; } 
    public void Visit(Banana fruit) { Price = 1.11; } 
} 

Nó hoạt động, nhưng lợi thế hơn thay đổi tầm thường này là những gì:

abstract class Fruit 
{ 
    public abstract void Accept(IFruitVisitor visitor); 
    public abstract double Price { get; } 
} 

Vì vậy, bạn nên sử dụng đội khách khi các điều kiện sau giữ:

  • Bạn có một bộ lớp học được xác định rõ ràng sẽ được truy cập.

  • Hoạt động trên các lớp đã nói không được xác định rõ hoặc đã biết trước. Ví dụ: nếu ai đó đang sử dụng API của bạn và bạn muốn cung cấp cho người tiêu dùng cách thêm chức năng quảng cáo mới vào đối tượng. Chúng cũng là một cách thuận tiện để mở rộng các lớp được niêm phong bằng tính năng đặc biệt.

  • Bạn thực hiện các hoạt động của một lớp đối tượng và muốn tránh kiểm tra loại thời gian chạy. Đây thường là trường hợp khi bạn đi qua một hệ thống phân cấp các đối tượng khác nhau có các thuộc tính khác nhau.

Không sử dụng khi khách:

  • Bạn hỗ trợ hoạt động trên một lớp học của các đối tượng mà loại có nguồn gốc không được biết trước.

  • Thao tác trên các đối tượng được xác định trước, đặc biệt nếu chúng có thể được kế thừa từ một lớp cơ sở hoặc được xác định trong giao diện.

  • Khách hàng của mình dễ dàng thêm chức năng mới vào các lớp bằng cách sử dụng thừa kế.

  • Bạn đang đi qua một hệ thống phân cấp các đối tượng có cùng thuộc tính hoặc giao diện.

  • Bạn muốn có một API tương đối đơn giản.

+6

Ngoại trừ khách truy cập không đóng cửa để sửa đổi (Nguyên tắc Mở/Đóng). Bất kỳ loại trái cây mới nào cũng phải có một phương pháp mới. Tốt hơn là khách truy cập chỉ có phương thức 'Truy cập (trái cây)' và thực hiện cụ thể để ánh xạ từng loại trái cây đến một phương pháp cụ thể. (Vì vậy, các lớp khách truy cập khác có thể mở rộng cơ sở cụ thể) – jgauffin

+4

@jgauffin một trong những hậu quả chính thức của mẫu Khách truy cập là bất cứ khi nào bạn thêm một đối tượng mới có thể truy cập được, phương pháp mới được tạo ra, do đó vi phạm Mở/Đóng được ngụ ý trong các hậu quả của mô hình này. Có một phương thức Visit() làm độ phân giải kiểu cung cấp rất nhiều nhược điểm, bao gồm không cho phép các IDE, các trình biên dịch, RTE, vv để xác thực các triển khai thực hiện. – Fleep

2

Ví dụ về mẫu khách truy cập. Sách, trái cây & rau là những yếu tố cơ bản của loại "có thể đến thăm" và có hai "Khách thăm", BillingVisitor & OfferVisitor mỗi người truy cập có mục đích riêng của mình .Algo để tính toán hóa đơn và algo để tính toán phiếu mua hàng trên các yếu tố này được đóng gói trong khách truy cập tương ứng và Số lượt truy cập (Yếu tố) vẫn giữ nguyên.

import java.util.ArrayList; 
import java.util.List; 


public class VisitorPattern { 

    public static void main(String[] args) { 
     List<Visitable> visitableElements = new ArrayList<Visitable>(); 
     visitableElements.add(new Book("I123",10,2.0)); 
     visitableElements.add(new Fruit(5,7.0)); 
     visitableElements.add(new Vegetable(25,8.0)); 
     BillingVisitor billingVisitor = new BillingVisitor(); 
     for(Visitable visitableElement : visitableElements){ 
      visitableElement.accept(billingVisitor); 
     } 

     OfferVisitor offerVisitor = new OfferVisitor(); 
     for(Visitable visitableElement : visitableElements){ 
      visitableElement.accept(offerVisitor); 
     } 
     System.out.println("Total bill " + billingVisitor.totalPrice); 
     System.out.println("Offer " + offerVisitor.offer); 

    } 

    interface Visitor { 
     void visit(Book book); 
     void visit(Vegetable vegetable); 
     void visit(Fruit fruit); 
    } 

    //Element 
    interface Visitable{ 
     public void accept(Visitor visitor); 
    } 


    static class OfferVisitor implements Visitor{ 
     StringBuilder offer = new StringBuilder(); 

     @Override 
     public void visit(Book book) { 
      offer.append("Book " + book.isbn + " discount 10 %" + " \n"); 
     } 

     @Override 
     public void visit(Vegetable vegetable) { 
      offer.append("Vegetable No discount \n"); 
     } 

     @Override 
     public void visit(Fruit fruit) { 
      offer.append("Fruits No discount \n"); 
     } 

    } 

    static class BillingVisitor implements Visitor{ 
     double totalPrice = 0.0; 

     @Override 
     public void visit(Book book) { 
      totalPrice += (book.quantity * book.price); 
     } 

     @Override 
     public void visit(Vegetable vegetable) { 
      totalPrice += (vegetable.weight * vegetable.price); 
     } 

     @Override 
     public void visit(Fruit fruit) { 
      totalPrice += (fruit.quantity * fruit.price); 
     } 

    } 

    static class Book implements Visitable{ 
     private String isbn; 
     private double quantity; 
     private double price; 

     public Book(String isbn, double quantity, double price) { 
      this.isbn = isbn; 
      this.quantity = quantity; 
      this.price = price; 
     } 

     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this); 
     } 
    } 

    static class Fruit implements Visitable{ 
     private double quantity; 
     private double price; 

     public Fruit(double quantity, double price) { 
      this.quantity = quantity; 
      this.price = price; 
     } 

     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this); 
     } 
    } 

    static class Vegetable implements Visitable{ 
     private double weight; 
     private double price; 

     public Vegetable(double weight, double price) { 
      this.weight = weight; 
      this.price = price; 
     } 


     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this);    
     } 
    } 


} 
1

Tôi nghĩ rằng mục đích chính của mô hình khách truy cập là nó có khả năng mở rộng cao. Trực giác là bạn đã mua một robot. Robot đã thực hiện đầy đủ các chức năng cơ bản như đi trước, rẽ trái, rẽ phải, quay lại, chọn thứ gì đó, nói một pha,…

Một ngày, bạn muốn rô bốt có thể đến bưu điện cho bạn. Với tất cả các chức năng cơ bản này, nó có thể làm được, nhưng bạn cần phải mang robot đến cửa hàng và "cập nhật" robot của bạn. Người bán hàng không cần phải sửa đổi robot, nhưng chỉ cần đặt một chip cập nhật mới cho rô bốt của bạn và nó có thể làm những gì bạn muốn.

Một ngày khác, bạn muốn rô bốt của mình đi đến siêu thị. Cùng một quá trình, bạn phải mang robot đến cửa hàng và cập nhật chức năng "nâng cao" này. Không cần phải sửa đổi bản thân robot.

và vân vân ...

Vì vậy, ý tưởng về mô hình của khách là, cung cấp tất cả các chức năng cơ bản thực hiện, bạn có thể sử dụng mẫu người truy cập để thêm vô số các chức năng phức tạp. Trong ví dụ này, rô bốt là các lớp nhân viên của bạn và "chip cập nhật" là khách truy cập. Mỗi lần cần một "cập nhật" mới của chức năng, bạn không sửa đổi lớp nhân viên của bạn, nhưng bạn thêm một khách truy cập.

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