良好的构建器模式链

bes*_*hes 2 java oop design-patterns chain-of-responsibility builder-pattern

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

public class BusinessObject {

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

}
Run Code Online (Sandbox Code Playgroud)

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

这是我的界面

public interface BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext);

}
Run Code Online (Sandbox Code Playgroud)

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

public class Team1ObjectAppender implements BusinessObjectAppender {

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

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext) {
        Team2Object object2 = somehowComputeObject2();
        businessObject.object2(object2)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

但它也存在这样的问题

  1. Team1 周围没有任何护栏,不会干扰另一个团队的对象或依赖另一个团队的数据。除了代码审查之外。

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

问题

  1. 这是正确的做法吗?

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

Dav*_*all 5

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

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

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

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您使用带有流畅外观构建器模式的流畅构建器,则可以像这样使用它:

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

但我不确定这是否是您想要实现的目标,特别是因为我不太熟悉 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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

这能解决您的问题吗?如果您有责任链,那么也许可以。

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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

这不是交叉这两个概念的唯一方法。尽管我并不想一一列举,但有几个端点使得模式之间的界限变得模糊。

我当然愿意回答你的问题。

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

责任链由命令源(在本例中为调用者块)组成,该命令源通过一系列连续处理对象(对象)的单链表中的每个处理对象来append()处理命令()。appendBusinessObjectAppender

如果你没有链条,那肯定是错误的图案。如果您不需要单一命令源(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

希望这能帮助您指明正确的方向。