A process and a thing
Christopher Alexander did not consider a building as finished so much as being an attempt, an approximation of an unattainable perfection[TTWoB79]. Every building had an age and a state of repair. The older a building became, the more likely it needed small repairs or an extension to match its new usage. But, for him, even a new but incomplete building was in a state of repair.
Rigid building designs are brittle and not very likely to survive changing times. They don’t allow for adaptation. Most modern buildings are like this. Consider the different physical space and connectivity needs of offices over the last few decades. In contrast, there was a suppleness to all of Christopher Alexander’s designs. He built each building, component, extension, or improvement so they sat comfortably with what was already there and remained conscious of the need to support an unseen future, greater form.
The same value in malleability can be said of code. Old code is only good if it can be repaired when it is no longer fit for purpose. Christopher Alexander wrote[NoO1-01] negatively about buildings with no scope for extension or capacity to adapt. Compositions that can only be put back into their final state were not living but were arrangements brought into existence by a dead process.
… when you build a thing you cannot merely build that thing in isolation, but must also repair the world around it, and within it, so that the larger world at that one place becomes more coherent, and more whole; and the thing which you make takes its place in the web of nature, as you make it.
— Christopher Alexander,
A Pattern Language [APL77], p. xiii.
If code cannot be changed, it’s not good code. If you can only make it work by shoring it up so it does not need to change while everything else works around its failings, then it’s still not good code. More to the point, it was never good code.
Patterns are about solving a problem, but they also contain the process of achieving a solution. As with wisdom, we see a reason behind this. Complex things built through traditional methods were always constructed piece-wise. No successful, large, complex thing was ever made in one step. My feelings resonated with the words of John Gall when I read:
A COMPLEX SYSTEM THAT WORKS IS INVARIABLY FOUND TO HAVE EVOLVED FROM A SIMPLE SYSTEM THAT WORKED.
The parallel proposition also appears to be true:
A COMPLEX SYSTEM DESIGNED FROM SCRATCH NEVER WORKS AND CANNOT BE PATCHED UP TO MAKE IT WORK. YOU HAVE TO START OVER, BEGINNING WITH A WORKING SIMPLE SYSTEM.
— John Gall,
The Systems Bible [SysBible02], p. 63.
Complex things at scale are only successfully made with traditional methods when built piece by piece. Because steps are required to build well, patterns must also be processes. They are the wise steps to take to achieve a new, better state, with each state being a distinct solution to an expanding problem.
The thing aspect of this has to do with an essential completeness of all
patterns. A whole thing in one notable entity means the pattern describes a
complete system of elements supporting each other. The system can be tiny, but
it has to be complete and self-supporting. As an example, by negation, we can take an
element away. The pattern of
Wholeness means a whole thing in one entity and repairing it to a wholesome state. At each step, the entire thing is good—or at least not worse than before. The south-facing wall has a usable seat, for example. And when we see something as incomplete, it’s not healthy. It needs repair. If we plan to add a shape to the building but it would introduce a shadow to the outdoors, we can recognise that and adjust the plan to fix it before it happens, just as tests tell us our proposed change is not good.
Many things will be unstable when we work on a project, but we can decide to work on those tasks that close the most significant gaps. We can look at the needs of the whole project and tackle the highest-impact decisions first. This way, we start with nothing and repair the project into something we are happy with. At each stage, we make it better, more alive, more whole.
Even though design patterns are processes, they regularly explain themselves by describing an outcome. The manipulations we need to apply to get to that outcome from our existing context would be much more helpful documentation. We can look at how a building plot turns into a building over time with design patterns. Residents change. Their needs change as they go through stages of life, both temporary and enduring. The building will no longer be a perfect fit for their changed needs. New patterns will become appropriate as new forces appear, so the building should be repaired into a new state. In software, we see this with changes caused by revelation or modifications required to meet new expectations of users becoming accustomed to features available in other, related software.
At the detail level, we may migrate to using a
The context for the
The nominalisation step is the transformation—the change we wish to make to the environment to fix it. It is a process of repair.
Taking the plot of land for a house in the northern hemisphere and splitting it such that the house is at the north end so the south-facing wall can have a semi-private, well-lit open area around it is also a process of repair to the current design. It is the right step to take:
- for the purpose of the land
- in the context of benefiting from good light when outside the property but still on the land beside the property
- to use the land well, to its fullest
- for a northern hemisphere property
Given context and forces, nominalising a function into an object can be the
right step. The
The
Because the code used by the authors at the time did not have the sophisticated
mechanisms available to modern languages, they did not recognise the pattern
for what it was. In effect, the
Some people use
class MyClass:
#...
def configure(self, new_strategy):
self.do_it = new_strategy
def do_thing(self):
self.do_it(self.context_a, self.context_b)
And if you are using C++11 or beyond, you can do this,
class MyClass {
public:
using Func=std::function<void(const TypeA&, const TypeB&)>;
void configure(Func new_strategy) {
m_do_it = new_strategy;
}
void do_thing() const {
m_do_it(m_contextA, m_contextB);
}
private:
Func m_do_it;
};
Snippet link: https://godbolt.org/z/rd41o1ssT
And even if you are using a language without these features, there are still workable solutions. You can achieve it in C, which almost proves this was never an object-oriented pattern in the first place.
struct MyStruct {
void(*do_it)(int, int);
int m_contextA;
int m_contextB;
};
void configure(MyStruct *ms, void(*new_strategy)(int, int)) {
ms->do_it = new_strategy;
}
void do_thing(MyStruct *ms) {
ms->do_it(ms->m_contextA, ms->m_contextB);
}
Snippet link: https://godbolt.org/z/MfMoxTh5T
So, the essence of the pattern (introduce a family of algorithms that can be selected from at runtime) is maintained in these examples, even though each of these implementations is utterly different from anything in the GoF book.