© Matthias Noback 2018
Matthias NobackPrinciples of Package Designhttps://doi.org/10.1007/978-1-4842-4119-6_11

11. The Stable Abstractions Principle

Matthias Noback1 
(1)
Zeist, The Netherlands
 

We’ve reached the last of the design principles related to package coupling, which means we have in effect reached the last of all the package design principles. This principle, the Stable Abstractions principle, is about stability, just like the Stable Dependencies principle. While the previous principle told us to depend “in the direction of stability,” this principle says that packages should depend in the direction of abstractness.

Stability and Abstractness

The name of the Stable Abstractions principle contains two important words: “stable” and “abstract”. We already discussed stability of packages in the previous chapter. A stable package is not likely to change heavily. It has no dependencies so there is no external reason for it to change. At the same time, other packages depend on it. Therefore, the package should not change, in order to prevent problems with those depending packages.

In the previous chapter, we learned that you can calculate stability and that you can verify that the dependency graph of a project contains only dependencies of increasing stability, or decreasing instability. In this chapter, we learn that we also have to calculate the abstractness of packages and that the dependency direction should be one of increasing abstractness, or decreasing concreteness.

The concept of abstractness is something we also encountered in previous chapters. For example, in the chapter about the Dependency Inversion principle (Chapter 5), we learned that our classes should depend on abstractions, not on concretions. We discussed several ways in which a class can be abstract. The most obvious way is when a class has abstract (also known as virtual) methods. These methods have to be defined in a subclass. This subclass is a concrete class because it is a full implementation of the type of thing that the abstract class tries to model. When a class only has abstract methods, we usually don’t call it a class, but an interface. Classes that implement the interface eventually have to provide an implementation for all of the abstract methods defined in the interface.

The Dependency Inversion principle told us to depend on abstractions, not on concretions. The reason was, like always, that we need to be prepared for change. A class that depends on a concrete thing is likely to change whenever some implementation detail of the concrete thing changes (see Figure 11-1). Besides, if at some point we want to replace the concrete thing with another concrete thing, we’d have to modify the class to understand and use that new concrete thing. And in that situation it’s probably not the only class that needs to be modified.
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig1_HTML.jpg
Figure 11-1

Depending on concrete things

If instead we define something abstract, like an abstract class or preferably an interface, and we depend on it, we are much better prepared for change (see Figure 11-2). Most changes occur in concrete things, i.e., in fully implemented classes. The abstract things, like interfaces, will remain the same over a longer period of time. So if we depend on an abstract thing, it is likely that we will not be negatively influenced by it—it’s supposed to be very stable.
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig2_HTML.jpg
Figure 11-2

Depending on abstract things

And this is where the two concepts, stability and abstractness, meet. If we consider stability to be the likeliness that something is going to change, then what is true for classes is also true for packages. As we know now, it’s better to depend on stable packages than to depend on unstable packages. Stable packages are less likely to change, so a depending package won’t be negatively influenced by changes in its dependencies. In the same way it’s safer to depend on abstract classes or interfaces because they are less likely to change.

We can follow the same reasoning while we apply the concept of abstractness to packages: it would be better to depend on an abstract package than on a concrete package. For the same reason—an abstract package would contain no particular implementation details that would be susceptible to change. Over a longer period of time it will stay the same.

How to Determine If a Package Is Abstract

The question is: is it possible to mark a package as either abstract or concrete? It is possible, even though “being abstract” is not a Boolean value. There are many degrees of abstractness. We might consider a class to be abstract if it contains at least one abstract method. Then a class is concrete if it has no abstract (or virtual) methods.

We can determine the abstractness of packages in a similar way. A package is abstract if it contains no regular classes, only interfaces and abstract classes. And a package is concrete if it has at least one fully implemented, concrete class.

Still we’d need a little nuance here. According to this definition of abstract and concrete packages, a package with 10 interfaces and 1 concrete class would count as a concrete package, even though it contains many more abstract things than concrete things. Therefore, we should take the total number of classes and interfaces into account.

The A Metric

The suggested way to find an indication of the abstractness of packages is to calculate the number of abstract classes and interfaces in a package, then divide that number by the total number of concrete classes, abstract classes, and interfaces in that package. The resulting thing would be a quotient with a value somewhere between 0 and 1. We call this number the A metric for packages:
A = C-abstract / (C-concrete + C-abstract)

When the value of the A metric for a package is equal to or near 0, it’s a highly concrete package. It contains (almost) no interfaces, only concrete classes, so it’s full of implementation details, and therefore liable to change.

When, on the other hand, the A metric is equal to or near 1, it’s a highly abstract package. It contains (almost) no concrete classes, but mostly abstract classes and interfaces. It’s likely that these abstract things will stay the same over time. After all, only concrete classes and consequently concrete packages are liable to change.

Abstract Things Belong in Stable Packages

So abstract packages are stable too. Or at least, they should be. This is where the Stable Abstractions principle steps in:

Packages that are maximally stable should be maximally abstract. Instable packages should be concrete. The abstraction of a package should be in proportion to its stability. 1

We already know that a package should depend only on packages that are more stable. Now we also know that abstract things are likely to be more stable, i.e., they change less often and less dramatically. Hence concrete classes can safely depend on them. But what if an interface is part of a highly unstable package? Then it’s consequently not safe to depend on that package. The unstable package is likely to change. The interface inherits the instability of its containing package.

Interfaces and abstract classes are better off in a stable package. The stability of the package itself will be beneficial for the abstract things it contains. At the same time, the abstract things are good for the stability of the containing package. Packages that apply the Dependency Inversion principle start to depend on it because of the abstract things it contains. This turns it into a more responsible package and will thereby force it to become more stable.

The opposite is also true: concrete classes are better off in unstable packages. The implementation details of the classes are likely to change anyway and this would better happen in an unstable package, which has less responsibility toward depending packages. If, however, a concrete class would be inside a highly stable package, it would make that package more unstable because a concrete class is liable to change.

Abstractness Increases with Stability

The Stable Abstractions principle adds one extra requirement. It wants to unify abstractness and stability into this simple rule: a package should be as abstract as it is stable.

Let’s assume you have a number of packages and have calculated the I values for all of them. Remember the I value indicates the stability of a package: the closer the value is to 1, the more unstable a package is. The closer it is to 0, the more stable it is. When you draw them in a diagram, the packages with the highest value for I are at the top and those with the lowest value for I are at the bottom. When you travel from a package to its dependencies you encounter packages that only have decreasing values for I (see Figure 11-3), that is, they become more and more stable.
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig3_HTML.jpg
Figure 11-3

All dependencies go in the direction of stability

To figure out if you have also applied the Stable Abstractions principle correctly, you also need to calculate the values for A (by dividing the number of abstract classes and interfaces by the total number of classes and interfaces). Then add the resulting A values to the dependency diagram (see Figure 11-4).
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig4_HTML.jpg
Figure 11-4

All dependencies go in the direction of abstractness

Now you only need to verify that each dependency arrow leads to a package with a higher value for A. In other words, dependencies should have an increasing abstractness.

Strictly speaking, the values for I and A added together should be exactly 1. This would mean that all packages are as abstract as they are stable. But this is completely unrealistic. There is always some margin to this. However, I + A should not be too far away from 1. In general, highly abstract packages should be highly stable, and concrete packages should be unstable.

The Main Sequence

It’s possible that stable packages contain concrete classes and unstable packages contain abstract classes or interfaces. These packages might be easy to spot, but there are many degrees of stability and abstractness. To find out which packages have imbalanced values for I and A, we can plot the packages in a diagram called the main sequence diagram.

We first draw a vertical axis going from 0 to 1. It stands for the degree of abstractness of a package (expressed by A). Then we draw a horizontal axis going from 0 to 1. This represents the degree of instability of a package (expressed by I). Finally, we draw a diagonal line from the top-left corner to the bottom-right corner. This line is called the main sequence (see Figure 11-5).
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig5_HTML.jpg
Figure 11-5

The main sequence diagram template

Now we plot each package at the right spot in the diagram, based on its values for I and A (see Figure 11-6).
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig6_HTML.jpg
Figure 11-6

The main sequence diagram for the packages in the previous dependency diagram

You’ll know when you have applied both the Stable Dependencies principle and the Stable Abstractions principle correctly if all the packages are near or on the diagonal, i.e. the main sequence. According to this rule, the previously shown main sequence diagram looks pretty good.

If you find any package that lies quite far from the main sequence, you should take a closer look at it. Consider its surrounding packages too and try to make some changes to its dependencies or its dependents in order to achieve the right amount of stability. It may also be necessary to change the abstractness of the package by relocating some abstract classes or interfaces.

Once you’ve fixed the biggest issues with stability and abstractness, you shouldn’t forget to regularly come back to see if packages have not started to drift away from the main sequence. After some time the nature of existing packages may change because of new features being added to them and this may eventually cause an imbalance.

Types of Packages

When you travel down the main sequence from the top-left to the bottom-right corner, you will first come across highly concrete, highly unstable packages. These should all be application-level packages. They are full of implementation details, specific to the actual project you’re working on. They are allowed to be concrete, because no other package depends on them. Hence, they are unstable packages bound to change as often as the business changes.

Taking some more steps on the main sequence you will find in the middle packages that are somewhat abstract and somewhat stable. They are much less affected by external changes, but to a certain degree they are allowed to change themselves without bringing the whole project in danger.

When the journey on the main sequence ends, you will be in the realm of abstract, stable packages: the foundational blocks of your application. These contain lots of interfaces and abstract classes, which is why many classes (and consequently packages) depend on them. These classes apply the Dependency Inversion principle correctly in order to be less susceptible to change. Hence, these abstract packages should be stable too. And they are, because they are responsible and have no dependencies themselves.

Interface Packages

When you want to create highly abstract, stable packages, you may end up simply extracting the interfaces from existing packages and putting them all in one big interface package.

This is not the best thing you can do for your project. By putting all the interfaces in one package, you are going to violate the Common Reuse principle: some clients need just one or two interfaces from the package, but still they would have to depend on the entire package.

Besides, not all interfaces are meant to be depended on outside of a given package. You may have lots of interfaces that are only for private use within the same package (depending on your programming language, you may be able to mark an interface as private).

One last thing to be aware of: before moving interfaces to a separate project, make sure that you have properly applied the Interface Segregation principle to all of them. That way, clients won’t be forced to depend on interfaces with methods they do not or should not want to use.

If you properly apply these rules, go ahead and create lots of small interface packages, each in support of one specific feature.

Strange Packages

What happens in the corners that lie far away from the main sequence? What misbehaving packages can be found there (see Figure 11-7)?
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig7_HTML.jpg
Figure 11-7

A package in the top-right corner

In the top-right corner, we will find packages that are both highly abstract and highly unstable. These packages can thus be characterized as:
  • Irresponsible (no other packages depend on them)

  • Dependent (they depend on lots of other packages)

  • Abstract (they contain only abstract classes or interfaces)

This is a very strange kind of package. It’s very unlikely that such a package can be found in your project, because it would most likely contain dead code. It’s never used by any part of the project, yet the code is abstract, meaning that it cannot be used standalone—someone has to provide an implementation for it. In other words, packages like these are useless and you should try to get rid of them.

On the opposite side of the diagram, in the bottom-left corner (see Figure 11-8), you will find packages that are highly concrete, yet highly stable.
../images/471891_1_En_11_Chapter/471891_1_En_11_Fig8_HTML.jpg
Figure 11-8

A package in the bottom-left corner

Such packages are:
  • Responsible (many packages depend on them)

  • Independent (they have no dependencies)

  • Concrete (they contain only concrete classes)

This kind of package would be heavily used all over the project. Because it doesn’t need any other packages to do its work, this is probably some kind of low-level library. However, it doesn’t offer any abstractions for the things it does. This means that it’s hard for classes in other packages to naturally comply to the Dependency Inversion principle: they have to depend on the concrete classes from this package instead of interfaces.

The solution for getting this package back in shape is to apply the Dependency Inversion principle to depending packages. They should not depend on the concrete classes from this package anymore, but instead depend on their own interfaces or interfaces defined in some more stable and abstract package.

Conclusion

In this chapter, we discussed the intimate relation between stability and abstractness. We learned that the more stable a package is, the more abstract things it should contain. The counterpart of this is that the more concrete things a package contains, the more unstable it becomes.

We looked at the main sequence diagram to get a grip on different kinds of packages and where they are on the sliding scale between concrete/unstable and abstract/stable. Using this diagram, we are able to spot packages with extraordinary characteristics.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.186.15