0

I like using the Builder/Director pattern to build complex objects. It works really well. The only restrictions (maybe it is not really a restrictions) I see is that all of the parameters for the construction of an object need be present before calling the builder.

I have now come to a situation where I need to build a complex object, but one part at a time. More than that, I need to support both scenarios.

Example of the first scenario (where I have all parameters needed to build a complex object at once): A user calls an api end point with a build request dto which contains all the parameters I need. I pass request dto to director and I build the object.

Example of the second scenario (where the building of an object is broken up into multiple stages): A user calls an api end point and passes in some dto which contains just the parameters to build first part one of the whole object. After some time, the user calls an api end point and passes another dto which contains just the parameters to build part two, and so forth.

What is the best way to handle this? Do I create multiple directors which build only those specific parts?

  • 1
    Is the object considered valid after the first part is built? – Robert Harvey Jul 31 '18 at 15:00
  • Hi, no it is not considered fully valid. – ColdAir Jul 31 '18 at 15:19
  • Ok. So why not just pass the first DTO to the second stage, and then pass both DTO's into the constructor of your new object? – Robert Harvey Jul 31 '18 at 15:58
  • 2
    Why do you think the builder pattern needs to know all the parameters upfront ? The GoF builder allows perfectly well to build complex objects parts by parts (in stages) and assemble the parts at the end. – Christophe Jul 31 '18 at 16:16
  • @Robert Harvey, where am i going to get the first part DTO during the second stage? Second stage is different end point which was called 4 days later. I have no idea what you mean about passing DTO into constructor of new object, by constructor do you mean builder or actual class constructor? If its former, I think you are getting the pattern confused. – ColdAir Jul 31 '18 at 17:25
0

There a few options the way I see it:

Option 1: Pass the Builder Around

There's no benefit in actually building and returning an object that will need to changed. Just pass the Builder object where ever it needs to go and provide access to its fields so that whatever needs to happen between each build stage can know the current state of the builder. The downsides to this are that you cant "lock in" a part of the Builders state if that is something you need to do. It can technically be done by having a function lockInStage1() or something which changes a flag that stops some setters from being used, but thats a bit messy.

Option 2: Provide a ReadOnly implementation

This is basically the same thing as Option 1, but without the Builder object. Just have a mutable version of the object and then allow the user to create a immutable version from it.

For example:

public class ReadOnlyMyObject {
    private int field1;
    private int field2;

    public ReadOnlyMyObject(MyObject obj) {
        field1 = obj.field1;
        field2 = obj.field2;
    }

    public int getField1() {
        return field1;
    }
}

public class MyObject {
    public int field1;
    public int field2;
    //fields can either be public or accessed through getters and setters

    public ReadOnlyMyObject asReadOnly() {
        return new ReadOnlyMyObject(this);
    }    
}

Personally, I prefer this over the builder pattern as it leaves you open to having a mutable object if it is ever needed, and why have a Builder class if the object isn't being built as one piece. It has the same disadvantage as Option 1 though. You can't really lock in the state of each stage.

Option 3: Builder for each Stage

Instead of a single object and Builder counterpart you would simply make an object for each stage and build it individually. The Builder for each stage would then take as a parameter the previous stage.

public class Stage1 {
    //stage 1 fields

    public class Builder {
        //Builder fields

        public Builder() { }

        //stage 1 builder functions

        public Stage1 build() {
            //build stage 1
        }
    }    
}

public class Stage2 {
    private Stage1 stage1
    //stage 2 fields

    public class Builder {
        private Stage1 stage1;
        //Builder fields

        public Builder(Stage1 stage1) {
            this.stage1 = stage1;
        }

        //stage 2 builder functions

        public Stage1 build() {
            Stage2 stage2 = new Stage2(this.stage1);
            //build stage 2
        }
    }    
}    


public class FinalObject {
    private Stage2 stage2
    //fields

    public class Builder {
        private Stage2 stage2;
        //Builder fields

        public Builder(Stage2 stage2) {
            this.stage2 = stage2;
        }

        //builder functions

        public FinalObject build() {
            FinalObject obj = new FinalObject(this.stage2);
            //build final object
        }
    }    
}

I don't love this, because you can potentially need to create a lot of classes. However there are some real benefits to this method.

  1. Each stage is locked in. Once it is created it can't be changed.

  2. If you need to add stages between 2 existing stages its fairly simple (kind of like how adding an object to a LinkedList has very little overhead. For that reason the stage objects shouldn't be called Stage, Stage2 or any ordinal value as it would force you to rename all of them.

Summary

Overall I would say if the stages need to be locked in go with the Multiple Builders as long as you don't need to change any data in a previous stage once it is created. It can be done, but its fighting the pattern.

If the data in each stage can potentially be changed by what happens in later stages go with passing the Builder or providing a ReadOnly version of the object.

  • I went with different builders for each step. I know that in the future more and more functionality will be added to each builder. – ColdAir Aug 30 '18 at 17:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.