I have been greatly influenced by Joshua Bloch's Effective Java book (2nd edition), probably more so than with any programming book I've read. In particular, his Builder Pattern (item 2) has had the greatest effect.
Despite Bloch's builder getting me much farther in the couple of months than in my past ten years of programming, I am still finding myself hitting the same wall: Extending classes with self-returning method-chains is at best discouraging, and at worst a nightmare–especially when generics come into play, and especially with self-referential generics (such as Comparable<T extends Comparable<T>>
).
There are two primary needs that I have, only the second of which I'd like to focus on in this question:
-
The first problem is "how to share self-returning method chains, without having to re-implement them in every…single…class?" For those who may be curious, I've addressed this part at the bottom of this answer-post, but it's not what I want to focus on here.
-
The second problem, which I am asking for comment on, is "how can I implement a builder in classes that are themselves intended to be extended by many other classes?" Extending a class with a builder is naturally more difficult than extending one without. Extending a class that has a builder that also implements
Needable
, and therefore has significant generics associated to it, is unwieldy.
So that is my question: How can I improve upon (what I call) the Bloch Builder, so I can feel free to attach a builder to any class–even when that class is meant to be a "base class" that may be extended and sub-extended many times over–without discouraging my future-self, or users of my library, because of the extra baggage the builder (and its potential generics) impose on them?
Addendum
My question focuses on part 2 above, but I wanted to elaborate a bit on problem one, including how I've dealt with it:
The first problem is "how to share self-returning method chains, without having to re-implement them in every…single…class?" This is not to prevent extending classes from having to re-implement these chains, which, of course, they must–rather, how to prevent non-sub-classes, that want to take advantage of these method chains, from having to re-implement every self-returning function in order for their users to be able to take advantage of them? For this I've come up with a needer-needable design that I'll print the interface skeletons for here, and leave it at that for now. It has worked well for me (this design was years in the making…the hardest part was avoiding circular dependencies):
public interface Chainable {
Chainable chainID(boolean b_setStatic, Object o_id);
Object getChainID();
Object getStaticChainID();
}
public interface Needable<O,R extends Needer> extends Chainable {
boolean isAvailableToNeeder();
Needable<O,R> startConfigReturnNeedable(R n_eeder);
R getActiveNeeder();
boolean isNeededUsable();
R endCfg();
}
public interface Needer {
void startConfig(Class<?> cls_needed);
boolean isConfigActive();
Class getNeededType();
void neeadableSetsNeeded(Object o_fullyConfigured);
}
Best Answer
I have created what, for me, is a big improvement over Josh Bloch's Builder Pattern. Not to say in any way that it is "better", just that in a very specific situation, it does provide some advantages--the biggest being that it decouples the builder from its to-be-built class.
I have thoroughly documented this alternative below, which I call the Blind Builder Pattern.
Design Pattern: Blind Builder
As an alternative to Joshua Bloch's Builder Pattern (item 2 in Effective Java, 2nd edition), I have created what I call the "Blind Builder Pattern", which shares many of the benefits of the Bloch Builder and, aside from a single character, is used in exactly the same way. Blind Builders have the advantage of
ToBeBuilt
class to be extended without having to extend its builder.In this documentation, I'll refer to the class-being-built as the "
ToBeBuilt
" class.A class implemented with a Bloch Builder
A Bloch Builder is a
public static class
contained inside the class that it builds. An example:Instantiating a class with a Bloch Builder
The same class, implemented as a Blind Builder
There are three parts to a Blind Builder, each of which is in a separate source-code file:
ToBeBuilt
class (in this example:UserConfig
)Fieldable
" interface1. The to-be-built class
The to-be-built class accepts its
Fieldable
interface as its only constructor parameter. The constructor sets all internal fields from it, and validates each. Most importantly, thisToBeBuilt
class has no knowledge of its builder.As noted by one smart commenter (who inexplicably deleted their answer), if the
ToBeBuilt
class also implements itsFieldable
, its one-and-only constructor can be used as both its primary and copy constructor (a disadvantage is that fields are always validated, even though it is known that the fields in the originalToBeBuilt
are valid).2. The "
Fieldable
" interfaceThe fieldable interface is the "bridge" between the
ToBeBuilt
class and its builder, defining all fields necessary to build the object. This interface is required by theToBeBuilt
classes constructor, and is implemented by the builder. Since this interface may be implemented by classes other than the builder, any class may easily instantiate theToBeBuilt
class, without being forced to use its builder. This also makes it easier to extend theToBeBuilt
class, when extending its builder is not desirable or necessary.As described in a below section, I do not document the functions in this interface at all.
3. The builder
The builder implements the
Fieldable
class. It does no validation at all, and to emphasize this fact, all of its fields are public and mutable. While this public accessibility is not a requirement, I prefer and recommend it, because it re-enforces the fact that validation does not occur until theToBeBuilt
's constructor is called. This is important, because it is possible for another thread to manipulate the builder further, before it is passed to theToBeBuilt
's constructor. The only way to guarantee the fields are valid--assuming the builder cannot somehow "lock" its state--is for theToBeBuilt
class to do the final check.Finally, as with the
Fieldable
interface, I do not document any of its getters.Instantiating a class with a Blind Builder
The only difference is "
UserConfig_Cfg
" instead of "UserConfig.Cfg
"Notes
Disadvantages:
ToBeBuilt
class,Compiling a Blind Builder is straight-forward:
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
The
Fieldable
interface is entirely optionalFor a
ToBeBuilt
class with few required fields--such as thisUserConfig
example class, the constructor could simply beAnd called in the builder with
Or even by eliminating the getters (in the builder) altogether:
By passing fields directly, the
ToBeBuilt
class is just as "blind" (unaware of its builder) as it is with theFieldable
interface. However, forToBeBuilt
classes which and are intended to be "extended and sub-extended many times" (which is in the title of this post), any changes to any field necessitates changes in every sub-class, in every builder andToBeBuilt
constructor. As the number of fields and sub-classes increases, this becomes impractical to maintain.(Indeed, with few necessary fields, using a builder at all may be overkill. For those interested, here is a sampling of some of the larger Fieldable interfaces in my personal library.)
Secondary classes in sub-package
I choose to have all builder and the
Fieldable
classes, for all Blind Builders, in a sub-package of theirToBeBuilt
class. The sub-package is always named "z
". This prevents these secondary classes from cluttering up the JavaDoc package list. For examplelibrary.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
Validation example
As mentioned above, all validation occurs in the
ToBeBuilt
's constructor. Here is the constructor again with example validation code:Documenting Builders
This section is applicable to both Bloch Builders and Blind Builders. It demonstrates how I document the classes in this design, making setters (in the builder) and their getters (in the
ToBeBuilt
class) directly cross-referenced to each other--with a single mouse-click, and without the user needing to know where those functions actually reside--and without the developer having to document anything redundantly.Getters: In the
ToBeBuilt
classes onlyGetters are documented only in the
ToBeBuilt
class. The equivalent getters both in the_Fieldable
and_Cfg
classes are ignored. I don't document them at all.The first
@see
is a link to its setter, which is in the builder class.Setters: In the builder-class
The setter is documented as if it is in the
ToBeBuilt
class, and also as if it does the validation (which really is done by theToBeBuilt
's constructor). The asterisk ("*
") is a visual clue indicating that the link's target is in another class.Further information
Why should a builder be an inner class instead of in its own class file?
Benefit of using static inner builder class
Putting it all together: The full source of the Blind Builder example, with complete documentation
UserConfig.java
UserConfig_Fieldable.java
UserConfig_Cfg.java