Developing SOLID Code

SOLID CODE

A Rule of Five

Good acronyms don’t always make for good coding, but the SOLID principles come close. They’ve been around a long time, and come from different backgrounds, but they were brought together by Robert C. Martin – also known as ‘Uncle Bob’.

While the examples here are in C#, the principles apply to any object-oriented language, and to some extent to any programming language.

The principles are:

The Single Responsibility Principle.

The Open Closed Principle.

The Liskov Substitution Principle.

The Interface Segregation Principle.

The Dependency Inversion Principle.

Is SOLID Code Robust Code?

I wouldn’t mark anyone down for saying that the underlying theme of the SOLID principles is to make our code more robust, but I like to look at it another way: the principles acknowledge just how fragile code always is.

And what did your mother tell you about fragile things?

DON’T TOUCH!

Which leads us to the overarching item in the SOLID principles, though not the first in the acronym.

The Open Closed Principle

Classes should be open to extension, but closed to modification.

Ever added a new feature to existing code? Of course you have. Ever accidentally broken another feature in the process? Almost certainly.

And so the OCP says, add new features by adding new code, not by modifying existing code. By ‘extension’ we could mean inheritance, or interface implementation, or adding an event handler or all sorts of other mechanisms. The important thing is not to change existing code.

That way, the worst you can do is fail to add the new feature, but you won’t break what’s there. Staying still is better than going backwards.

Each of the other principles, to a greater or lesser degree, derives from this.

(See video: SOLID – The Open Closed Principle https://youtu.be/J55tD3QPAQY)

The Single Responsibility Principle

A class should have only one reason for change.

A bit contradictory, perhaps, since we could read the OCP as saying ‘a class should have zero reasons for change.’

There are two things we can draw from this:

  • The principles are not absolute. They’re a good basis, but there will always be compromises.
  • Don’t be afraid of writing new classes. When asked to add a new feature, the instinct can often be to think, ‘Where in the existing code does this feature go?’ If by ‘where’ we mean ‘in which project’ that’s fine, but a new feature should always mean a new class. Classes almost always begin their lives with a single responsibility. It’s over time that they get bloated.

(See video: SOLID – The Single Responsibility Principle https://youtu.be/IrcMr0Xqz8Q)

The Interface Segregation Principle

Interfaces belong to clients, not implementations.

It can be hard to sum these principles up in a simple phrase, but what this means is, when you design an interface you should be thinking ‘what does my client code need?’ rather than ‘what does my library provide?’

If your library contains a large number of features, then offer a large number of separate interfaces, each focusing on a single … responsibility. That way, clients can pick and choose what they need.

And so we can see, the ISP is really just the SRP applied specifically to interfaces. Interfaces should be narrow and stable:

  • Narrow – they have a small number of closely related methods. The SRP.
  • Stable – they don’t change once written. The OCP. And narrow interfaces are more likely to be stable.

(See video: SOLID – The Interface Segregation Principle https://youtu.be/hBeS-k80qz4)

The Dependency Inversion Principle

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

The title of this principle is somewhat misleading, suggesting that we should reverse the direction of some of our existing dependencies.

Here, GUI is dependent on GameLogic. If we change the GameLogic we may have to change the GUI. (Which may be a breach of the OCP):

GUI depends on GameLogic

But if we invert the dependency

GameLogic depends on GUI

the GameLogic has to change just because the GUI has changed, which is even worse.

The longer description of the DIP is more helpful. Both modules should be dependent on an abstraction – by which we usually mean an interface.

Both dependent on interface

Thus we can change either the GUI or the GameLogic without affecting the other.

Of course, both are now dependent on the interface, but if the interface is narrow and stable (i.e. it obeys the ISP) then the risk of change is minimal.

(See video: SOLID – The Dependency Inversion Principle https://youtu.be/XydkF1AVbYE)

The Liskov Substitution Principle

A derived class should be substitutable for its base class.

The LSP, named after the computer scientist Barbara Liskov, is the oldest and most formal of the five principles. It’s fundamental to how object-orientation works, and there’s a lot of detail to it which has helped direct the evolution of O-O systems.

Thankfully, this concept is built into modern O-O languages.

void CalculatePay(Employee employee)

CalculatePay(new Employee()); // Works - it's an employee

CalculatePay(new Manager());  // Works - a Manager is substitutable for an Employee

CalculatePay(new Product());  // Won't compile - there's no inheritance relationship

By ‘substitutable’ we mean that any variable or parameter which accepts a base class can also accept a derived class. Anything that we can do to the base class (any method we can call, etc.)  we can also do to the derived class. So we can say ‘a Manager is a kind of  Employee’, we can say ‘a Manager additionally has a budget’, but what we must not say is ‘unlike an Employee, a Manager cannot earn overtime.’

O-O languages largely prevent this, but there are tricks that developers mistakenly use to cheat the LSP. For example:

class Manager : Employee
{
    public override decimal CalcOvertime() => throw new NotImplementedException();
}

This may seem to effectively remove the functionality that we had in the base class, but does it make our code any better?

The problem is not specifically in the CalcOvertime() method. It’s in the inheritance structure we’ve chosen in the first place.

And that’s where we link back to the OCP, through one final thing to bear in mind.

(See video: SOLID – The Liskov Substitution Principle https://youtu.be/DOCNiTEiaBk)

It’s Too Late to Start Now

Sadly, we can’t always just sit down and apply the SOLID principles to existing code. We cannot add a new feature by adding a new class if the existing code does not have an appropriate extension point – a base class to derive from or an interface to implement or some such.

Very often, existing code will need to be refactored to include appropriate extension points. But it’s worth the effort. Once you’ve got that code on a better footing, then applying the SOLID principles will make new features far easier to introduce.

You can find out more about writing SOLID code in C# in Course 511 – .NET Best Practices and Design Patterns.

Type to search blog.learningtree.com

Do you mean "" ?

Sorry, no results were found for your query.

Please check your spelling and try your search again.