📜 ⬆️ ⬇️

The correct polymorphic Java builder.

What is all this about?


When implementing chained builder in Java, everything is fine until you need to add inheritance. Immediately, two problems arise - how to make the parent builder methods return the object of the child builder and how to pass the child builder to functions that accept the parent. A pattern implementation is proposed that solves both problems. The source can be found here on the githaba.

Upd. Real problem


The application has dto-objects to display the result, which are constructed as follows:

1) The builder of the necessary dto-object is created.
2) The builder is transferred to different classes in a chain; each class uses the builder to set the fields it needs.

One day they decided to introduce a new version of the API, the dto-object was expanded with the help of inheritance and it turned out that its builder could not be thrust into the existing class chain for completion.
')

Formulation of the problem


There should be 100,500 words about the importance of the pattern builder, so as not to bore the reader with this crap, let's get down to business right away. Suppose there are 3 classes with clear names Gen1, Gen2 and Gen3. They form a linear hierarchy of Gen3 → Gen2 → Gen1. Each of them contains exactly one very important method named setValX (where X is the number from the class name). We want to get Builder1, Builder2, Builder3 builders, each of which contains the corresponding valX method, which is implemented only in one class (we don’t want to copy-paste).

The chains should also work:

Gen1 gen1 = builder1.val1("val1").build(); Gen2 gen2 = builder2.val1("val1").val2("val2").build(); Gen3 gen3 = builder3.val1("val1").val2("val2").val3("val3").build(); 

And the ability to use child builders instead of parent ones:

 Gen1 someFunction(Builder1 builder1) { return builder1.val1("val1111"); } ... someFunction1(builder3.val2("val222").val3("val333")); 

What and how did it happen


The builder is supposed to do the following: create an object at the very beginning, then fill its fields, and in the build () function return it to the client. In this case, we need a class that will do a simple thing - to store a link to the object being constructed and to store a link to the builder of the desired type, which will be returned by all value setters. The following class solves the problem:

 public class BuilderImpl<T, RetBuilder> { protected T nested; RetBuilder returnBuilder; protected BuilderImpl(T child) { nested = child; } protected T getNested() { return nested; } protected void injectReturnBuilder(RetBuilder builder) { returnBuilder = builder; } protected RetBuilder self() { return returnBuilder; } public T build() { return nested; } } 

Of course, it would be better to get rid of the injectReturnBuilder method by passing the necessary data to the constructor, but alas, this child builder will be passed there, which cannot be used until the end of the super () parent constructor. The getNested () method on the amateur can be accessed directly from the nested field. The self () method is made not to confuse the field with the word this.

Now let's think about this problem. If we have some generic Builder1 <> which implements everything we need for the Gen1 class (with some Gen1 parameters, Builder1), we will not need to inherit the Builder2 Gen2 generic from it (with some Gen1 parameters, Builder1), and from Builder3 for Gen3, it turns out that Builder3, in its ancestors, has two implementations of the original Builder1 with different parameters, which, alas, is expressly prohibited by Java.

But there is a solution - it is necessary to divide the setting of the object's fields and the creation of an object into different classes.
Classes with InnerBuilderX names are responsible for setting fields and returning an object and allow inheritance. Classes with the names FinalBuilderX are inherited from the corresponding InnerBuilderX, adding the creation of the original object and are not allowed for further inheritance.

Another difficulty is to write InnerBuilderX with the correct combination of wildcard. Through long trial and error (reading the specifications is not our way) was found an acceptable option. But until it was found, combinations were tried, to which the Inspector Idea was dying or was wrong, which somewhat slowed down the development. And so, here is the code for the InnerBuilder1 class Gen1. The parameter T is the type of the stored object, RetBuilder is the type of the builder that is returned from the installation function val1.

 public static class InnerBuilder1<T extends Gen1, RetBuilder extends InnerBuilder1<? extends T, ?>> extends BuilderImpl<T, RetBuilder> { protected InnerBuilder1(T created) { super(created); } public RetBuilder val1(String val) { getNested().setVal1(val); return self(); } } 

Of course, the recursive construction class InnerBuilder1 <T extends Gen1, RetBuilder extends InnerBuilder1 <? extends T,? >> a little annoying, but it really works.

Well, FinalBuilder is pretty simple:

 private static class FinalBuilder1 extends InnerBuilder1<Gen1, FinalBuilder1> { private FinalBuilder1() { super(new Gen1()); //   this injectReturnBuilder(this); } } 

It remains to add a static function to create a builder:

 public static InnerBuilder1<? extends Gen1, ?> builder() { return new FinalBuilder1(); } 

We now turn to the child builder. We inherit the implementation for the internal builder and make the creation of the object in the final:

 public static InnerBuilder2<? extends Gen2, ?> builder() { return new FinalBuilder2(); } public static class InnerBuilder2<T extends Gen2, RetBuilder extends InnerBuilder2<? extends T,?>> extends InnerBuilder1<T, RetBuilder> { protected InnerBuilder2(T created) { super(created); } public RetBuilder val2(String val) { getNested().setVal2(val); return self(); } } private static class FinalBuilder2 extends InnerBuilder2<Gen2, FinalBuilder2> { private FinalBuilder2() { super(new Gen2()); injectReturnBuilder(this); } } 

You can try to compile the test code:

 Gen2.builder().val1("111").val1("111").val1("111").val1("111").val2("222").build(); 

Happened! And what about polymorphism?

 //    Gen1 Gen1 buildGen1Final(Gen1.InnerBuilder1<? extends Gen1, ?> builder) { builder.val1("set value from Gen1 builder"); return builder.build(); } ... //     Gen2 buildGen1Final( Gen2.builder().val2("set value from Gen2 builder") ); 

Everything works too. Similarly, the builder is implemented for the Gen3 class, for details you can contact the githad

Source: https://habr.com/ru/post/315156/


All Articles