I’m in the process of reworking an open-source library to make it operate more easily for my use cases. The writer of the library has a particular view of how software should be, and it will not co-exist with the sort of design patterns I’d like to use.
I’d like to think that this is a question of too many valid opinions, rather than particularly invalid ones. Put another way, there are some particular black spots that both variations of the design are trying to avoid. The question is which are the least bad?
Principle 1: Inheritance is Worse Than Composition
Generally speaking, having a class for each variation of an object soon gets old. It’s better to have the ability to compose an object with properties that come together to produce the end result, rather than define a class which represents each permutation.
If you’re a functional programmer you might even say that having a parent function to just weave together the components of the desired configuration is a good idea too.
Principle 2: Convention beats Configuration
Given there are a few things people just want to do with a library, why not have a facade that hides the dirty implementation details, and just provides some helper functions that do the things.
Principle 3: You Can’t Cover Every Use Case
This is the undoing of principle 2…
If you can’t cover every use case, then you need people to be able to easily weave their configurations together.
Principle 4: You Really Can’t Predict Execution Control
People have use cases which demand different ways of sequencing actions. This is why JUnit4 rules are so hard to use out of context – JUnit 4 dictates a particular way a rule must integrate itself and it is very inflexible.
Trading these Off
I won’t dwell on the alternatives, but will instead look at some decisions I’ve taken that sort of solve the problems.
- A class with a default constructor can be a case of configuration over convention – base classes that allow for lots of permutations by composition can be subclassed to make a good default
- Mutable objects allow easier composition of alternatives, especially with fluent setters
- Copy-on-write fluent setters are also fine, but can lead to some issues in terms of sequencing execution – if the object you started with is not the one you end up with, then framework code may have been speaking to the wrong object
- Providing both a template method for execution AND the ability to sequence your own execution, with safeguards against it going wrong, is most flexible
- When there’s the possibility of mixing multiple techniques together, having a helper to compose a collection of tools into a single execution avoids having to create all permutations up front
So in short, I ended up with more classes, and fewer facade magic methods. I gave each class both a template method AND manual controls.
I also made a few tiny subclasses whose only job was to put a default constructor on top of something.
More to follow when this gets as far as release.
Opinions expressed by Java Code Geeks contributors are their own.