首页 > 解决方案 > 良好的构建器模式链

问题描述

我有一个像这样的复杂业务对象

public class BusinessObject {

    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...

}

我正在使用责任链模式的轻微修改来构建上述对象。

这是我的界面

public interface BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext);

}

现在团队可以来编写他们的附加程序。像这样

public class Team1ObjectAppender implements BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext) {
        Team1Object object1 = somehowComputeObject1();
        businessObject.object1(object1)
    }
}
public class Team2Appender implements BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext) {
        Team2Object object2 = somehowComputeObject2();
        businessObject.object2(object2)
    }
}

通过使用这种方法,在复杂对象构造的情况下,逻辑不会膨胀。

但它也有类似的问题

  1. Team1 周围没有护栏,可以搞乱另一个团队的对象或依赖于另一个团队的数据。除了代码审查。

  2. BusinessObject 是多态的情况,一旦我创建了 1 种类型的构建器,就不可能在附加程序中更改它。

问题

  1. 这是正确的模式吗?

  2. 还有什么其他方法可以实现相同的目标?(以可扩展、可理解的方式创建复杂的对象)

标签: javaoopdesign-patternsbuilder-patternchain-of-responsibility

解决方案


如果您打算使用构建器模式,那么在关注点分离之后,我更愿意通过使用构建器模式对象为BusinessObject对象使用单独的类。BusinessObjectBuilder为了从相关的域/业务对象访问构建器模式对象,您可以(可选地,如果合适的话,我会推荐)添加一个public static create()方法来实例化构建器对象和封装的业务对象来构建。我个人更喜欢 builder 对象的流利风格,因为这些方法可以链接在一起,这使得编写代码变得更加容易。

由于您担心将 Team1Object 字段和 Team2Object 字段构建为单独的关注点的复杂性,我认为您正在寻找的不是平面构建器模式,而是构建器模式的外观或构建器方面。为了使用构建器的外观,您将使用公共构建器基类和从基类派生的构建器外观类。

基类在实例化时将创建一个简单的 BusinessObject 并提供一种方法来构建每个字段,包括通过合并流利的外观构建器。流利的立面建造者将只建造对象地块的一部分,该部分本身可能很复杂,因此可能是与整个对象的整体建造不同的关注点。

与所有 fluent 构建器类一样,返回类型与 fluent 构建器(或 fluent 外观构建器)类相同。考虑以下修改:

public class BusinessObject {
    internal BusinessObject() {
        // Default constructor should exist
        // but only needs to be visible at the
        // BusinessObjectBuilder scope.
        // use whatever access modifier you would
        // prefer, however, based on needs,
        // internal or public is appropriate.
        // in C++, use `friend BusinessObjectBuilder`
    }
    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...
    public static BusinessObjectBuilder create() {
        return new BusinessObjectBuilder();
    }
}

public class BusinessObjectBuilder {
    protected BusinessObject bObject; // the object we will build
    internal BusinessObjectBuilder() {
        // A default constructor, again minimally visible
        // to BusinessObject; internal or public is good here.
        // Needs to create a basic BusinessObject.
        bObject = new BusinessObject();
    }
    public BusinessObjectBuilder debug(String debugString) {
        // Sets BusinessObject.debug
        this.bObject.debug += debugString + "\n";
        // Then returns the BusinessObjectBuilder.
        // Every method should return this except the facade methods
        // and the build method.
        return this;
    }
    public Team1ObjectBuilder team1Object() {
        // Returns the Team1Object builder facet.
        return new Team1ObjectBuilder(this.bObject);
    }
    public Team2ObjectBuilder team2Object() {
        // Returns the Team2Object builder facet.
        return new Team1ObjectBuilder(this.bObject);
    }
    public BusinessObject build() {
        // Technically, it's already built at this point. Return it.
        return this.bObject;
    }
}

public class Team1ObjectBuilder extends BusinessObjectBuilder {
    private BusinessObject bObject; // the object we will build
    internal Team1ObjectBuilder(BusinessObject bObject) {
        // This time we copy the object we were building
        this.bObject = bObject;
    }
    private Team1Object somehowComputeObject1() {
        // pour on the magic
        return new Team1Object();
    }
    public Team1ObjectBuilder append(Context someApplicationContext) {
        this.bObject.object1 = somehowComputeObject1();
    }
}

public class Team2ObjectBuilder extends BusinessObjectBuilder {
    private BusinessObject bObject; // the object we will build
    internal Team2ObjectBuilder(BusinessObject bObject) {
        // Again we copy the object we were building
        this.bObject = bObject;
    }
    private Team2Object somehowComputeObject2() {
        // pour on the magic
        return new Team2Object();
    }
    public Team2ObjectBuilder append(Context someApplicationContext) {
        this.bObject.object2 = somehowComputeObject2();
    }
}

如果你使用这个 fluent builder 和 fluent facade builder 模式,你可以像这样使用它:

BusinessObject complexBusinessObject = BusinessObject.create()
                                                     .debug("Let's build team1Object.")
                                                     .team1Object().append( /* someApplicationContext */)
                                                     .debug("Let's build team2Object.")
                                                     .team2Object().append( /* someApplicationContext */)
                                                     .debug("All done.")
                                                     .build();

但是我不确定这是否是您想要实现的目标,特别是因为我对 Team1 和 Team2 对象并不十分熟悉,或者您可能如何根据职责和层次结构来定义它们。

你提到了责任链。当组件链中的每个组件(在链中)轮流处理命令/查询并可选地停止链继续进行时,使用此模式。

考虑一个过程,例如雇用员工。沿途有几个过程。随着每个过程的完成,链中的下一个过程开始。如果发生异常,则可能根本没有雇用该员工(停止链条)。

为此,我们有一个责任链,我们将使用责任链设计模式。例如,如果 Team2 进程依赖于 Team1 进程,您可以使用此模式,因为它可以解决此问题。

为了使用责任链模式,您将需要BusinessObject一个或多个BusinessObjectModifier类。由于这里的范围仅限于Team1AppenderTeam2Appender对象,我们将使用这两个作为参考。

为了构建链,您可能需要一个基类用于next链中下一个链接的字段,以及一个add()传递给链中下一个负责链接的方法。

考虑以下责任链模式:

    public class BusinessObject {
        public Team1Object object1;
        public Team2Object object2;
        public String debug;
        ...
    }

    public abstract class BusinessObjectAppender { // provides shared append() modifier
        protected BusinessObjectAppender next = null;
        public void add(BusinessObjectAppender boa) {
            if (this.next == null) {
                this.next = boa;
            }
            else {
                next.add(boa); // recursive call to the end of the linked list "chain"
            }
        }
        public abstract void append(BusinessObject businessObject, Context someApplicationContext);
    }

    public class Team1ObjectAppender extends BusinessObjectAppender {
        public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
            Team1Object object1 = somehowComputeObject1();
            businessObject.object1 = object1;
            if (this.next == null) {
                return businessObject; // you have to since you can't pass by ref/out in java
            }
            else {
                return next.append(businessObject, someApplicationContext);
            }
        }
    }

    public class Team2ObjectAppender extends BusinessObjectAppender {
        public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
            Team2Object object2 = somehowComputeObject2();
            businessObject.object2 = object2;
            if (this.next == null) {
                return businessObject; // you have to since you can't pass by ref/out in java
            }
            else {
                return next.append(businessObject, someApplicationContext);
            }
        }
    }

现在,这应该建立链。要使用它,您可以执行以下操作:

BusinessObject businessObject = new BusinessObject();
BusinessObjectAppender appendChain = new Team1ObjectAppender();
appendChain.add(new Team2ObjectAppender()); // start --> team1 --> team2 --> done
businessObject = appendChain(businessObject, /*someApplicationContext*/);

这能解决你的问题吗?如果你有一个责任链,那么也许。

我看到您的原始规范使用构建器作为主题来传递链而不是最终对象。这是两种模式的有趣交集。

如果您想使用构建器,然后使用责任链方法构建对象,您可能会考虑以下内容:

public class BusinessObject {
    internal BusinessObject() {
        // Default constructor should exist
        // but only needs to be visible at the
        // BusinessObjectBuilder scope.
        // use whatever access modifier you would
        // prefer, however, based on needs,
        // internal or public is appropriate.
        // in C++, use `friend BusinessObjectBuilder`
    }
    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...
    public static BusinessObjectBuilder create() {
        return new BusinessObjectBuilder();
    }
}

public abstract class BusinessObjectAppender { // provides shared append() modifier
    protected BusinessObjectAppender next = null;
    public void add(BusinessObjectAppender boa) {
        if (this.next == null) {
            this.next = boa;
        }
        else {
            next.add(boa); // recursive call to the end of the linked list "chain"
        }
    }
    public abstract void append(BusinessObject businessObject, Context someApplicationContext);
}

public class Team1ObjectAppender extends BusinessObjectAppender {
    public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
        Team1Object object1 = somehowComputeObject1();
        businessObject.object1 = object1;
        if (this.next == null) {
            return businessObject; // you have to since you can't pass by ref/out in java
        }
        else {
            return next.append(businessObject, someApplicationContext);
        }
    }
}

public class Team2ObjectAppender extends BusinessObjectAppender {
    public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
        Team2Object object2 = somehowComputeObject2();
        businessObject.object2 = object2;
        if (this.next == null) {
            return businessObject; // you have to since you can't pass by ref/out in java
        }
        else {
            return next.append(businessObject, someApplicationContext);
        }
    }
}

public class BusinessObjectBuilder {
    protected BusinessObject bObject; // the object we will build
    internal BusinessObjectBuilder() {
        // A default constructor, again minimally visible
        // to BusinessObject; internal or public is good here.
        // Needs to create a basic BusinessObject.
        bObject = new BusinessObject();
    }
    public BusinessObjectBuilder debug(String debugString) {
        // Sets BusinessObject.debug
        this.bObject.debug += debugString + "\n";
        // Then returns the BusinessObjectBuilder.
        // Every method should return this except the facade methods
        // and the build method.
        return this;
    }
    public BusinessObjectBuilder append(Context someApplicationContext) {
        // Create the chain
        BusinessObjectAppender appendChain = new Team1ObjectAppender();
        appendChain.add(new Team2ObjectAppender()); // start --> team1 --> team2 --> done
        this.bObject = appendChain(this.bObject, someApplicationContext);
        // Return the Builder.
        return this;
    }
    public BusinessObject build() {
        // Technically, it's already built at this point. Return it.
        return this.bObject;
    }
}

然后像这样使用它:

BusinessObject complexBusinessObject = BusinessObject.create()
                                                     .debug("Run through the chain of responsibilities.")
                                                     .append( /* someApplicationContext */)
                                                     .debug("All done.")
                                                     .build();

这不是交叉这两个概念的唯一方法。有几个端点在图案之间的线条模糊不清,尽管我真的不想一一列举。

我当然想回答你的问题。

  1. 这是正确的模式吗?这取决于你需要什么。

责任链由一个命令源(append()在这种情况下为调用程序块)组成,该命令源append通过一系列处理对象(对象)的单链表中的每个处理对象处理命令( BusinessObjectAppender)。

如果你没有链条,那肯定是错误的模式。如果您不需要单一的命令源(append()在一个地方调用),那么它不一定是正确的设计,或者可以重构直到它是。

构建器模式提供了一种解决方案,用于在构造函数不切割复杂对象时构建它。在这种情况下,构造这样的对象本身就是一个单独的问题,因此构造从它正在构建的类中分离出来并放入一个单独的构建器类中。

如果您需要一种方法来构造与表示方式不同的对象,那么它可能是正确的模式。

例如,向驾驶员或买方或卖方展示汽车的方式肯定不会使用与在工厂中制造汽车相同的界面。当然,它的品牌、型号和年份都是一样的。但客户并不关心零件成本、建造时间、各种系统测试的测试结果,而员工在建造当天就参与其中。但是,果然,它在 6.5 秒内变为 0-60,并被涂成红色。

当构建一个对象很复杂并且表示与构建它的方式不同时,构建器模式将解决它。(当您使用流利的方法时,它看起来不错。)

建造者模式和责任链模式都是最初的“四人帮”设计模式的一部分。

它们的不同之处在于建造者模式是一种创造模式,而责任链是一种行为模式。

我不打算重写这本书,所以我可以参考标题,“设计模式:可重用的面向对象软件的元素”(1994 年。Gamma,Erich;Helm,Richard;Johnson,Ralph;和 Vlissides, John.) 如果您希望将其中一种模式与您自己的需求相匹配。由于您尚未解释团队 1 和团队 2 的目的,我无法为您决定什么是最好的。

  1. 还有哪些方法?

四人帮提供了一些其他的创造和行为模式。

如果责任链不能解决您的问题,那么命令模式可能会。(这几乎就是BusinessObjectAppender.append()抽象方法为您所做的,减去了链;append()大致execute()曾经实现过。)

如果您需要跨多个 (1...n) 进程为同一主题执行相同的命令,但是这些进程没有在责任链中链接在一起并且不需要特定的顺序,那么一个简单的迭代器就可以了。幸运的是,Java 提供了许多非常容易迭代的工具。考虑ArrayList<Appender> appenders

有很多很多可供选择的选项。有时你可以把它们混合起来。

实际上,我在 Udemy 上学习了一个设计模式课程,专门针对 C++,但是网上有很多地方可以找到这些信息。这看起来是一个很好的摘要来源,特别是因为这些示例是用 Java 编写的,并且提供了我选择的设计选择的替代方案,并为您提供了一些额外的示例:JournalDev

希望这有助于为您指明正确的方向。


推荐阅读