Navigating Principle Languages
Introduction
When making a design decision based on principles, it is necessary to find those principles which fit to the given design problem. This means the designer has to figure out which aspects need consideration. Seasoned designers will already know that by experience but there is also some guidance for that task. Principle languages interconnect principles in a way that the consideration of one principle automatically leads to other principles which are likely to be relevant in the same design situations. They point to other aspects to consider (complementary principles), to possibly downsides (contrary principles), and to principles of different granularity which might fit better to the given problem (generalizations and specializations).
How to Navigate
The following approach is how you find a characterizing set for a given design problem.
Starting Principles: Typically for each design decision there are one or two principles which obviously fit to the problem. When reasoning about duplicated code,
DRY is an obvious start. When reasoning about
interfaces,
EUHM is the right starting point, etc. The
OOD Principle Language gives further guidance by grouping the principles in different categories where each category roughly corresponds to typical kinds of design decisions. So when facing a design problem about how to divide a system into modules, how these modules shall communicate, how module interfaces shell be crafted or how the internal structure of a module shall look like, there is a small group of principles being candidates for a starting principle. Moreover the very first of the principles in these groups (
ML,
MP,
TdA/IE,
EUHM,
IH/E) are slightly more general than the others making them a predominant candidates for a starting principles. Of course for choosing the right starting principles, one has to roughly know all the principles in the principle language.
Traversing the Principle Language:
First the characterizing set just consists of the starting principle(s).
Then the following is repeated for each principle in the characterizing set
Have a look at the related principles. They may or may not qualify for the characterizing set.
Contrary principles are likely to point to possible downsides, so they are the most important ones. If you find that a contrary principle is relevant for the given design problem (independent of whether it's a disadvanatge), add it to the characterizing set.
Complementary principles may point to further aspects which are relevant in the same context. These further aspects are less likely to be disadvantages (compared to the currently considered principle) but they can be nevertheless. If the principle is relevant, add it to the characterizing set.
Specializations may fit better to the given problem. They are typically more helpful but applicable to a smaller set of problems. If you find a specialization to be suited better than the currently considered principle, replace the current one with the specialization. You may also encounter situations where a specialized principle cannot replace the current one but adds a further aspect. In such a case, add it to the characterizing set.
Generalizations are handled similar to specializations. If a principle seems to describe a similar aspect but does not quite fit itself, a generalization may replace the current one. But there are also cases where one might want to keep both in the characterizing set.
Repeat the procedure with the (now altered) characterizing set.
The procedure stops when there are no further principles which qualify for the given design problem. Alternatively the procedure may be aborted if the designer already feels comfortable with making a decision.
Result: The result is a characterizing set for the given design problem. Sets with around four or five principle are common. Characterizing sets with only one principle are rare. There may also be cases with seven or eight principles but that's also rare.
Remarks:
The procedure is not deterministic. The order in which the principles are considered, may vary.
Characterizing sets are not unique for several reasons:
Different starting principles may have been taken.
For some principles it may be a matter of opinion whether they qualify or not.
The procedure may be aborted at any time.
The whole procedure is meant to be light-weight. It's not meant to be documented or otherwise to be followed pedantically. The idea is to have a lightweight approach which is not much more than a way of thinking. While inexperienced designers might want to look every principle up, more advanced ones will only need a short glance at the principle language graph. Experienced designers who do not need this kind of guidance at all can skip all that and just use the principle language as a set of vocabulary for talking about their design. So the principle language helps designers with very different levels of experience.
The rationale section in the wiki may be used to justify whether a principle qualifies or not.
Example
Context
The following—rather sophisticated—example shows the usage of the OOD Principle Language. It details the assessment of a solution found in the CoCoME system1). The details of the system are irrelevant here but it resembles an information system which can be found in supermarkets or other stores. There are several components which are grouped into the typical layers of an information system: The presentation layer (GUI), the application or business logic layer and the data layer.
In CoCoME there is a mechanism for getting access to other components. In a nutshell it works like this:
For the data layer there is a data component, a class DataImpl
which aggregates three subcomponents Enterprise
, Persistence
, and Store
and gives access to them.
public class DataImpl implements DataIf
{
public EnterpriseQueryIf getEnterpriseQueryIf()
{
return new EnterpriseQueryImpl();
}
public PersistenceIf getPersistenceManager()
{
return new PersistenceImpl();
}
public StoreQueryIf getStoreQueryIf()
{
return new StoreQueryImpl();
}
}
public class DataIfFactory
{
private static DataIf dataaccess = null;
private DataIfFactory() {}
public static DataIf getInstance()
{
if (dataaccess == null)
{
dataaccess = new DataImpl();
}
return dataaccess;
}
}
Essentially DataIfFactory
resembles a mixture between the design patterns factory and
singleton. The latter one is important here. The purpose of a singleton is to make a single instance of a class globally accessible. Here DataImpl
is not ensured to be only instantiated once as it still has a public constructor. Nevertheless the “factory” class makes it globally accessible. In every part of the software DataIfFactory.getInstance()
can be used to get hold of the data component. And since DataIf makes the three subcomponents accessible, also these are accessible from everywhere. There is no need to pass a reference around.
Question
Finding a Characterizing Set
We will examine this question using the OOD principle language. First we have to find suitable starting principles. This is one of the rather sophisticated cases where finding a starting principle is at least not completely obvious. If we don't have a clue where to start, we'll have a look at the different categories of principles in the language. Essentially the “factory” enables modules to access and communicate with each other. So we are looking for principles about module communication. There are three of them in the principle language: TdA/IE, LC, and DIP. TdA/IE does not seem to fit, but LC seems to help. Coupling should be low and the mechanism couples modules in a certain way. So we'll choose LC as a starting principle and our characterizing set looks like this: {LC}.
Now we'll have a look at the relationships. LC lists KISS, HC, and RoE as contrary, TdA/IE, MP, and IH/E as complementary, and DIP as a specialization. Let's examine them:
-
The mechanism may be considered simple or complex, so
KISS qualifies
We'll insert it into the characterizing set: {LC,
KISS}
HC
RoE
-
MP
IH/E
DIP
So up until now the characterizing set is {LC, KISS, RoE, TdA/IE}. Now let's examine the relationships of the newly added principles. KISS lists GP, ML and MP as contrary principles and MIMC as a specialization.
Characterizing set up until now: {LC, KISS, RoE, TdA/IE, ML}. ML was newly added. Maybe on this point we might decide to abort the process because we already have a good idea of the aspects. But for the sake of the example, we'll continue with the relationships of ML. The wiki page lists KISS as a contrary principle and DRY, EUHM, UP, and IAP as specializations.
-
DRY
EUHM
UP
Whether UP qualifies or not, depends on the rest of the system, the team, other projects, etc. For the example, we'll assume that there are no other projects and parts of the system we want to be consistent with.
IAP
As a result we get {LC, KISS, RoE, TdA/IE, ML} as the characterizing set.
Note that although in this example the principles are examined in a certain order, the method does not prescribe any.
Using the Characterizing Set
In order to answer the above question, we have to informally rate the solution based on the principles of the characterizing set:
LC
The solution creates a relatively strong coupling to the concrete implementations of the components. If a class uses the “factory” and the components it gives access to, there is no way to have the class use other implementations of the components. There is no way to replace the implementations by a stub for testing purposes, there is no way to smoothly switch to another Data
component possibly using another way of storing the data. Every change in the arrangement of the classes needs a change in the code. LC is rather against this solution.
-
The solution is pretty easy to implement. Furthermore it is easy to get access to an arbitrary component. So according to
KISS this is a good solution.
RoE
-
Getting access to a the
Store
subcomponent requires asking
DataIfFactory
for the
Data
component and asking that one for the store. There is no way to tell the “factory” to do something. TdA/
IE is against the solution.
ML
So LC, RoE and TdA/IE are against the solution, KISS thinks it's good and ML has nothing against it. As it is not the number of principles which is important, the designer still has to make a sound judgment based on these results. What is more important: Coupling, testability, and clarity or a simple and fast implementation. In this case we'd rather decide that the former are more important, so we should rather think about a better solution.
Deciding between Alternatives
In the next step we would think about better alternatives and might come up with dependency injection and service locators. So there are three alternatives (with several variations): The current solution and the two new ideas.
We already constructed a characterizing set. So the only thing to do is to rate the ideas according to the principles:
The current “factory” approach is abbreviated “F”, dependency injection is DI and SL stands for service locator. In the following a rough, informal rating is described, where “A > B” means that the respective principle rates A higher/better than B. “=” stands for equal ratings.
LC
-
RoE
The rating of RoE depends on the concrete variant of the pattern. In the DI approach the dependencies are explicitly visible on the interface, which is not the case in the two other approaches. In solution F the dependency is not visible from the interface at all. Same with SL if the service locator is globally accessible. Even if a reference to the service locator is explicitly passed around, it is still not visible which services provided by the locator are used. On the other hand getting a reference is explicit with F and SL. In the DI approach it is only explicit when it is done manually. Typical DI frameworks wire the instances implicitly.
-
ML
As you can see all three possibilities have their advantages and disadvantages. The designer now has to weight the aspects in order to get to a decision. In this case we might state the following:
F is ruled out because it is not testable. The other two approaches have lower couplings (LC) which make them better testable. The advantages wrt.
KISS and ML do not justify that liability.
The solutions DI and SL are not very far apart but DI is slightly better wrt. LC, TdA/
IE and ML.
TdA/
IE can be regarded less important because it is a heuristic which is normally applied in other situations.
For RoE we also have to decide whether to use a framework or not. In CoCoME we would rather want to avoid a framework because the rest of the system is implemented in that way (
UP). We could incorporate that aspect in the characterizing principle or make another decision based on a newly created one. If we are comfortable with making the decision without constructing a characterizing set, just based on UP, we could also do that. In order not to complicate the example and in order to show this possibility, we'll do the latter. So a framework won't be used and manual dependency injection is good wrt. RoE.
Based on this weighting, we decide to use DI.