The SOLID Principles

Yeah we start to make the Training class look ridiculously big, when in reality all we are doing is using the same method to make an animal speak.

We are currently just overriding that behaviour in the subclasses.

Here is the better implementation.

public class Training { public void TrainAnimalToSpeak(Animal anAnimal) { anAnimal.

Speak(); }}This is much better.

Now we can pass in anything that is a type of Animal, including Cat and Dog in this case.

This method is flexible enough that if we create a new animal class, we don’t need to do anymore than implement it’s Speak function in order for it to be trained on how to speak.

From a unit testing point of view, this is also great, as we can now create a mock of an Animal, and implement whatever we want during testing.

Doing so will allow us to focus more on the code inside of the TrainAnimalToSpeak method rather than the code that is implemented by the class that is inheriting from the Animal class.

Interface Segregation PrincipleOk this one was interesting to me, and for a while I got this one wrong.

I want you to think about a car, a light switch, an adult and a child.

Now a child will tend to mimic behaviour of the parent and like to turn things on.

An adult however will want to save money and not waste resources, turning things off when they don’t need them.

However when they do need them, they will turn something on.

Also an adult when required will want to drive their car to go to work or run choirs etc.

A child can’t drive.

Ok so what has all this got to do with interfaces and segregating them.

Again think about the car and the light switch.

A car can be turned on, turned off, and it can be driven.

A switch can also be turned on and off, but can’t be driven.

Based on what we know here we could say that have have the following interfaces.

public interface ITurnOn { void TurnOn(); }public interface ITurnOf { void TurnOff(); }public interface IDrivable { void Drive();}So with these interfaces in place we could implement the following.

A car has the ITurnOn, ITurnOff and the IDrivable behaviour, which means they have the TurnOn, TurnOff and Drive methods implemented respectively.

A light switch has the ITurnOn and ITurnOff behaviour, but not the IDrivable which means they have just the TurnOn and TurnOff methods implemented respectively.

Now the common mistake people make is that they stick all necessary requirements for a class into the one interface unlike the above.

Therefore:-public interface ICar { void TurnOn(); void TurnOff(); void Drive()}public interface ISwitch { void TurnOn(); void TurnOff();}Now why is this so bad.

Let’s review what a child can do, and what it shouldn’t be able to do.

Simply put we said a child can only switch things on.

In the case of the ISwitch interface, they can do that, however they also have the ability to switch things off.

Something that based on our rules, a child should not be able to do.

The ICar example is even worse.

Not only through the ICar interface do we give the child the ability to turn the car on and off, but we also give the child the ability to DRIVE!!!!.We certainly don’t want that happening.

This is why we break down our interfaces in to multiple ones ie segregation.

If we just use an ITurnOn interface for the child, it means we have ensured that the child could only ever know how to turn something on.

However for the parent we can implement all 3 interfaces, so they can turn things on and off, as well as drive when needed.

Outside of the restricting responsibility policy mentioned above, unit testing is simplified too.

By not having to mock so many methods there is less effort needed to write tests.

Dependency Inversion PrincipleFirstly to cut all the BS some people say, the Dependency Inversion Principle is NOT Dependency Injection.

So what is it?.Well the thoughts around this principle are:-High level modules should not depend on low level modules.

Both should depend on abstractions.

Abstractions should not depend on details.

Details should depend on abstractions.

Sounds a little cryptic, I know.

Firstly we should probably get an understanding of what an abstraction is.

In my own words, an abstraction is simply a way of showing the intent a certain piece of code will do, without giving away the details of how it does it.

In C# abstractions can be thought of generally using an interface, or in some cases abstract base classes.

Other things you may hear around this principle is the “Separation of concerns” and “decoupling” your classes.

These are just fancy phrases to represent this principle.

So let’s now address point one above here, and I will do so with the Child and the ITurnOn example mentioned previously.

Remember how the car and the switch can both be turned on.

That means that the Car and the Switch classes can handle turning themselves on, when asked to do so.

The child can interact with anything that can be turned on.

Therefore to the Child class, the ITurnOn interface would be considered an abstraction that the Child depends on.

Let’s see this as code.

public interface ITurnOn { void TurnOn();}public class Car: ITurnOn { public void TurnOn() { Console.

WriteLine("Engine Started"); }}public class Switch: ITurnOn { public void TurnOff() { Console.

WriteLine("Switch is on"); }}public class Child { public void TurnOnDevice(ITurnOn device) { Console.

WriteLine("Hehe dad will not know"); device.

TurnOn(); Console.

WriteLine("Oh no he noticed.

Run!!!!"); } }So in the Child class we can see it has a method called TurnOnDevice.

This method takes a parameter that is using the ITurnOn interface as an abstraction.

Both the Car and Switch implement the ITurnOn interface, meaning they can be switched on.

This allows us to write code like the following.

Child naughtyChild = new Child();Car dadsCar = new Car();Switch lightSwitch = new Switch();// Lets be naughty and turn on dads carnaughtyChild.

TurnOnDevice(dadsCar);// While we're at it, lets be more naughty and turn on the light switchnaughtyChild.

TurnOnDevice(lightSwitch);So as you can see here, the child can turn on both the car and the light switch without ever understanding how they get turned on.

The child does not know how the car engine starts up, or how the light lights up when he/she turns the switch on.

The child does not care.

This fact addresses the second point about this principle.

Abstractions allow us to utilise certain classes without having to know everything about it.

With this said, this now addresses two things.

Firstly if our classes require functionality defined in other classes (remember I mentioned this in the Single Responsibility Principle section), we must specify to the caller what abstractions we need in order to operate as intended.

This can be done either as part of constructing the class or as part of calling a method.

Secondly by demanding what abstractions we need to fulfil the work needed, we satisfy the Open/Closed principle.

Lastly by having abstractions, we can better mock up dependencies during tests so we can focus on the logic that a particular class implements without having to test all its dependencies as well.

In conclusion it’s my opinion that every developer in the IT industry should understand these principles and how they work.

It’s all good to know how to write code in a specific language, but to me at the end of the day, code is code.

What makes certain developers stand out from the rest is their understanding of software engineering and the best practices.

Understanding the SOLID principles alone helps us to build better code for others to read and maintain in the future, so please take the time to understand and apply them.

.

. More details

Leave a Reply