当前位置: 首页 > 知识库问答 >
问题:

需要帮助理解“Builder”设计模式

澹台展鹏
2023-03-14

我正在重构前一段时间编写的一些代码,试图通过实现一些设计模式使其更加可靠。具体来说,我尝试使用构建器模式实例化GUI对象。

以下是“产品”的代码:

public class ContainerShell {
    private JFrame mainView = new JFrame("Election Simulator");
    private JTabbedPane tabbedPane = new JTabbedPane();
    private PopulationController populationController;
    private CandidateController candidateController;
    private ElectionController electionController;

    public JFrame getMainView() {
        return mainView;
    }

    public JTabbedPane getTabbedPane() {
        return tabbedPane;
    }
    
    public void addController(Controller uiController) throws RuntimeException {
        switch (uiController.getType()) {
            case POPULATION:
                this.populationController = (PopulationController) uiController;
            case CANDIDATE:
                this.candidateController = (CandidateController) uiController;
            case ELECTION:
                this.electionController = (ElectionController) uiController;
            default:
                throw new RuntimeException("Unknown controller type");
        }
    }
    
    public PopulationController getPopulationController() {
        return populationController;
    }

    public CandidateController getCandidateController() {
        return candidateController;
    }

    public ElectionController getElectionController() {
        return electionController;
    }
    
}

这是“混凝土建造者”的代码:

public class GuiBuilder implements Builder {
    public static final Component contentPaddingX = Box.createRigidArea(new Dimension(10,0));
    public static final Component contentPaddingY = Box.createRigidArea(new Dimension(0,10));
    public static final Component borderPaddingX = Box.createRigidArea(new Dimension(20,0));
    public static final Component borderPaddingY = Box.createRigidArea(new Dimension(0,20));
    private ContainerShell voteSimGui = new ContainerShell();
    
    public GuiBuilder() {
        
    }

    @Override
    public void addPopulationController(PopulationController uiController) {
        voteSimGui.addController(uiController);
    }

    @Override
    public void addCandidateController(CandidateController uiController) {
        voteSimGui.addController(uiController);
    }

    @Override
    public void addElectionController(ElectionController uiController) {
        voteSimGui.addController(uiController);
    }

    public ContainerShell build() {
        voteSimGui.getTabbedPane().addTab("Population", voteSimGui.getPopulationController().getPopPane());
        voteSimGui.getTabbedPane().addTab("Candidates", voteSimGui.getCandidateController().getCandPane());
        voteSimGui.getTabbedPane().addTab("Election Results", voteSimGui.getElectionController().getElectPane());
        voteSimGui.getMainView().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        voteSimGui.getMainView().getContentPane().add(voteSimGui.getTabbedPane());
        voteSimGui.getMainView().pack();
        voteSimGui.getMainView().setVisible(true);
        return voteSimGui;
    }
    
}

以及“抽象生成器”接口的代码:

public interface Builder {
    void addPopulationController(PopulationController popController);
    void addCandidateController(CandidateController candController);
    void addElectionController(ElectionController electController);
}

现在,这里是“Director”的当前工作构建方法:

public void buildGUI() {
        GuiBuilder guiBuilder = new GuiBuilder();
        guiBuilder.addPopulationController(ControllerFactory.getPopulationInstance());
        guiBuilder.addCandidateController(ControllerFactory.getCandidateInstance());
        guiBuilder.addElectionController(ControllerFactory.getElectionInstance());
        gui = guiBuilder.build();
    }

酷,那有什么问题吗?好吧,这可能最终无关紧要,但这是我对该方法的实际首选实现:

gui = new GuiBuilder()
    .addPopulationController(ControllerFactory.getPopulationInstance())
    .addCandidateController(ControllerFactory.getCandidateInstance())
    .addElectionController(ControllerFactory.getElectionInstance())
    .build();

看看它看起来有多干净?唯一的问题是JetBrains无论出于什么原因都在抱怨……我不知道为什么。编译器给出的具体错误是无法解析令牌“addCandidateController”(但它接受addPopulationController)。因此,结合之前的实现工作的事实,我知道这不一定是代码功能的问题,因为它根本不喜欢我试图将方法链接在一起的方式。但是为什么允许Java这么做呢?!(来自另一个项目):

HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://api.kraken.com/0/private/Balance"))
                    .header("API-Key", auth.getPubKey())
                    .header("API-Sign", auth.sign("/0/private/Balance", "nonce=" + nonceValue))
                    .POST(HttpRequest.BodyPublishers.ofString("nonce=" + nonceValue))
                    .build();

同样,这是另一个项目的一个例子,我在其中实现了Java的HttpRequest。生成器;但是这个实现工作得很好。那么我错过了什么?

共有2个答案

龚浩宕
2023-03-14

问题很简单,您希望“构建器设置器”的返回类型为本身,并且所有此类“构建器设置器”都以返回this;结尾。换句话说,替换:

public void addCandidateController(CandidateController uiController) {
    voteSimGui.addController(uiController);
}

使用:

public GuiBuilder addCandidateController(CandidateController uiController) {
    voteSimGui.addController(uiController);
    return this;
}

不过,有几个重要的注意事项,因为您没有在这些片段中很好地应用这些“模式”:

>

  • 你在到处侵犯雅格尼。为什么在blazes中为这个构建器制作界面?增加抽象层和间接层是一种罪恶。通常情况下,它是最不邪恶的,也就是说,一个好主意,但这是尽管事实,你正在引入抽象和间接层。间接层混淆了问题,增加了代码量。这些都是成本。你通过支付这些费用来“买”什么?在为构建器提供接口的情况下,答案通常是:完全没有。

    构建器通常的命名方案只是属性,而不是添加add表示有一个列表,即使这样,通常的方法仍然是不使用单词add。正确的名称只是candidateController,而不是addCandidateController。

    构建器的一个优点是您使用它“构建”的实际对象可以是不可变的。如果您的ContainerShell对象不是不可变的,那么构建器有什么意义呢?为什么要有一个构建器(以及接口,现在您拥有的代码是您真正需要的3倍)?为什么不只是:

    class ContainerShell {
      public ContainerShell setPopulationController(PopulationController c) {
        this.populationController = c;
        return this;
      }
      public ContainerShell setCandidateController(CandidateController c) {
        this.candidateController = c;
        return this;
      }
      public ContainerShell setElectionController(ElectionController c) {
        this.electionController = c;
        return this;
      }
    }
    

    上面的代码可以按照您想要的方式精确使用:

    new ControllerShell()
     .setPopulationController(ControllerFactory.getPopulationInstance())
     .setCandidateController(ControllerFactory.getCandidateInstance())
     .setElectionController(ControllerFactory.getElectionInstance())
     ;
    

    并使用四分之一的代码,显然是赢家。

    最好将构建器视为替换大型参数列表。换句话说,构建器实际上是简单方法调用的替代方案——当所述方法(或构造函数,它在几乎所有相关方面都等同于静态方法)需要如此大的参数列表以至于阅读调用它的代码令人困惑时,您可以使用它们。例如,给定此方法:

    public Bridge(int buildYear, int carLanes, int bikeLanes, int walkLanes, int span) { ... }
    

    对它的调用看起来像:new Bridge(1981, 4, 2, 2, 2005)。任何阅读它的人都不知道发生了什么,每次你尝试编写对它的调用时,你都不知道该做什么。这是标志1:哦,也许构建器是合适的。标志2是:实际上,对于这些参数中的许多,我可能希望它是可选的(java做得不好,因为你所能做的就是组合重载爆炸)。构建器将参数命名为“参数”,并为您提供将它们设为可选的选项。换句话说,构建器会替换它:

    new Bridge(1981, 4, 0, 2, 2005)
    

    有了这个:

    Bridge.builder()
      .buildYear(1981)
      .carLanes(4)
      // we don't call .bikeLanes; it defaults to 0.
      .walkLanes(2)
      .span(2005)
      .builder()
    

    正如您所见,构建器要“冗长”得多,因此使用它们(更多代码!)是要付出代价的。你从中得到的东西值这个价吗?我可以这么说,在这个例子中,灌篮。这段代码的可读性要高得多。您还可以选择将构建桥的工作拆分,以便各个独立的代码段分别完成部分工作(您不能真正“拆分”新桥(1981,4,0,22005),但您可以创建一个桥生成器,调用一半的“setter”,然后将生成器对象交给其他方法来完成工作。很少有人想要这个,但如果这是相关的,那么建设者是很棒的。

    从技术上讲,任何时候用它替换方法都可以使用构建器,无论该方法是什么。在实践中,“具有非常长、笨重、令人困惑的参数列表的方法”在制作不可变对象时最常出现:鉴于您需要指定构造函数中的所有字段(没有setters;它是一个不可变对象!),自然,您会得到长列表,然后导致需要构建器。大多数书籍、教程等“捷径”这种逻辑推理,他们不应该这样做。它是这样的:

    • 不可变对象会导致长参数。
    • 长篇大论会导致混乱。
    • 构建器替换了长的argslsts,从而避免了混淆。

    不要跳过步骤,比如说“不可变最好由构建器生成”。这太简单了。

    重点是,你要在这里替换哪个“长argslist”?我看不出来-因此,builder在这里是错的。除非你想深入其中,并将ContainerShell对象更改为不可变。不变性有各种各样的优点,然而,GUI对象并不真正“需要”这些优点中的任何一个,这再次让我们回到:我怀疑你想要一个构建器来实现这一点。我认为你只是想让二传手回归自我,忘记所有关于为这做一个建设者的事情。

    请记住,编程风格指南和原则只有一个目标:使您的代码更易于维护(包括在面对未来可能的更改请求时更加灵活)。如果您无法想象规则的应用如何有助于实现这一目标,那么就不要应用它——规则本身本身并不是一个目标。

  • 何峰
    2023-03-14

    要启用调用链,请返回正在修改的对象,即生成器对象。

    在每个添加…方法中,添加最后一行,并返回。并将返回类型从void更改为builder类。

    顺便说一句,我不认为需要您的Builder接口。你真的会有多个实现吗?

    @Override
    public GuiBuilder addPopulationController(PopulationController uiController) {
        voteSimGui.addController(uiController);
        return this ;
    }
    
    @Override
    public GuiBuilder addCandidateController(CandidateController uiController) {
        voteSimGui.addController(uiController);
        return this ;
    }
    

    您可以在Java的OpenJDK实现中找到构建器的开源示例。请参阅DateTimeFormatterBuilderHttpRequest. Builder

     类似资料:
    • 你能帮我在jrxml中设置或更改哪些属性以获得相同的PDF输出吗?

    • 我使用ACR122读卡器已经有一段时间了,它在读取Mifare 1K或Mifare Ultralight NFC卡时都没有问题。 将读卡器升级到最新版本(ACR1251)后,我的程序无法读取Mifare 1K卡的UID。 这是我用来阅读的片段: 使用新版rad阅读器: ResponseAPDU.getSW1()函数返回98 而getSW2()返回130 我试着在网上和读卡器文档中搜索响应代码的解释

    • 我已经从Godaddy购买了SSL,我的网站托管在AWS中。我想在AWS上设置SSL。我绑定使用证书管理器导入证书。它问了我三件事: 证书体*--在这里,我从Godaddy获得了。crt文件内容 证书私钥*--这里我仍然困惑需要输入什么。请帮我做这个 证书链--在这里我输入了sf_bundle-g2-g1.crt代码。 请帮助我启用AWS中的SSL。提前致谢

    • 我试图写一个代码为每个股票价值是75美元或更多添加一个"*"在STK_FLAG列。 ORA-06550:第15行,第21列:PLS-00201:标识符“STK\U FLG”必须声明ORA-06550:第15行,第5列:PL/SQL:SQL语句忽略ORA-06550:第23行,第7列:PL/SQL:ORA-00904:“STK\U FLG”:无效标识符ORA-06550:第17行,第5列:PL/SQ

    • 我需要一些帮助用JSOUP解析这个html。我正在尝试从表中的每一列获取数据值。我一直在看JSoup文档,试图弄清楚我到底需要做什么,但还是不确定。看起来网站使用了CSS和内联格式的组合;其中大部分可以转换为CSS并减小页面大小。 这是html文件的一个小片段(实际上差不多有5 MB大小)。 更新:我已经更新了源代码,以更准确地显示html的结构。我假定tbody将位于表元素中是一个给定的条件。我

    • 我有一个linq查询,我在foreach循环中迭代其结果。 第一个是一个从表布局面板中获取控件集合的查询,然后我遍历该集合并从表布局面板中删除控件,因此: 上面的操作并不像我预期的那样(即抛出一个错误),它只删除了一半的控件(奇数的控件)。似乎在每次迭代中,AllItems都会减少其自身,尽管底层集合正在修改,但不会抛出错误。 如果使用字符串数组执行Similar: 这次Visual studio