In nature, out of every possible arrangement of several elements, only a few arrangements are stable. This is illustrated with atoms combined together, or smaller particles arranged together into atoms, where not every combination is sustainable.
Unstable arrangements tend to move toward stable ones over time. Whenever you observe the elements, you mostly see stable arrangements of them. Because there is only a relatively small number of stable arrangements, a brain can be trained to recognize them, and they can even be named and incorporated into the language.
Better with a brain
The capability to recognize common arrangements of elements is beneficial because it saves a lot of time and thinking. Rather than describing in the details each arrangement each time, it is therefore very economical -cheaper- to describe each stable arrangement once, and then to declare that such arrangement happens in this or that case. Saying: “this is an equilateral triangle” is times more efficient than explaining what it is: “there are 3 lines of the same length, each connected to the two others such as they form a closed path, etc.” It also enables thinking at a higher level.
In software development
In software development, the usual elements are classes, interfaces, methods, fields, associations (implementation, delegation, instantiation) and various constraints between them. Given a few of these elements we can form many possible arrangements, however only a relatively small number of them is useful and of interest. This happens because the useless arrangements tend to be quickly replaced by the skill-full developer into other that are more useful. For example, any arrangement of two distinct classes that depend to each other, forming a cycle, is usually not desirable. Patterns authors have been working for almost two decades to inventory as many useful arrangements as they could find, resulting into many books and publications in every domain.
Common and stable arrangements of two to three classes together form the basis for design patterns as in the GoF book, an idea I have experimented in a previous post: systematic combination of subpatterns generates design patterns.
Common stable arrangements of methods, fields and how they relate with each other within one class are simply stereotypes of classes, which we tend to call patterns anyway like the ValueObject pattern in the Domain Driven Design book.
Note that in this discussion we are focusing on arrangements of programming elements in the solution space, not in the problem space, but pattern express intents too.
Harnessing stable arrangements of things: toward more efficient tools
I believe that making explicit the use of predefined common stable arrangements of programming elements, in the coding process, can boost the efficiency of many tools. I also believe that such common and stable arrangements of programming elements have already been identified and are already well-documented in the existing pattern literature.
Rather than configuring tools at the programming element level (class, field, method etc.), if the code is augmented with explicit declarations of the patterns used, the tools can then be configured at the pattern level. For each tool, the idea is to prepare in advance how to configure it for each supported pattern. This preparation must be automated, so that given an occurrence of a known pattern in the code base, the configuration of the tool can be automatically derived from the particular details of the pattern occurrence.
In other words:
tool configuration= auto-configuration(pattern, tool) + pattern occurrence
A simple case study
To start with an example, let us consider the pattern Abstract Factory, that defines an Abstract Factory interface and one or more Product interface(s). Then assume that in our code base we have an occurrence of this pattern, where the interface WidgetFactory participates as the Abstract Factory, and the interfaces Window and Button participate as Product. Concrete classes form two families, one Linux family (LinuxWidgetFactory, LinuxWindow, LinuxButton) and one Mac family (MacWidgetFactory, MacWindow, MacButton), where each concrete class participates as ConcreteFactory, ConcreteProduct and ConcreteProduct respectively.
Dependencies restrictions (à la Jdepend + Junit)
The auto-configuration(AbstractFactory pattern, dependency checker tool) could be programmed like the following:
//Given a pattern occurrence from the actual base: occ //Factory interface knows about the Product interface, not the other way round For each Product in occ, add constraint (Product, Must not depend upon, AbstractFactory) //ConcreteProduct must not know about the AbstractFactory For each ConcreteProduct participant in occ, add constraint (ConcreteProduct, Must not depend upon, AbstractFactory) //ConcreteProduct must not know about the ConcreteFactory For each ConcreteProduct participant in occ, add constraint (ConcreteProduct, Must not depend upon, ConcreteFactory) //Interfaces must not depend upon their implementor For each abstract participant in occ, add constraint (participant, Must not depend upon, implementor of participant)
I have expanded the auto-configuration script to highlight how we can do more sophisticated configuration as soon as it is supposed to be reused many times, something we would never afford for one-shot configuration. Also in the above script, it is quite obvious that we can extract more powerful primitives to simplify the declaration of the script itself.
I have already presented this idea: toward smarter dependency constraints (patterns to the rescue).
Dependency injection (à la Spring)
The auto-configuration(AbstractFactory pattern, IoC container) could be programmed like the following:
//Given a pattern occurrence from the actual base: occ //Given the selected family (Mac or Linux) we want to inject: F //Bind the ConcreteFactory from F to the AbstractFactory interface For the AbstractFactory participant in occ, and for the ConcreteFactory in occ that belongs to F, bind (ConcreteFactory, AbstractFactory) //Bind each ConcreteProduct from F to the Product interface For each ConcreteProduct in occ that belongs to F, bind (ConcreteProduct, corresponding Product)
Again we can see that we can automate the binding of each implementation to its corresponding interface from the single explicit selection of the family F we want to inject. This is a great way to factor out dependency injection to make it more manageable. In particular, the configuration is closer to the way we actually think.
The same idea can be applied for many other tools, in particular:
- Properties checking, static or via unit test partial auto-generation (immutability, identity, side-effect) (à la PMD, an idea I have presented: patterns as another kind of types)
- Packaging and modules management (à la Maven)
- Auto-documentation (à la Graphviz or UMLGraph, also an idea I have experimented: documenting the design of software using patterns)
In this post I described how it makes sense to consider overall arrangements of programming elements as higher-level constructs, which I identify with patterns (the solution part of patterns in fact). I emphasize the fact that the useful arrangements are not that numerous, and that many of them are already documented in the pattern literature. Finally I present how such arrangements or patterns, if declared explicitly in the code, can be leveraged to automate tools configuration in a powerful way.
As usual, any feedback is highly welcome.