The topic of this post is covariant return types in the context of the builder pattern and how you can use the builder pattern with inheritance in Java. To illustrate what the concept of covariant return types is, consider the following example (roughly taken from Wikipedia):
Here the D class overrides the foo method of C, but it doesn't return the same type (B versus A). Since B is a subclass of A, however, this satisfies the property of the return type being covariant, as we are still guaranteed that the return type of foo is "an A." So why is this useful? Suppose you have a Resource class. The builder pattern gives you some nice syntax like this to chain methods together when constructing an object:
It's especially useful when the list of fields is large and you often only want to assign a subset of them. In that case constructors don't cut it (you'd need exponentially many of them to cover all cases...) so the builder pattern provides a convenient alternative that avoids having to pass in a bunch of null values. The key to making this work is having the setters return the "this object" and thus enable chaining. Simple enough, but when you introduce inheritance it gets a bit tricky.
Now we have a subclass ExpiringResource which is just a Resource with an expiration time. Although this code looks natural, it doesn't compile because the withName method returns a Resource and not an ExpiringResource, which doesn't have the withExpirationTime method. Fortunately, we can leverage covariant return types to make this work:
We are overriding the withName method so that it returns an ExpiringResource instead of a normal Resource (a covariant return type). Then we can successfully chain the methods together and make our example compile. It's not shown here, but you'd typically do the same with the other two methods on Resource as well. For these simple setters, it's a bit unfortunate that we have to add so much code in the subclasses, but you can imagine if those methods had other side effects or validation that it would be worthwhile to share that code. To wrap up, Java's support for covariant return types allows us to use the builder pattern while maintaining a sensible inheritance hierarchy.
No comments:
Post a Comment