The Stable-Abstractions Principle

What?

This is the last of the Principles of Package and Component Design, the Stable-Abstractions Principle (SAP). It says: ”A component should be as abstract as it is stable.”

In the Stable-Dependencies Principle (SDP) post we learned that stability, or in that case Instability, I, can be calculated using the formula I = Ce / (Ca + Ce) where Ca is the number of afferent couplings and Ce is the number of efferent couplings. A component that has many dependencies towards it, but depend only on a few external classes is considered stable, and vice versa.

Conforming to SAP leads to stable components containing many abstract classes, and instable components containing many concrete classes.

Why?

The goal of making stable components abstract is to allow for them to be easily extended and avoid them constraining the design.

Instable components on the other hand can contain concrete classes since they are easily changed

How?

Just as for instability we can define a metric that helps us understand how abstract a component is. The formula is very simple:

A = Na / Nc

where Na is the number of abstract classes in the component and Nc is the total number of classes in the component. A component with A = 1 is completely abstract while a component with A = 0 is completely concrete.

The Stable-Dependencies Principle

What?

The Stable-Dependencies Principle (SDP) says: ”Depend in the direction of stability.”

What this means is that the direction of the dependencies in the component dependency graph should point towards more stable components. To understand what this means we need to define stability for a component. Uncle Bob turns this around and defines a way to measure instability, I, using the formula:

I = Ce / (Ca + Ce)

where Ca is the number of classes outside this component that depend on classes within this component (afferent couplings) and Ce is the number of classes inside this component that depends on classes outside this component (efferent couplings). I has the range [0,1] where I = 0 indicates a maximally stable component and I = 1 indicates a maximally instable component.

A component that does not depend on any other component is maximally stable and a component that only depend on other components but have no components depending on it is maximally instable.

Why?

When we design our components there will be some components that we want to be easy to change. They contain classes that often needs to be modified when requirements change. If these components have many dependencies to them (are very stable; have a value I close to 0) they will be difficult to change since changing them will impact many other components.

Figure 1 shows a component diagram where SDP is violated. The Stable component, that has an instability value of I = 0.25 has a dependency to the Volatile component, that has an instability value of I = 0.75. This dependency makes the volatile component difficult to change.

Figure 1. Violating SDP

How?

So how do we fix violations to SDP? The answer is, once again, inverting dependencies by applying the Dependency Inversion Principle (DIP). Look at Figure 1, to fix the violation we can create an interface or abstract base class and put in a new component, CG. We can then let the Stable component depend on CG and have the Volatile component implement the concrete classes. By doing that we end up with the dependency graph showed in Figure 2.

Figure 2. Conforming to SDP

Now with this change in place the volatile component has been made easier to change.

The Acyclic Dependencies Principle

What?

The Acyclic Dependencies Principle (ADP) is the first of three principles that deals with the relationships between components.

It says: ”Allow no cycles in the component dependency graph.”

If you draw the components and the dependencies between them and you are able to follow a dependency back to a component you have already visited, then it is a violation to ADP.

Figure 1. Violating ADP

Why?

So why should you care about cyclic dependencies? Because their existence makes it very hard to split up responsibilities and work on different tasks in parallel without stepping on each other toes all the time. Take a closer look at ComponentH in Figure 1 above. The dependency to ComponentA makes it depend on every other component in the system. Since ComponentD depends on ComponentH, it also makes ComponentD depend on every other component in the system.

This makes it really hard to work with and release components D and H. But what would happen if we could remove the dependency from ComponentH to ComponentA? Then it would be possible to work with and release ComponentH in isolation. The person, or team, working with ComponentD could lift in new releases of ComponentH when they needed, and wait with taking in the new release if they had other more prioritized tasks to solve before moving to the new release.

How?

How can we break a dependency such as the dependency from ComponentH to ComponentA? There are two options we can use:

  1. Apply the Dependency-Inversion Principle (the D in SOLID) and create an abstract base class or interface in ComponentH and inherit it in ComponentA. This will invert the dependency so that ComponentA depends on ComponentH instead.
  2. Create a new component – let’s call it ComponentI – that both ComponentA and ComponentH depends on. See figure 2 below.
Figure 2. Conforming to ADP

It is easy to introduce cyclic dependencies. You need to keep track of the dependencies in your system and break cycles when they appear.

The Common Closure Principle

What?

The Common Closure Principle (CCP) states: ”The classes in a component should be closed together against the same kind of changes. A change that affects a component affects all the classes in that component and no other components.”

To put it in other words, a component should not have multiple reasons to change. This is the Single Responsibility Principle (SRP) from SOLID restated for components.

Why?

The goal of CCP is to increase maintainability. If a change to requirements always triggers changes in a lot of the components it will be harder to make the change, each change will lead to a lot of re-validation and redeploying, and parallelizing different changes will be harder.

You would prefer the changes to occur in one component only.

How?

As with SRP, full closure is not possible. There will always be changes that requires multiple components to be changed. The aim should be to be strategic and place classes that we, from experience, know often changes together into the same component.

The classes don’t have to be physically connected (i.e. are connected through code) but can also be conceptually connected.

The Common Reuse Principle

What?

The Common Reuse Principle (CRP) states: ”The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.”

In the last post I wrote about the Reuse/Release Equivalence Principle (REP). It says that reusable components must be releasable components, with everything that comes with making it so. I finished with writing that classes should have a common theme or use case.

CRP emphasizes this, by saying that the classes that make up a component should be inseparable. It should be hard or impossible to reuse only some of the classes of a component. The using component should be (directly or indirectly) be using all of them.

Why?

Components in .NET are made from assemblies carried in a DLL. If one component uses a class in another component, even if it only uses a single method in a single class, it has a strong dependency to that component.

This means that every time someone makes a change to used component, even in code that the using component do not use and don’t care about, a new version of the used DLL is created and the using component must be re-validated against that new version.

This is a waste of resources and effort and should be avoided

How?

CRP says something about which classes to put in a component, but mostly it says which classes to not put in the component. The classes that make up a component should be tightly coupled and depend heavily on each other. It should not be possible to separate them easily. If a class does not fit into this description it does not belong in the component and should be put elsewhere.

The Reuse/Release Equivalence Principle

What?

When we group classes into components we strive towards making some of them reusable so that we can potentially use them for other purposes and also other teams in the organization may use them if they want to. The Reuse/Release Equivalence Principle (REP) states that ”The granule of reuse is the granule of release”. In other words, if a component should be considered reusable it must be a releasable unit.

Why?

The reasons for why we should group classes into releasable components are mostly political. It has to do with the expectations other people have on the components and the support they require if they are going to reuse the code.

The users will expect the components to have version numbers, good documentation and support, etc

How?

When grouping classes into components, reuse should be considered. Either all the classes in a component are reusable, or none of them are. Do not put classes that should be reused and classes that should not be reused into the same component.

The reusable components should also ”target the same audience” i.e. they should have a common theme or use case. Think of it this way: If the component is reused, all of the classes in the component should be reused. Do not put classes that have nothing in common into the same reusable component.

Principles of Package and Component Design

One of the most known set of principles regarding software design is probably SOLID. But an important part of structuring software that SOLID does not cover is how to group classes into packages and components in a way that makes it scale, both when the application itself grows but also when the number of teams and developers working with the code grows.

Fortunately this is also something that Uncle Bob has thought of and written down a number of principles on. In the next set of blog posts I will cover these principles briefly, but I do encourage you to buy a copy of either ”Agile Principles, Patterns, and Practices in C#” or ”Clean Architecture” so that you can really read up on them and look at a larger example on how they can be applied.

Uncle Bob has written down six different principles on package and component design. Three of them targets package cohesion, and the other three governs package coupling. They are called:

  • The Reuse/Release Equivalence Principle (REP)
  • The Common Reuse Principle (CRP)
  • The Common Closure Principle (CCP)
  • The Acyclic Dependencies Principle (ADP)
  • The Stable-Dependencies Principle (SDP)
  • The Stable-Abstractions Principle (SAP)

I will write about them in that order, starting with REP and ending with SAP.

Oh, and by the way. In .NET a component is called an Assembly and is usually carried in a DLL.

SOLID – The Dependency-Inversion Principle

We have now reached the grande finale of the blog series on SOLID, the big D, The Dependency-Inversion Principle. Let’s jump right into it.

Definition

The Dependency Inversion Principle (DIP) states:

A: High-level modules should not depend on low-level modules. Both should depend on abstractions.

B: Abstractions should not depend upon details. Details should depend upon abstractions.

This needs some explaining. More traditional software development methods tend to create software structures in which high-level modules depends on low-level modules. DIP says these dependencies should be exchanged with abstractions. I’ll show you an example on how this can be done in the ”How to implement” section.

We must also define ”high-level” and ”low-level” in order to understand this properly. I like to define high-level modules as close to the Domain/Business logic and low-level as those close to the input and output of the program. A typical high-level module contains the domain models while a typical low-level module contains code that deals with I/O (for example reading user input commands or persisting data to a database).

Code smells

Code that does not adhere to DIP can be hard to change and re-use. In other words it smells of Rigidity and Immobility. The smells comes from the direct dependencies, making it hard to change lower-level modules without making large changes to the high-level modules.

Assume as an example that you have a high-level class, Product, that have a direct dependency to a low-level class, SqlDb. Now, changes to the low-level class will force the high-level class to be changed. And it may also be hard to re-use the high-level class due to this direct dependency.

How to implement

Traditional layering

In order to invert the dependencies a set of service interfaces can be added, at the same level as the current module. These interfaces should then be implemented by the lower level module. See the diagrams.

In the traditional layering style there are direct dependencies from higher-level classes to lower level classes. This means that the top level classes will be dependent on changes on the lowest level. A change to the lowest level may propagate all the way up to the top forcing a complete recompile, re-test and re-deploy of all modules in the system.

Dependency Inverted Layering

If we add an abstract service interface at all layers that have dependencies to lower layers we effectively break this dependency chain. The high-level, mid-level, and low-level modules can be put into different assemblies (projects) so that a change in one layer does not affect any other layers as long as the interface isn’t changed.

Another aspect that is different from the traditional way of putting code into layers is that the abstract interfaces are grouped with the clients, and not together with their implementations. The interfaces should be designed from the clients needs and not the other way around. Changes to the interfaces should be driven from the clients, i.e. inverted compared to traditional layering.

Summary

This post ends the series on the SOLID principles. I started with an introduction to SOLID where I explained why you should care to learn what SOLID is and also introduced some code smells that is common in large software projects. I then went through the five different SOLID principles, The Single-Responsibility Principle, The Open/Closed Principle, The Liskov Substitution Principle, The Interface Segregation Principle, and The Dependency-Inversion Principle and explained what they say, which code smells that may appear if not adhered to, and how to implement them.

I must remind you to take it easy and not add abstractions, apply patterns, and introduce these principles until you detect the smells. Doing that will add unnecessary complexity to your code, making it harder to work with and understand.

However, when you do introduce the principles, use all of them on the smelly part of the code. Now stop reading and write some code!

SOLID – The Interface Segregation Principle

We are closing in on the final principle of SOLID. In the last post I wrote about the Liskov Substitution Principle, and now it is time to take a look at The Interface Segregation Principle (ISP).

Definition

The Interface Segregation Principle was coined by Robert C. Martin. It is explained in his book ”Agile Software Development Principles, Patterns, and Practices” from 2002 but has probably been around longer than that. It states that:

Clients should not be forced to depend on methods they do not use

The goal of it is to reduce the side effects and frequency of required changes by splitting the software into multiple, independent, parts.

Code Smells

As the code evolves and more features are added it is common that classes grow and more and more functionality is added to them. This has a tendency to grow into ”fat” interfaces, which can be identified by that their methods can broken up into groups, each group serving a different set of clients. The code will start to smell of:

  • Needless complexity and needless redundancy – when interfaces grow you risk ”interface pollution”, i.e. interfaces that contain methods only needed by one or a few clients. Some implementations may not need all the methods of the interface but are forced to implement degenerated versions of the methods (methods that are empty or throws an exception when called). It may also be tempting to push empty implementations and exception throwing down to the base class, which leads to violations of LSP.
  • Rigidity and Viscosity – A change to an interface, enforced by a single client, will force all other derivatives to be updated. Even though they do note require the functionality.

Fortunately there are some straight forward ways to fix these violations.

How to implement

In order to realize how to implement ISP you need to understand that clients of an object does not need to access it directly, they can instead access it through an abstract base class or a thin interface. In other words, split your fat interfaces into several thinner ones and serve each client the specific interface(s) they need. Let’s see an example, assume you have a class that looks like this:

public class ComplexClass
{
  // Methods called by clients in group A
  public void MethodA1() { ... }
  public void MethodA2() { ... }

  // Methods called by clients in group B
  public void MethodB1() { ... }
  public void MethodB2() { ... }
  public void MethodB3() { ... }

  // Methods called by clients in group C
  public void MethodC() { ... }
}

public class ClientInGroupA
{
  // Only calls methods MethodA1 and A2, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupA(ComplexClass cc)
  {
     _cc = cc;
  }
  ...
}

public class ClientInGroupB
{
  // Only calls methods MethodB1, B2, and B2, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupB(ComplexClass cc)
  {
    _cc = cc;
  }
  ...
}

public class ClientInGroupC
{
  // Only calls method MethodC, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupC(ComplexClass cc)
  {
    _cc = cc;
  }
  ...
}

Instead of handing all the clients the same interface, all public methods of an object of type ComplexClass. You can segregate the interface into three separate new interfaces, one for each group of clients:

public interface IGroupA
{
  void MethodA1();
  void MethodA2();
}

public interface IGroupB
{
  void MethodB1();
  void MethodB2();
  void MethodB3();
}

public interface IGroupC
{
  void MethodC();
}

public class ComplexClass() : IGroupA, IGroupB, IGroupC
{
  ...
}

public class ClientInGroupA
{
  // Only dependent on methods it uses
  private readonly IGroupA _oa;
  public ClientInGroupA(IGroupA oa)
  {
    _oa = oa;
  }
  ...
}

public class ClientInGroupB
{
  // Only dependent on methods it uses
  private readonly IGroupB _ob;
  public ClientInGroupB(IGroupB ob)
  {
    _ob = ob;
  }
  ...
}

public class ClientInGroupC
{
  // Only dependent on methods it uses
  private readonly IGroupC _oc;
  public ClientInGroupC(IGroupC oc)
  {
    _oc = oc;
  }
  ...
}

Summary

Applying the Interface Segregation Principle can help against code smells such as Needless Complexity, Needless Redundancy, Rigidity, and Viscosity. Even though it might not be possible to split up the implementation, it is possible to define thinner interfaces, streamlined towards the need of the clients.

Next up is the last part of SOLID, the Dependency Inversion Principle, which will help you design your interfaces in a way that is suitable for Object Oriented Design.

SOLID – The Liskov Substitution Principle

In the previous post I wrote about the O in SOLID, The Open/Closed Principle. Now it is time for the L, The Liskov Substitution Principle (LSP).

Definition

In the year 1988, the American Computer Scientist Barbara Liskov, wrote

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

In other words this means that subtypes must be substitutable for their base types. An example of a violation of LSP would be: Given a method M(B) where B is an object of type BaseClass. If M(B) behaves badly given an object D of type DerivedFromBaseClass then D violates LSP.

Code Smells

Violation of LSP leads to Fragility. Parts of your code might break in subtle ways when adding new derived classes.

Attempts to fix this issues might lead to violations of OCP, which in turn introduces Rigidity and possibly also Immobility.

How to apply

Unlike the two previous principles, SRP and OCP, there are no named patterns to apply in order to fix violations of LSP. Manual inspection and unit testing comes a far way when it comes to detect violations and then you will need to determine from case to case how to fix the violation.

It can be tempting to put a test in the code to determine the type of the object passed to the method:

public void Method(Base b)
{
  // Do NOT do this. It violates OCP
  if (b is Derived d) { ... }
  else { ... }
}

These types of tests however violates OCP since it is no longer possible to add new derived types without changing existing code.

What can be done is, instead of adding type tests, you separate the violating methods from the existing interface. This will leave you with a common interface, that all classes can implement, and a specialized interface that contains the methods that one or more classes should not implement. For example, let’s assume that we have an interface, IF, that is implemented by the classes CA, CB, and CC. Now, assume CC conforms to LSP for methods MA, MB, and MC but violates LSP for methods MD and ME. You can then break out MA, MB and MC from IF and move them to a new interface IFCommon. You then let IF derive from IFCommon, let CA and CB still implement IF but let CC implement only IFCommon and it’s own versions of MD and ME. See the diagrams below:

CC violates LSP since it’s implementations of MD and ME behaves badly
The code now conforms to LSP. It is no longer possible to use CC where IF is expected.

Summary

The Liskov Substitution Principle puts tougher demands on derived classes than just the fact that they have a common base class. I demands that it should be possible to use the derived classes instead of the base classes, without breaking anything. Conformance to LSP is an enabler for OCP, since for it to be possible to extend the current behavior, by adding new derived classes, these must work as expected when used as their base class.

We are now more than half way through SOLID, next up is The Interface Segregation Principle.