If you’re like me, a .NET developer, and you occasionally have to build-up complex objects, then you’ve probably tried one or more of the creational design patterns to isolate that builder code from the rest of your project. Design patterns are great for that sort of thing but one big drawback I’ve noticed is that most of them don’t lend themselves to reuse at all. I mean, at all. For instance, I like the Builder pattern, but while using it I’ve often found myself writing the same Director and ConcreteBuilder classes, over and over again. The only thing that ever changes are the class names, the method signatures, and, of course, the actual builder code.
I’ve often thought that a reusable Builder class would be awesome but I also knew that the design would have to differ from the GOF Builder, since that approach places all the construction knowledge inside the ConcreteBuilder class. I also knew, from my own experiences, that a good reusable Builder would need to be capable of sharing resources at build time – things like repositories, files, or even other objects. Finally, I thought that a good reusable Builder design should inherently prevent a Builder from becoming tightly coupled to a Product through that Product’s configuration requirements.
I looked around the Interwebz and quickly found a pile of really good Builder designs. Almost all of them were based on the classic GOF Patterns. Some of them had features I liked but none of them were put together in a way that made reuse possible. A few were even generic but as I looked closer I realized that, at most, they were simply sharing a few method signatures, not actually reusing any implementation.
I decided to make my own but that left me wondering what I really needed in a reusable Builder abstraction. I turned to my work designing NUGET packages for inspiration. Many of my NUGET packages expose a public API that’s really nothing more than a big, complex, C# object. Because of that fact, I already used builders all over the place.
After some careful consideration, using my own history as a NUGET designer for guidance, I decided that a reusable Builder should employ a Provider abstraction for breaking down complicated construction recipes into manageable chunks, and also for making the sharing of resources easier. I further decided that a Builder should use a separate abstraction for configuring those Providers, so the Builder itself wouldn’t become tightly coupled to it’s corresponding Product – a phenomenon I’d witnessed in my own projects. I decided to name that abstraction a “Setup”, since the word “Configuration” already had so many other connotations.
So, that meant I was going to create a reusable, possibly generic Builder, that would actually reuse it’s construction logic between types. This Builder would defer to Provider objects so that it wouldn’t need to know everything about how to create all possible types, and also to make the Builder itself more extensible. It would use a Setup object for each Provider so the configuration process would remain clean, no matter how many Providers were needed to construct a given Product type.
I also knew that I wanted to be able to stack Builder objects in order to build complex objects that were themselves comprised of other, complex objects. No matter how deep that rabbit hole went, I knew that I still wanted to be able to configure everything from the topmost Builder, in a way that wouldn’t create overlapping sets of configuration properties, or compromises between Provider with similar configuration needs.
Finally, I knew that I might want to allow for lists of child Providers, for scenarios where a Provider might optionally use extended logic or resources at runtime, such as optionally encrypting data before writing it.
The design I came up with is shown in the diagram below:
I’ll quickly define the various interfaces:
* IBuilderProduct – is an interface that defines the Product type(s) for my Builder. Just like in a GOF Builder, I use the term Product to mean anything constructed by my builder. In my builder though, all Product(s) share one feature: a collection of IBuilderProvider objects.
* IBuilder – is an interface that defines the behavior of my builder. It contains a single Builder method, that returns an IBuilderProduct reference. It also contains a collection of IBuilderSetup objects that define the configuration for any Provider objects, to be created along the way.
* IBuilderProvider – is an interface that serves as a placeholder for any Provider objects used by my Builder, at build time.
* IBuilderSetup – is an interface that defines the configuration settings needed to build a Provider instance. Setups are also builders themselves, in that they construct Provider instances at build time – hence the need for there to be an association between a Setup and a Provider.
The diagram also demonstrates that the Builder contains a collection of zero or more Setup objects, that should be added to the Builder before it’s Build method is called. Each one of those Setup objects is also a Builder in it’s own right, and creates a Provider instance, at build time, that is then injected into the Product instance, before the Product is returned from the Builder.
Setups have a collection of child Setups, so, each Setup is also responsible for ensuring that any corresponding Providers are created for it’s children.
Providers also have a collection of child Providers, which is used by the corresponding Setup to ensure that every Setup has a matching Provider at the end of the Build process.
Product’s can be anything, really, the only constraint being that type must implement the IBuilderProduct interface so that the Builder has a place to put the collection of Providers. That collection of Providers is what makes the Builder itself reusable, since the Product actually builds itself by utilizing the collection of Providers for algorithms, resources, repositories and so forth.
The end result is a Builder that’s actually reusable and extensible. I’ve used the design to make my CG.Builder NUGET package, and I’ve used that package to create complex objects in a variety of other CODEGATOR NUGET packages. In fact, I’ve yet to run into any construction scenarios that I couldn’t handle with this reusable library.
So, how does the Builder do what it does? I’ll explain that in the next article, so check back.