5

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

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

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

这是我所拥有的一个例子:

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 方法。提前感谢您的建议,帮助。

PS> 我再说一遍,上面的类是捏造的,看起来有些误解,尤其是这个字符串标签:)

4

2 回答 2

3

IMO,您现有的解决方案,有两个构造函数,很好。

您可以使用策略模式并让您的构造函数采用某个PanelProvider接口的实例而不是 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 看起来是一样的。

如果您真的想使用访问者模式,那么您必须对上面的内容进行一些修改:

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);
       }
   });
}

但这对我来说似乎是过度工程。

于 2012-05-12T12:49:57.133 回答
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方法更类似于Wikipedia 页面上的访问者模式示例。

我同意 JB Nizet 的观点,即使用访问者模式可能有点过度设计;但是,如果您PanelProvider按照 JB Nizet 的建议使用 a,除非您提前知道参数是 aString或,否则您可能仍需要进行检查,这是您试图避免的。ConstructedClassinstanceof

现在这是我个人的偏好,所以如果你愿意,你可以忽略:尽量不要像 Misko Hevery 在“缺陷:构造函数做真正的工作”中建议的那样在构造函数中做工作。例如,您可以将构造逻辑移动到工厂中。以下使用上述访问者模式的修改版本:

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 很难测试,但您可能需要阅读“缺陷:脆弱的全局状态和单例”和“指南:编写可测试代码”

于 2012-05-12T14:14:22.053 回答