Not all participants in a pattern are equal, some are aware there is a pattern, while other participants do not, cannot and must not. Let us have a look at that principle and its consequences for coupling and dependencies management.
Some patterns are intrusive, which means that adding them to an existing code base requires changing the code base. Fortunately many patterns are not intrusive, and given an existing code base, introducing such patterns only requires the addition of specific code, more precisely the addition of the pattern participants that are dedicated to the pattern.
However in the patterns books, each pattern defines many participants, many of which being actually already in the existing code, and in the case of non-intrusive patterns they do not have to be changed to play their role in the patterns: these participants are therefore ignorant participants, they participate without knowing. Participants that are not ignorant are called dedicated participants.
If we were now to remove the pattern occurrence, we would just delete the dedicated participants and leave the ignorant participants intact. This is what I call the Pattern Participants Ignorance principle (PPIP):
In a pattern occurrence, every participant that would remain after we completely removed the pattern occurrence from the code must NOT be aware of the pattern, and is called an ignorant participant; adding or removing the pattern to the code must not change a single line of the source code of these participants. Participants that are not ignorant are called dedicated participants
If the source code is in source control, adding or removing a pattern should never involve any commit to the source of any ignorant participant. If the source code is not available, we could still introduce the patterns. In simpler words, ignorant participants are passive participants, whereas dedicated participants only exist to support the pattern.
By definition, every ignorant participant should not be aware of the pattern and this implies in particular that they should not be aware of any dedicated participant.
This works well for many patterns, where every participant is ignorant but a few:
- Decorator: the participants Decorator and Concrete Decorator are the only dedicated participants
- Proxy: the participant Proxy is the only dedicated participant (more code can actually be also dedicated, for instance to realize a remote proxy)
- Adapter: the participant Adapter is the only dedicated participant
- Composite: the participant Composite is the only dedicated participant
- Null Object: the Null Object itself is the only dedicated participant (of course)
There are patterns that do require modifications to the existing source code, they are intrusive. Intrusive patterns violate Participant Ignorance since ignorant participants must know a little about the pattern because they are modified to support it.
Some patterns are almost non-intrusive except for the calling code (Client participant) that need to be changed a bit:
- Abstract Factory: the Abstract Factory and Concrete Factory are the only dedicated participants, but the calling code must call the new factory; however the Product and Concrete Product participant are ignorant
- Builder: the Builder and Concrete Builder are the only dedicated participants, but the calling code must call the new builder; however the Product and Concrete Product participant are ignorant
Other patterns are much more intrusive. For example, the Visitor pattern, when applied with double-dispatch, requires the addition of accept(Visitor) methods in each element to be visited, which modifies participants that are indeed ignorant and that should not be touched in an ideal world. Visitors using a switch-case approach or a reflection-based approach, or coded in a language that support multi-methods are not intrusive, and “purer”.
The Observer pattern also requires the Subject participant to know about its Observers, with the addition of a collection of Observers, attach() and detach() methods, and the notification logic, whereas the Subject would remain in the absence of the Observer pattern: it is an ignorant participant, and the typical Observer pattern violates Participant Ignorance.
To be fair, there are patterns for which our perspective is less relevant, where almost every participant appears to be deeply involved (very dedicated) to the pattern, and if we removed the pattern we would change everything. In the State pattern, each State and ConcreteState usually deals with the Context, which knows them as well.
In the Mediator pattern every colleague knows the Mediator, and each Concrete Mediator deals with each Colleague or Concrete Colleague. Introducing a Strategy is also really intrusive, ironically for the purpose of being able to add new behaviours in a non-intrusive way during software evolution…
Participant Ignorance helps to think clearly about the forces in action in a pattern, which usually discourage coupling (the more ignorant participants the better). If like I do you document the use of patterns in your code by using Javadoc tags (or Java annotations), then the principle states that these tags or annotations must only be located in the code of the dedicated participants to leave ignorant participant untouched when refactoring to or from patterns. More interestingly, the PPIP enable to derive allowed and forbidden dependencies between the participants of a pattern occurrence: ignorant participants must never depend on dedicated participants.