2012-05-12 101 views
5

我对访问者模式有点不友好,但我有一个需要访问者实现的任务(如果我想避免“instanceof”检查)。如何使用访问者模式替换“instanceof”

我有一个类是几个GWT元素的包装:标签,面板,小工具(可以是复选框,列表框,文本框等)。我使用一个数组作为UI的相似部分的集合。例如。标签+复选框,标签+文本框;标签+按钮等。

某些元素以不同的方式构建(例如派生自另一个类的一部分,例如Panel)。因此,我有两个相同的构造函数,但是在一个地方使用了重载方法。我可以合并这些构造函数,并使用里面提到的方法中的“instanceof”来检查元素。但我不喜欢这个解决方案,并且想用Visitor模式替换它。说实话,我不知道该怎么做,希望对你有所帮助。

这里是什么,我有一个例子:

public class MyWidgets { 
    private String stringLabel; 
    private Widget widget; 
    private Panel panel; 

    public MyWidgets(String stringLabel, Widget widget) { 
     this.stringLabel = stringLabel; 
     this.widget = widget; 

     initPanel(stringLabel, widget); 
    } 

    public MyWidgets(ConstructedClass cs, Widget widget) { 
     this.widget = widget; 

     initPanel(cs, widget); 
    } 

    private initPanel(String label, Widget widget) { 
     panel = SomeStaticUtilityClass.initPanel(new Label(label), widget); 
    } 

    private initPanel(ConstructedClass cs, Widget widget) { 
     panel = SomeStaticUtilityClass(cs, widget); 
    } 
} 

像这样的东西(我试图使它最大抽象,在现实中却是比较困难的)。

所以,我必须使用“的instanceof”的解决方案:

private initPanel(Object object, Widget widget) { 
    if(object instanceof String) { 
    panel = SomeStaticUtilityClass.initPanel(new Label(label), widget); 
    } 
    if(object instanceof ConstructedClass) { 
    panel = SomeStaticUtilityClass.initPanelFromObject(cs, widget); 
    } 
} 

我希望从“的instanceof”保存并离开只是一个构造函数,甚至,如果可能的话,一个init方法没有它的重载版本。 非常感谢您的建议,帮助。

P.S>我再说一遍,上面的类制作,看起来像一些误解尤其是这个字符串标签:)

+0

嗯..东西似乎是奇怪的以下行:“panel = SomeStaticUtilityClass(cs,widget);”。 SomeStaticUtilityClass是类还是方法? :) – Javaguru

+0

我更正了示例 – Dragon

回答

3

IMO,现有的解决方案,具有两个构造,是好的。

您可以使用策略模式,让您的构造函数获取一些接口的实例,而不是Object。该接口将具有以下方法:

Panel createPanel(Widget widget); 

。客户端会将StringPanelProvider的实例或ConstructedClassPanelProvider的实例传递给构造函数。因此你的构造看起来像:

public MyWidgets(PanelProvider panelProvider, Widget widget) { 
    this.widget = widget; 
    this.panel = panelProvider.createPanel(widget); 
} 

而且StringPanelProvider实施将看起来像

public class StringPanelProvider implements PanelProvider { 

    private String s; 

    public StringPanelProvider(String s) { 
     this.s = s; 
    } 

    @Override 
    public Panel createPanel(Widget widget) { 
     return SomeStaticUtilityClass.initPanel(new Label(s), widget); 
    } 
} 

的ConstructedClassPanelProvider会看起来是一样的。

如果你真的想用Visitor模式,那么你就必须修改上面一点点:

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

public interface Visitor { 
    void stringVisited(String s); 
    void constructedClassVisited(ConstructedClass cs); 
} 

public class StringVisitable { 
    private String s; 

    public StringVisitable(String s) { 
     this.s = s; 
    } 

    void accept(Visitor visitor) { 
     visitor.stringVisited(s); 
    } 
} 

// similar for ConstructedClassVisitable 

public MyWidgets(Visitable visitable, final Widget widget) { 
    this.widget = widget; 
    visitable.accept(new Visitor() { 
     public void stringVisited(String s) { 
      panel = SomeStaticUtilityClass.initPanel(new Label(label), widget); 
     } 

     public void constructedClassVisited(ConstructedClass cs) { 
      panel = SomeStaticUtilityClass.initPanelFromObject(cs, widget); 
     } 
    }); 
} 

但这样子过度设计给我。使用visitor pattern

+0

我认为与面板工厂结合使用的策略模式似乎比使用访问者更合适。 – Javaguru

+0

两种变体:你和creemama的一个很好。他们对我有用。 非常感谢。 – Dragon

2

一个实现如下:

public interface ConstructionArgVisitor { 
    void visit(LabelText text); 

    void visit(ConstructedClass clazz); 
} 

public interface ConstructionArg { 
    void accept(ConstructionArgVisitor visitor); 
} 

public class LabelText implements ConstructionArg { 
    private final String text; 

    public LabelText(String str) { 
     this.text = str; 
    } 

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

    public String getString() { 
     return this.text; 
    } 
} 

public class ConstructedClass implements ConstructionArg { 
    @Override 
    public void accept(ConstructionArgVisitor visitor) { 
     visitor.visit(this); 
    } 
} 

public class MyWidgets implements ConstructionArgVisitor { 
    private String stringLabel; 
    private Widget widget; 
    private Panel panel; 

    public MyWidgets(ConstructionArg constructionArg, Widget widget) { 
     this.widget = widget; 
     constructionArg.accept(this); 
    } 

    @Override 
    public void visit(LabelText labelText) { 
     this.stringLabel = labelText.getString(); 
     this.panel = SomeStaticUtilityClass.initPanel(new Label(labelText.getString()), this.widget); 
    } 

    @Override 
    public void visit(ConstructedClass clazz) { 
     this.panel = SomeStaticUtilityClass.initPanelFromObject(clazz, this.widget); 
    } 
} 

该解决方案是非常相似的JB Nizet的一个。此实现的ConstructorArgVisitor和JB Nizet的Visitor接口之间的区别是方法名称。 visit方法在ConstructorArgVisitor中过载,而在JB Nizet的Visitor中,方法名称包含其中的类型(例如,stringVisited)。 visit方法的重载更接近于visitor pattern on the Wikipedia page的示例。

我同意JB Nizet使用访问者模式可能有点过度工程;但是,如果您按照JB Nizet的建议使用PanelProvider,除非您知道提前提供的参数是StringConstructedClass,否则您可能仍需要执行instanceof检查,您试图避免此检查。

现在这是我的个人偏好,所以如果你喜欢,你可以不理会:尽量不要像Misko Hevery在“Flaw: Constructor does Real Work”中推荐的那样在构造函数中工作。例如,您可以将构建逻辑移入工厂。以下使用上述访问者模式的修改版本:

public interface ConstructionArgVisitor<T> { 
    T visit(LabelText text); 

    T visit(ConstructedClass clazz); 
} 

public interface ConstructionArg { 
    <T> T accept(ConstructionArgVisitor<T> visitor); 
} 

public class LabelText implements ConstructionArg { 
    private final String text; 

    public LabelText(String str) { 
     this.text = str; 
    } 

    @Override 
    public <T> T accept(ConstructionArgVisitor<T> visitor) { 
     return visitor.visit(this); 
    } 

    public String getString() { 
     return this.text; 
    } 
} 

public class ConstructedClass implements ConstructionArg { 
    @Override 
    public <T> T accept(ConstructionArgVisitor<T> visitor) { 
     return visitor.visit(this); 
    } 
} 

public class MyWidgetsFactory implements ConstructionArgVisitor<MyWidgets> { 
    private final Widget widget; 

    public MyWidgetsFactory(Widget widget) { 
     this.widget = widget; 
    } 

    public MyWidgets createMyWidgets(ConstructionArg constructionArg) { 
     return constructionArg.accept(this); 
    } 

    @Override 
    public MyWidgets visit(LabelText text) { 
     return new MyWidgets(text.getString(), this.widget, SomeStaticUtilityClass.initPanel(
       new Label(text.getString()), this.widget)); 
    } 

    @Override 
    public MyWidgets visit(ConstructedClass clazz) { 
     return new MyWidgets(null, this.widget, SomeStaticUtilityClass.initPanelFromObject(clazz, this.widget)); 
    } 
} 

public class MyWidgets { 
    private final String stringLabel; 
    private final Widget widget; 
    private final Panel panel; 

    public MyWidgets(String stringLabel, Widget widget, Panel panel) { 
     this.stringLabel = stringLabel; 
     this.widget = widget; 
     this.panel = panel; 
    } 
} 

public static void main(String[] args) { 
    final Widget widget = ...; 
    final MyWidgetsFactory factory = new MyWidgetsFactory(widget); 

    // create MyWidgets from label text 
    final String str = ...; 
    final MyWidgets labelWidget = factory.createMyWidgets(new LabelText(str)); 

    // create MyWidgets from constructed class 
    final ConstructedClass clazz = ...; 
    final MyWidgets constructedClassWidget = factory.createMyWidgets(clazz); 
} 

我还看到,您在构建过程中调用静态方法。尽管在许多代码库中GUI非常难以测试,但您可能需要阅读“Flaw: Brittle Global State & Singletons”和“Guide: Writing Testable Code”。