2012-01-10 15 views
6

Trong Wikipedia sample và trong sách GoF, việc sử dụng mẫu Khách truy cập được bắt đầu bằng cách gọi phương thức accept trên một số người chấp nhận. Nhưng tại sao nó theo cách này? Tại sao chúng ta không thể bắt đầu gọi phương thức visit với người chấp nhận mong muốn làm đối số? Chúng tôi vẫn có thể làm cho hành vi của khách truy cập phụ thuộc vào 2 loại - khách truy cập và người chấp nhận (công văn kép) - và chúng tôi có thể loại bỏ cuộc gọi dư thừa (giống như tôi).Tại sao chúng ta bắt đầu Khách truy cập bằng cách gọi cho Acceptor.accept() và không phải là Visitor.visit()?

Dưới đây là mẫu mã để minh họa điều này:

public interface Visitor { 
    void visit(AcceptorA acceptor); 
    void visit(AcceptorB acceptor); 
} 

// 
// Visitor which sings 
// 
class SingingVisitor implements Visitor { 
    public void visit(AcceptorA acceptor) { 
     System.out.println("sing A"); 
    } 

    public void visit(AcceptorB acceptor) { 
     System.out.println("sing B"); 
    } 
} 


// 
// Visitor which talks 
// 
class TalkingVisitor implements Visitor { 
    public void visit(AcceptorA acceptor) { 
     System.out.println("talk A"); 
    } 

    public void visit(AcceptorB acceptor) { 
     System.out.println("talk B"); 
    } 
} 

// 
// Acceptor subclasses 
// 
class AcceptorA implements BaseAcceptor { 
} 

class AcceptorB implements BaseAcceptor { 
} 

// 
// Launcher class 
// 
class VisitorMain { 
    public static void main(String[] args) { 
     Visitor v = new TalkingVisitor(); 
     AcceptorA a = new AcceptorA(); 
     AcceptorB b = new AcceptorB(); 

     v.visit(a); 
     v.visit(b); 
     v = new SingingVisitor(); 
     v.visit(a); 
     v.visit(b); 
    } 
} 
+0

Nó chỉ là một ví dụ - không ai nói rằng nó phải được thực hiện theo cách này. –

+3

@DaveNewton Tôi đã hỏi câu hỏi này chính xác để tìm hiểu xem liệu tôi có nhìn thấy một số lợi thế của phương pháp tiếp cận GoF hay chỉ là một quy ước. –

Trả lời

5

xem xét:

class House implements HouseAcceptor { 
    HouseAcceptor kitchen; 
    HouseAcceptor livingRoom; 

    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
     kitchen.accept(visitor); 
     livingRoom.accept(visitor); 
    } 
} 

class Kitchen implements HouseAcceptor { 
    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
    } 
} 

class LivingRoom implements HouseAcceptor { 
    void accept(HouseVisitor visitor) { 
     visitor.visit(this); 
    } 
} 

class SpeakingHouseVisitor implements HouseVisitor { 
    void visit(HouseAcceptor acceptor) { 
     System.out.println("Inside a HouseAcceptor"); 
    } 

    void visit(House acceptor) { 
     System.out.println("Inside a House"); 
    } 

    void visit(Kitchen acceptor) { 
     System.out.println("Inside a Kitchen"); 
    } 

    void visit(LivingRoom acceptor) { 
     System.out.println("Inside a LivingRoom"); 
    } 
} 

... 
HouseAcceptor acceptor = new House(); 
HouseVisitor visitor = new SpeakingHouseVisitor(); 

... 
// Doing it your way 
visitor.visit(acceptor); 
// Output: Inside a HouseAcceptor 

// Doing it the right way 
acceptor.accept(visitor); 
// Output: 
// Inside a House 
// Inside a Kitchen 
// Inside a LivingRoom 

Lưu ý rằng nếu bạn làm điều đó theo cách của bạn, loại thời gian chạy của chấp nhận của bạn sẽ không tạo sự khác biệt: kiểu tĩnh sẽ được sử dụng. Bằng cách thực hiện công văn kép, bạn đảm bảo rằng cả hai loại thời gian chạy đều được sử dụng.

2

Visitor s không có kiến ​​thức về làm thế nào để điều hướng các lĩnh vực nội private của một sáng tác Object.

Nếu bạn gọi Visitor.visit(something) thì nó sẽ phải tìm ra nếu có điều gì đó có trường riêng tư cần chuyển đổi. Để làm điều đó, bạn cần có something để chấp nhận Visitor của mình. Khi bạn quyết định rằng điều hướng phải nằm trong các đối tượng được truy cập (và không phải là Visitor), thì bạn nhận ra rằng bạn cần một cuộc gọi quay lại Visitor để cho biết phần tử tiếp theo trong đường dẫn điều hướng là gì. Thông thường đó là phương pháp accept(...); tuy nhiên, nếu bạn cố gắng để làm cho accept(...) chỉ là trình bao bọc để bắt đầu điều hướng (bằng cách ủy nhiệm tham số), thì bạn cần một bộ phương pháp thứ hai để báo cho Visitor bạn đang nhập X ngay bây giờ, bạn nhập Y ngay bây giờ.

Bằng cách sử dụng phương pháp GOF, người ta có thể phân loại an toàn một mục được truy cập sửa đổi đường dẫn truy cập để bao gồm hoặc bỏ qua các trường bổ sung. Điều này sẽ không ảnh hưởng đến Visitor hiện tại bởi vì giao diện của chúng sẽ không thay đổi. Người ta cũng sẽ không cần biên dịch lại các lớp con của Visitor.

Bằng cách sử dụng cách tiếp cận được đề xuất, khi thêm một loại mới vào phân cấp các mục cần truy cập, một người sẽ cần phải biên dịch lại tất cả khách truy cập, ngay cả khách truy cập không quan tâm đến loại mới.

Một thỏa hiệp tốt sẽ là:

public interface Visitable { 
    public void accept(Visitor v); 
} 

được tất cả các bạn "dữ liệu hệ thống phân cấp" thực hiện có thể đến thăm, và khách của bạn có một "phương pháp thuận tiện" như vậy

public abstract class Visitor { 

    public void initiate(Visitable v) { 
    v.accept(this); 
    } 

    public abstract void accept(...); 
    public abstract void accept(...); 
    public abstract void accept(...); 

} 

Nhưng đó là tùy thuộc vào bạn nếu có một giao diện thích hợp hơn với một lớp cơ sở như vậy. Với tôi, tôi thích giao diện lỏng lẻo hơn, nhưng ý kiến ​​khác nhau.

+0

-1 Bạn đang mô tả combo khách truy cập/Composite/Iterator. Mẫu khách truy cập cũng hữu ích hơn thế. Và thỏa hiệp của bạn trông giống như một công văn gấp ba lần đối với tôi Khách truy cập-> Có thể truy cập-> Khách truy cập. – greyfairer

+0

@greyfairer, tôi không đề xuất ba công văn. Tôi chỉ ra rằng các Visitable phải gọi khách truy cập nhiều lần, và nếu bạn có ý định đưa "bắt đầu" trong khách truy cập, nó sẽ trở thành một chức năng thuận tiện được gọi là một lần. Tôi không thích giải pháp, nhưng tôi sẽ minh họa nó để hy vọng chỉ ra các chi phí mã mà không thêm lợi ích. Ít nhất cuộc gọi Visitor-> Visitable chỉ xảy ra một lần (nhưng thực sự, tại sao lại lãng phí khung xếp chồng bổ sung?) –

+0

Mẫu khách truy cập cũng hữu ích nếu bạn không có tổng hợp có thể truy cập. Trong trường hợp này, Visitable sẽ chỉ gọi cho khách truy cập một lần, nhưng nó sẽ gọi phương thức nạp chồng đúng. – greyfairer

5

Sử dụng phiên bản của bạn, sau đây sẽ không biên dịch:

List<BaseAcceptor> list = ... 
for(BaseAcceptor ba: list) 
    vi.visit(ba) 

Trình biên dịch java không thể xác định (tĩnh) những gì ba sẽ, vì vậy nó không thể quyết định tại thời gian biên dịch mà thăm phương pháp để gọi. Bạn cần viết phương thức bổ sung:

public void visit(BaseAcceptor ba){ 
    if(ba instanceof AcceptorA) 
    visit((AcceptorA)ba); 
    else if(ba instanceof AcceptorB) 
    visit((AcceptorB)ba); 
} 

Điều này không cần thiết khi sử dụng mẫu khách truy cập.

+0

Nếu tôi có phương thức "stub" 'visit (BaseAcceptor)' trong giao diện 'Visitor', mã đầu tiên * sẽ * biên dịch. Nhưng nó vẫn không cho tôi hành vi mong muốn, đúng vậy. –

0

bạn không có công văn kép nào. chấp nhận thường lấy một người truy cập trừu tượng làm đối số.

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