In the first instalment of this post, I introduced the notions of physical and logical designs. Then, I introduced three new stereotyped UML dependencies needed to express the relations between classes in terms of both interfaces and implementations. In this post I discuss how to transform a logical design into a physical architecture that can be exploited in order to analyze the overall system testability.
To show how this transformation is obtained, consider the simple UML class diagram in Figure 1: it illustrates the Shape hierarchy, one of its clients (the ShapeEditor class), and a utility class (ShapeUtil). The first step to translate this diagram into a logical design diagram (according to the Lakos method) is to characterize each relation between classes in terms of generalization and the three new stereotyped dependencies «Uses-In-The-Interface», «Uses-In-The-Implementation», and «Uses-In-Name-Only» (the latter is not supported by all programming languages, as noted in the aforementioned instalment).
To select the proper type of dependency, we have to check the source code, if available. Otherwise, at design-time, we can set a specific dependency type between two classes in order to force a particular property: for example, we specify that a class X «Uses-In-The-Implementation» a class Y when we want to treat Y as an encapsulated implementation detail of X, avoiding to propagate this logical dependency to any external client which uses X.
I suppose here that at design-time ShapeUtils will be coupled with every concrete shape, using them substantially in its implementation. Thus, from a logical point of view, ShapeUtils «Uses-In-The-Implementation» Triangle, Square, and Circle.
The same reasoning is applied for the class ShapeEditor, with one important difference: whereas ShapeUtils needs to delve directly with each concrete class, the ShapeEditor can (should) treat every shape trasparently, without knowing directly its concrete type. Hence, ShapeEditor will depend only from the (abstract) base class Shape, and the communication with concrete shapes will be realized by means of polymorphism. The resulting logical design is depicted in Figure 2. Note that, in order to show the transition between logical and physical design, I have specified in the diagram also the component to which every class belongs to (showing components is not mandatory in a logical design, as it is in physical design).
The last step that we have to do to complete the transformation is to map any arrow (relation) between classes in the logical design into a dependency between components in the physical design. The transformation of both generalization and dependency relations between the classes ShapeUtil, Triangle, and Shape is depicted in Figure 3.
The final physical design corresponding to the class diagram of Figure 1 is illustrated in Figure 4.
This diagram has an important property of good software design: it is layered. In other words, it is levelizable: it is possible to partition all the components in a system based on their physical dependencies into equivalence classes called levels. I will discuss levels and their implication to testability in the next instalment.