2016-08-12 60 views
7

的原因之一考慮Visitor_pattern訪問者模式執行

這種分離的一個實際的結果是要添加到現有的對象結構的新的操作,而無需修改那些結構的能力。

假設您沒有第三方庫的源代碼,並且您已在相關對象上添加了一項操作。

由於您沒有對象,因此無法修改您的元素(第三方類)以添加訪問者。

enter image description here

在這種情況下,雙重分派是不可能的。

那麼哪個選項通常是首選?

Option 1:在第三方類的基礎上再擴展一個繼承層次並實現模式如圖中所示的雙重調度?

對於B類的給定層次結構,其延伸A類,我將添加

ElementA extends A 
ElementB extends B 

現在ConcreteElements衍生自ElementA代替類A.

缺點:類的數量將會增加。

Option 2:使用Visitor類中央助手類,並完成單個分派完成的工作。

缺點:根據UML圖,我們並沒有真正遵循訪客模式。

正確的,如果我錯了。

+2

這是最絕對*不*的理由使用*任何*的模式。並且訪問者模式不被任何UML圖表描述。無論如何,不​​知道你在說什麼語言,甚至不可能討論實現。該語言是否支持泛型? Lambda表達式?它有動態功能嗎?所有這些允許你創建一個不需要雙重調度的訪問者 –

+0

我已經提供了一些更多的上下文和語言(java)的詳細信息 –

+0

@AdityaW什麼是你要實現的用例/用戶故事? –

回答

7

您可以結合使用包裝訪問者來解決您的問題。 使用包裝add a visit方法允許您增加這些對象的可用性。當然,你可以獲得包裝器的全部優點(減少對傳統類的依賴)和缺點(附加對象)。


下面是JAVA一個工作機例如(因爲它是相當嚴格的,本身並不做雙調度,和我很熟悉了吧):

1)你的舊對象

假設你有你的舊對象Legacy1Legacy2不能變化,其中有具體的業務方法:

public final class Legacy1 { 
    public void someBusinessMethod1(){ 
     ... 
    } 
} 

public final class Legacy2 { 
    public void anotherBusinessMethod(){ 
     ... 
    } 
} 

2)準備包裝

你只是包裝他們在一個VisitableWrapper具有visit方法是把你的訪客,如:

public interface VisitableWrapper { 
    public void accept(Visitor visitor); 
} 

隨着以下實現:

public class Legacy1Wrapper { 

    private final Legacy1 legacyObj; 

    public Legacy2Wrapper(Legacy1 original){ 
     this.legacyObj = original; 
    } 

    public void accept(Visitor visitor){ 
     visitor.visit(legacyObj); 
    } 
} 

public class Legacy2Wrapper { 

    private final Legacy2 legacyObj; 

    public Legacy2Wrapper(Legacy2 original){ 
     this.legacyObj = original; 
    } 

    public void accept(Visitor visitor){ 
     visitor.visit(legacyObj); 
    } 
} 

3)遊客,在準備好了!

然後自己遊客 s時,可以設置訪問像這樣的包裝:

public interface Visitor { 
    public void visit(Legacy1 leg); 
    public void visit(Legacy2 leg); 
} 

一個實現像這樣:

public class SomeLegacyVisitor{ 

    public void visit(Legacy1 leg){ 
     System.out.println("This is a Legacy1! let's do something with it!"); 
     leg.someBusinessMethod1(); 
    } 

    public void visit(Legacy2 leg){ 
     System.out.println("Hum, this is a Legacy 2 object. Well, let's do something else."); 
     leg.anotherBusinessMethod(); 
    } 
} 

4)釋放的動力

最後在你的代碼中,這個框架可以像這樣工作:

public class TestClass{ 
    // Start off with some legacy objects 
    Legacy1 leg1 = ... 
    Legacy2 leg2 = ... 

    // Wrap all your legacy objects into a List: 
    List<VisitableWrapper> visitableLegacys = new ArrayList<>(); 
    visitableLegacys.add(new Legacy1Wrapper(legacy1)); 
    visitableLegacys.add(new Legacy2Wrapper(legacy2)); 


    Visitor visitor = new SomeLegacyVisitor(); // Use any of your visitor implementation ! 
    for(VisitableWrapper wrappedLegacy: visitableLegacys){ 
     wrappedLegacy.accept(visitor); 
    } 
} 

預期的輸出:

This is a Legacy1! let's do something with it! 
Hum, this is a Legacy 2 object. Well, let's do something else. 

缺點:

  1. 相當多的樣板。如果使用Java開發,請使用Lombok
  2. 相當多的包裝對象實例。可能會或可能不會成爲你的問題。
  3. 您需要事先知道對象的具體類型。這意味着你知道他們的子類型,他們不是捆綁在列表中。如果是這樣的話,你別無選擇,只能使用反射。
0

應該有可能爲某些層次結構的類添加新功能,而無需更改基類接口。可能的行爲類型應該是不變的,而不同類別的操作應該以不同的方式執行。

訪客模式允許將所有操作集中在一個類中。可能有很多Concrete Element類(從圖中可見),但是對於其中的每個類,將在Concrete Visitor類中實現visit()方法,這將定義他自己的算法。

定義和執行方法的Element類的每個子類:

public interface Visitor { 
    void visit(Element element); 
} 

public class ConcreteVisitor implements Visitor { 
    public void visit(Element element) { 
     // implementation 
    } 
} 

訪問者模式很容易通過落實新的類此接口與他的方法實現擴展新的業務。

下面的結構封裝的Element類:

public lass ObjectStructure { 
    private Element element; 
    // some methods 
} 

ObjectStructure類都可以聚集的Element一個或多個實例。演示文稿Visitor作用:

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

而且在具體執行實體法accept()的:

public class ConcreteElement implements Element { 
    public void accept(Visitor visitor) { 
     visitor.visit(); 
    } 
} 

使用訪問者模式的允許保存從巨大的邏輯功能或複雜的配置Element層次。

希望在定義新的Visitor子類時將功能添加到所有類的層次結構中。但可能會出現問題:visit()應覆蓋每個層次類型。爲了避免這種情況,最好定義AbstractVisitor類,並全部將其全部visit()方法體留空。

結論:使用此模式是良好的類層次結構類型Element保持不變。如果添加新類,它通常會在Visitor類型的類中發生相當大的變化。

+0

您還沒有解決我對源代碼不可用的查詢。我不能在類的層次結構中添加接受和訪問方法。 –

-1

如果您的圖書館沒有accept方法,您需要使用instanceof來完成。 (通常情況下,你會用Java進行兩次單一調度以模擬雙派調度;但這裏我們使用instanceof來模擬雙派調度)。

這裏是例子:

interface Library { 
    public void get1(); 
    public void get2(); 
} 

public class Library1 implements Library { 
    public void get1() { ... } 
    public void get2() { ... } 
} 

public class Library2 implements Library { 
    public void get1() { ... } 
    public void get2() { ... } 
} 

interface Visitor { 
    default void visit(Library1 l1) {} 
    default void visit(Library2 l2) {} 

    default void visit(Library l) { 
     // add here instanceof for double dispatching 
     if (l instanceof Library1) { 
      visit((Library1) l); 
     } 
     else if (l instanceof Library2) { 
      visit((Library2) l); 
     } 
    } 
} 

// add extra print methods to the library 
public class PrinterVisitor implements Visitor { 
    void visit(Library1 l1) { 
     System.out.println("I am library1"); 
    } 
    void visit(Library2 l2) { 
     System.out.println("I am library2"); 
    }   
} 

,現在在任何方法,你可以寫:

Library l = new Library1(); 
PrinterVisitor pv = new PrinterVisitor(); 
pv.visit(l); 

,它會打印到你 「我LIBRARY1」;

+0

您還沒有解決我對源代碼不可用的查詢。我不能在類的層次結構中添加接受和訪問方法 –

0

我的答案是非常相似的邁克爾·馮·Wenckstern的,與我們有一個名爲accept方法(更像是標準模式)的改進,而我們處理未知具體的類 - 有沒有保證,在某些時候我們以前沒有見過的具體實現將不會出現在類路徑中。 我的訪問者還允許返回值。

我還爲visit方法使用了更爲詳細的名稱 - 包括方法名稱中的類型,但這不是必需的,您可以將它們全部稱爲visit

// these classes cannot be modified and do not have source available 

class Legacy { 
} 

class Legacy1 extends Legacy { 
} 
class Legacy2 extends Legacy { 
} 

// this is the implementation of your visitor  

abstract class LegacyVisitor<T> { 
    abstract T visitLegacy1(Legacy1 l); 
    abstract T visitLegacy2(Legacy2 l); 

    T accept(Legacy l) { 
     if (l instanceof Legacy1) { 
      return visitLegacy1((Legacy1)l); 
     } else if (l instanceof Legacy2) { 
      return visitLegacy2((Legacy2)l); 
     } else { 
      throw new RuntimeException("Unknown concrete Legacy subclass:" + l.getClass()); 
     } 
    } 
} 
public class Test { 
    public static void main(String[] args) { 
     String s = new LegacyVisitor<String>() { 

      @Override 
      String visitLegacy1(Legacy1 l) { 
       return "It's a 1"; 
      } 

      @Override 
      String visitLegacy2(Legacy2 l) { 
       return "It's a 2"; 
      } 
     }.accept(new Legacy1()); 

     System.out.println(s); 
    } 
} 
0

首先我必須對遺留代碼做一些假設,因爲您沒有提供關於它的更多細節。假設我需要添加一個新方法到Legacy而不需要重新實現所有內容。這是我會做:

public interface LegacyInterface { 
    void A(); 
} 

public final class LegacyClass implements LegacyInterface { 
    @Override 
    public void A() { 
     System.out.println("Hello from A"); 
    } 
} 

首先擴展了 「合同」

public interface MyInterface extends LegacyInterface { 
    void B(); 
} 

而在一個 「decorated」 的方式實現它

public final class MyClass implements MyInterface { 
    private final LegacyInterface origin; 

    public MyClass(LegacyInterface origin) { 
     this.origin = origin; 
    } 

    @Override 
    public void A() { 
     origin.A(); 
    } 

    @Override 
    public void B() { 
     System.out.println("Hello from B"); 
    } 
} 

關鍵的一點是MyInterface extends LegacyInterface :這是保證實現將受益於遺留代碼的服務和個人添加。

使用

MyInterface b = new MyClass(new LegacyClass()); 
0

我認爲最好的辦法是Option 1:擴展第三方類的頂部多了一個繼承層次結構,實現與雙調度訪問者模式。

問題是你需要額外的類的數量,但是這可以用動態包裝裝飾器來解決。 包裝裝飾是添加接口實現,方法和屬性已經存在obejcts方式:How to implement a wrapper decorator in Java?

這樣您需要Visitor接口,並把那裏visit(L legacy)方法:

public interface Visitor<L> { 
    public void visit(L legacy); 
} 

在AcceptInterceptor你可以把代碼accept方法

public class AcceptInterceptor { 

    @RuntimeType 
    public static Object intercept(@This WrappedAcceptor proxy, @Argument(0) Visitor visitor) throws Exception { 
     visitor.visit(proxy); 
    } 
} 

WrappedAcceptor接口定義的方法接受一個訪問者和送等和檢索被包裝的對象

interface WrappedAcceptor<V> { 
    Object getWrapped(); 
    void setWrapped(Object wrapped); 
    void accept(V visitor); 
} 

最後的實用代碼來創建周圍的任何obect包裝:

Class<? extends Object> proxyType = new ByteBuddy() 
.subclass(legacyObject.getClass(), ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC) 
.method(anyOf(WrappedAcceptor.class.getMethods())).intercept(MethodDelegation.to(AcceptInterceptor.class)) 
.defineField("wrapped", Object.class, Visibility.PRIVATE) 
.implement(WrappedAcceptor.class).intercept(FieldAccessor.ofBeanProperty()) 
.make() 
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) 
.getLoaded(); 
WrappedAcceptor wrapper = (WrappedAcceptor) proxyType.newInstance(); 
wrapper.setWrapped(legacyObject);