简体   繁体   中英

Good chain of builder pattern

i have a complex business object like so

public class BusinessObject {

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

}

I am using slight modification of chain of responsibility pattern to build the above object.

Here is my interface

public interface BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext);

}

Now teams can comes and write their appenders. like so

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

By using this approach, In case of complex object construction the logic does not bloat up.

But It also has issues like

  1. There are no guardrails around Team1 messing up with another team's object or being dependent on another team's data. Apart from code reviews.

  2. The cases where BusinessObject is polymorphic, Once i have created builder of 1 type it is not possible to change it in appenders.

Question

  1. Is this right pattern to do?

  2. what are other ways to achieve the same thing? (creating complex objects in scalable, understandable way)

If you plan to use a builder pattern, then following separation of concerns, I would prefer to use a separate class for the BusinessObject object by employing a BusinessObjectBuilder builder pattern object. In order to access the builder pattern object from the relevant domain/business object, you can then (optionally, and I would recommend if appropriate, to) add a public static create() method to instantiate both a builder object and an encapsulated business object to build. I personally prefer the fluent style of builder objects, as the methods can be chained together and it makes writing the code so much easier.

As you are concerned about the complexity of building a Team1Object field and Team2Object field as separate concerns, I would think that what you are looking for is not a flat builder pattern, but instead facades of a builder pattern, or builder facets. In order to use facades of a builder, you would use a common builder base class and builder facade classes derived from the base class.

The base class, upon instantiation will create a simple BusinessObject and provide a method to build each field, including, by incorporating the fluent facade builders. The fluent facade builders will build only one part of the object parcel, the part of which may be complex in and of itself and could therefore be a separate concern from the overall building of the object as a whole.

As in all fluent builder classes, the return type is the same as the fluent builder (or fluent facade builder) class. Consider the following modifications:

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

If you employ this fluent builder with fluent facade builder pattern, you can then use it like so:

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

But then I'm not sure if this is what you wanted to achieve, particularly because I'm not exquisitely familiar with Team1 and Team2 objects or how you might would define them in terms of duty and hierarchy.

You mentioned chain of responsibility. This pattern is used when a chain of components each get a turn (in a chain) to process a command/query, and optionally stop the chain from proceeding.

Consider a process such as hiring an employee. There are several processes along the way. As each process is completed, the next process in the chain begins. If an exception occurs, perhaps the employee isn't hired after all (stopping the chain).

For this we have a chain of responsibilities and we would use the chain of responsibility design pattern. If, for example, Team2 processes depend on Team1 processes, you can use this pattern as it would solve this issue.

In order to use a chain of responsibility pattern, you will need the BusinessObject as well as one or more BusinessObjectModifier classes. Since the scope here is limited to Team1Appender and Team2Appender objects, we'll use those two as a reference.

In order to build the chain, you might want a base class to use for a next field for the next link in the chain and an add() method to hand-off to the next responsible link in the chain.

Consider the following chain of responsibility pattern:

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

Now, this should set up the chain. To use it, you might do something like:

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

Does this solve your problem? If you have a chain of responsibility, then perhaps.

I see your original specification used a builder as the subject to pass around the chain instead of the final object. This is an interesting intersection of the two patterns.

If you wanted to use a builder but then build an object using a chain of responsibility method, you might consider something along the lines of:

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

And then use it like so:

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

This isn't the only way to intersect these two concepts. There are several endpoints where the lines blur between patterns, though I don't really wish to enumerate them all.

I would like to, of course, answer your questions.

  1. Is it the right pattern? That depends on what you need.

Chain of responsibility consists of a source of command (the append() caller block in this case) which processes the command ( append ) through each processing object within a singly linked-list of a serially-sequenced series of processing objects ( BusinessObjectAppender objects).

If you have no chain, it's definitely the wrong pattern. If you don't require a single source of command (calling append() in one place), then it's not necessarily the right design or could be refactored until it is.

Builder pattern provides a solution for building a complex object when a constructor just doesn't cut it. In this case, constructing such an object is itself a separate concern, and therefore construction is broken off of the class it is building and put into a separate builder class.

If you need a way to construct an object which is different than the way it will be represented, it could be the right pattern.

For example, the way in which an automobile is presented to a driver or buyer or seller is most certainly not using the same interfaces that were used to build it in a factory. Of course, it will have a make, model, and year, all the same. But the customer isn't concerned with the cost of parts, time it takes to build, test results for various systems tests, which employees were involved on the days it was being built. But, sure enough, it goes 0-60 in 6.5 seconds and was painted the color red.

When building an object is complex and representation differs from the way in which it is built, a Builder pattern will solve it. (And it looks nice when you use a fluent approach.)

Both the builder pattern and the chain of responsibility pattern are part of the original "Gang of Four" design patterns.

Where they differ is Builder pattern is a Creational pattern and Chain of Responsibility is a Behavioral pattern.

I don't aim to rewrite the book, so I could just refer you to the title, "Design Patterns: Elements of Reusable Object-Oriented Software" (1994. Gamma, Erich; Helm, Richard; Johnson, Ralph; and Vlissides, John.) if you are looking to match one of their patterns to your own needs. Since you haven't explained the purpose of team 1 and team 2, I can't decide for you what is best.

  1. What are the other ways?

The Gang of Four provided a few other Creational and Behavioral patterns.

If Chain of Responsibility doesn't solve your problem, then a Command pattern might. (That's pretty much what the BusinessObjectAppender.append() abstract method did for you, minus the chain; as append() is roughly execute() once implemented.)

If you need to execute the same Command for the same subject across several (1...n) processes, but where the processes are not linked together in a responsibility chain and require no particular order, then a simple Iterator would do fine. Fortunately Java provides many facilities which are iterable very easily. Consider ArrayList<Appender> appenders .

There are many, many options to choose from. Sometimes you can mix them up.

I actually picked up a design patterns class on Udemy, specifically for C++, but there's many places online where you can find this information. This looks like a good source of summaries, particularly because the examples are in Java, and offer alternatives to the design choices I chose, giving you some extra examples: JournalDev .

Hopefully, that helps point you in the right direction.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM