Wednesday, June 09, 2010

Using inheritance with fluent interfaces: get this

Recently I had a situation where I needed to implement Joshua Bloch's Builder pattern (see Effective Java, item 2) over a hierarchy of Java domain objects. Similar problems would arise when building other types of fluent interface, which commonly "return this" from each method in order to support method chaining. Creating a working solution presented some interesting challenges!

Without going into too many details, the fluent Builder pattern is needed in languages without named arguments and default arguments, like Java, in order to avoid long lists of constructor parameters, or a bunch of setters on the object (which may be immutable). For example:

public class X {
protected int foo;
protected int bar;

public static class Builder {
private X x = new X();

public Builder withFoo( int foo ) {
x.foo = foo; return this;
}
public Builder withBar( int bar ) {
x.bar = bar; return this;
}
public X build() { return x; }
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

You would then use this code as follows:

X x = new X.Builder().withFoo( 1 ).withBar( 2 ).build();

Obviously, for a class with only two instance variables, this pattern doesn't make much sense -- you'd just use a constructor. But for domain objects with lots more than two instance variables, or with subclasses that may add more than two, it makes plenty of sense.

However, this becomes more challenging when dealing with objects that use inheritance. In the project I'm working on, we had a number of domain objects that were (legitimately) using inheritance. The trouble is that the builder pattern doesn't particularly handle this well as it stands.

The simplest way of creating builders for inherited classes is simply to duplicate the builder methods:

public class Y extends X {
private int baz;

public static class Builder {
private Y y = new Y();

public Builder withFoo( int foo ) {
y.foo = foo; return this;
}
public Builder withBar( int bar ) {
y.bar = bar; return this;
}
public Builder withBaz( int baz ) {
y.baz = baz; return this;
}
public Y build() { return y; }
}

protected Y() {}

public int getBaz() { return baz; }
}

But duplication is EvilTM, especially if you have lots of builder methods, or lots of subclasses. We really don't want both Builder classes to have the withFoo and withBar methods. How do we get around this?

My first thought was something along the following lines:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X> {
private T x;

public Builder() { x = createX(); }
public Builder<T> withFoo( int foo ) {
x.foo = foo; return this;
}
public Builder<T> withBar( int bar ) {
x.bar = bar; return this;
}
public T build() { return x; }
protected abstract T createX();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder extends X.Builder<Y> {
public Builder withBaz( int baz ) {
y.baz = baz; return this;
}
protected Y createX() { return new Y(); }
}

protected Y() {}

public int getBaz() { return baz; }
}

The only trouble with that is that the following fails to compile:

Y y = new Y.Builder().withFoo( 1 ).withBaz( 3 ).build();

Why? Because withFoo returns a Builder<Y>, not a Y.Builder; and the withBaz method is on the latter, not the former.

So...the code I actually wrote looked like this:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X,
B extends Builder<T, B>> {

private T obj;

public Builder() { obj = createObj(); }
public B withFoo( int foo ) {
obj.foo = foo; return this;
}
public B withBar( int bar ) {
obj.bar = bar; return this;
}
public T build() { return built; }
protected abstract T createObj();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder
extends X.Builder<Y, Y.Builder> {

public Builder withBaz( int baz ) {
obj.baz = baz; return this;
}
protected Y createObj() { return new Y(); }
}

protected Y() {}

public int getBaz() { return baz; }
}

Now the return types are correct...but the withFoo and withBar methods won't compile. The trouble is that this inside X.Builder<T, B> is of type X.Builder<T, B>, not of type B. Actually, at runtime, the builder class should indeed be of type B, but it would be inelegant and type-unsafe to cast this to B everywhere.

Happily, there is a solution that doesn't involve casting. This is the final version of the code:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X,
B extends Builder<T, B>> {

private T obj;
private B thisObj;

public Builder() {
obj = createObj(); thisObj = getThis();
}
public B withFoo( int foo ) {
obj.foo = foo; return thisObj;
}
public B withBar( int bar ) {
obj.bar = bar; return thisObj;
}
public T build() { return built; }
protected abstract T createObj();
protected abstract B getThis();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder
extends X.Builder<Y, Y.Builder> {

public Builder withBaz( int baz ) {
obj.baz = baz; return thisObj;
}
protected Y createObj() { return new Y(); }
protected Builder getThis() { return this; }
}

protected Y() {}

public int getBaz() { return baz; }
}

We added the getThis() method, which is always implemented as return this; in each subclass. This ensures that the Builder subclass is in fact the B type parameter to X.Builder<T, B>.

So, at the price of a small wart (the getThis() method plus associated instance variable), we've got a solution that maintains type safety, removes duplication, and allows all the code completion goodness that your IDE of choice may offer. Win!

28 comments:

eric said...

Great post, Eric. For a moment, I wondered why bother with the Builder in this case, when you could just make the mutator methods fluent. So you would have

X x = new X().setFoo(foo).setBar(bar);

On the other hand, this wouldn't be acceptable for purely initialization parameters that should be immutable.

Thanks for posting this.

Alexei Udalov said...

Hello it's wonderfull post.
But what a variable built
in public T build() { return built; } method?

Gabrielle Anderson said...

I've extended this to a multi-class hierarchy at https://onelostlogician.wordpress.com/2016/10/10/inheritance-generics-and-builders/ (though it does use some minimal casting).

aris said...

Well written post. Only a question about the implementation of getThis() in each subclass.

I've tried your solution and if we declare in the abstract class

B getThis() { return (B) this }

then it works without the need to implement it in each subclass.
What do you think about this idea?
Thanks
Stefano

shasthika said...

perfect explanation about java programming .its very useful.thanks for your valuable information.best java institute in chennai | best java training in velachery

JohnSmith said...

Hi, your article really nice. That is so logical and clearly explained. Keep it up! I follow up your blog for the future post.
Regards,
Java Online Training | Java Online Training in India | Java Online Training India | Java Online Training in Hyderabad | Java Online Training Hyderabad | Java Training in Hyderabad | Java Training in India | Java Training Institutes in India | Java Training Institutes in Hyderabad | Java Course in Hyderabad | Java Training | Learn Java Online | Online Java Training | Best Java online Training Institutes in Hyderabad | Best Java Training Institutes in Hyderabad | Best Institutes for Java | Java Institutes in Hyderabad | Best Institutes for Java in Hyderabad | Learn Java | Java Training Institutes in Hyderabad | Java Certification | Java Certification Training | Java Certification Training in Hyderabad | Java Certification Training in India

Mary Brown said...

Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Java developer learn from Java Training in Chennai. or learn thru Java Online Training India . Nowadays Java has tons of job opportunities on various vertical industry.

Dipanwita said...

Inheritance is a useful feature of OOP. It helps to know how to use it well. java training in chennai

johnsy sai said...

Existing without the answers to the difficulties you’ve sorted out through this guide is a critical case, as well as the kind which could have badly affected my entire career if I had not discovered your website.
Digital Marketing online training

full stack developer training in pune

full stack developer training in annanagar

full stack developer training in tambaram

full stack developer training in velachery










Mouni yoga said...

Thank you for sharing such great information with us. I really appreciate everything that you’ve done here and am glad to know that you really care about the world that we live in
Python training in marathahalli
AWS Training in chennai

AWS Training in bangalore

aruna raj said...

I believe there are many more pleasurable opportunities ahead for individuals that looked at your site.
Blueprism training in btm

Blueprism online training

AWS Training in chennai

Nila shri said...

I found your blog while searching for the updates, I am happy to be here. Very useful content and also easily understandable providing.. Believe me I did wrote an post about tutorials for beginners with reference of your blog. 
Data Science training in Chennai
Data science training in bangalore
Data science online training
Data science training in pune

Sugi Bala said...

The post is written in very a good manner and it entails many useful information for me. I am happy to find your distinguished way of writing the post. Now you make it easy for me to understand and implement the concept.
java training in omr | oracle training in chennai

java training in annanagar | java training in chennai

Genga Savitha said...

It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me...

angularjs Training in chennai
angularjs-Training in pune

angularjs-Training in chennai

angularjs Training in chennai

angularjs-Training in tambaram

pavithra dass said...

Thank you for sharing such great information with us. I really appreciate everything that you’ve done here and am glad to know that you really care about the world that we live in.
SEO training course
Best SEO training in chennai
SEO Training Center in Chennai
SEO Institutes in Chennai
SEO Course Chennai
SEO Training near me

Revathy A said...

I have read a few of the articles on your website now, and I really like your style of blogging. I added it to my favourites blog site list and will be checking back soon.

angularjs interview questions and answers

angularjs Training in bangalore

angularjs Training in bangalore

angularjs online Training

angularjs Training in marathahalli

angularjs interview questions and answers

janani said...

Awesome..You have clearly explained …Its very useful for me to know about new things..Keep on blogging..
Java interview questions and answers

Java training in Chennai | Java training institute in Chennai | Java course in Chennai

Java training in Bangalore | Java training institute in Bangalore | Java course in Bangalore

thulasi ragini said...

The post is written in very a good manner and it entails many useful information for me. I am happy to find your distinguished way of writing the post. Now you make it easy for me to understand and implement the concept.
Python training in bangalore
Python course in pune
Python training in bangalore

kevin antony said...

This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.

rpa training in chennai
rpa training in bangalore
rpa course in bangalore
best rpa training in bangalore
rpa online training

Ram priya said...

Have you been thinking about the power sources and the tiles whom use blocks I wanted to thank you for this great read!! I definitely enjoyed every little bit of it and I have you bookmarked to check out the new stuff you post
Data Science training in Chennai | Data Science Training Institute in Chennai
Data science training in Bangalore | Data Science Training institute in Bangalore
Data science training in pune | Data Science training institute in Pune
Data science online training | online Data Science certification Training-Gangboard
Data Science Interview questions and answers

johnsy sai said...

The knowledge of technology you have been sharing thorough this post is very much helpful to develop new idea. here by i also want to share this.
Best Devops Training in pune
excel advanced excel training in bangalore
Devops Training in Chennai

vijay antony said...

I am really happy with your blog because your article is very unique and powerful for new reader.
Click here:
selenium training in chennai | selenium course in chennai
selenium training in bangalore | selenium course in bangalore
selenium training in Pune | selenium course in pune | selenium class in pune
selenium training in Pune | selenium course in pune | selenium class in pune
selenium online training | selenium training online | online training on selenium

pavithra dass said...

This post is much helpful for us. This is really very massive value to all the readers and it will be the only reason for the post to get popular with great authority.
Java J2ee Training in Chennai
German Training Institute in Chennai
german language coaching centres in chennai
Java Coaching Center in Chennai
Best Java Training in Chennai
German Training Centers in Chennai

Ananya Krishnan said...

Good job in presenting the correct content with the clear explanation. The content looks real with valid information. Good Work

DevOps is currently a popular model currently organizations all over the world moving towards to it. Your post gave a clear idea about knowing the DevOps model and its importance.

Good to learn about DevOps at this time.


devops training in chennai | devops training in chennai with placement | devops training in chennai omr | devops training in velachery | devops training in chennai tambaram | devops institutes in chennai | devops certification in chennai | trending technologies list 2018

Anbarasan14 said...

Excellant blog!!! I got to know more usefyl information by reading your blog. Thanks for posting this blog.

TOEFL Classes in Chennai
Best TOEFL Classes in Chennai
TOEFL in Chennai
Best TOEFL Class in Chennai
TOEFL Training Center in Chennai
TOEFL Coaching near me
TOEFL Training in Chennai

sandhiya arav said...

I am really enjoying reading your well written articles.
It looks like you spend a lot of effort and time on your blog.Keep Doing.
Digital Marketing Training in Bangalore
Digital Darketing Courses in Bangalore
Best Digital Marketing Courses in Bangalore
German Language Institute in Bangalore
German Speaking Classes in Bangalore
German Language in Bangalore

Safety Professionals said...

can you offer guest writers to write content for you? I wouldn’t mind producing a post or elaborating on some the subjects you write concerning here. Again, awesome weblog!
safety course in chennai

Aruna Ram said...

Great idea! Thank you for your wonderful post and very easily understand to me. Really good work please keeping...
Web Development Courses in Bangalore
Web Development Training in Bangalore
Web Designing Course in Tnagar
Web Designing Training in Saidapet
Web Designing Training in Omr
Web Designing Course in Navalur