» SOLID principles

SOLID principles

I’m applying for a job in a frankly interesting company, so I’m reviewing all the good programming practices I was taught and learned during my career. Those are essential for developing good code, however an alarming number of “programmers” don’t really understand them (or actually never heard about them).

In this and subsequent posts I will try to explain fundamental coding principles, refactor techniques, design patterns and so, as clearly as possible, and probably using ailuromaniac examples. Today we’ll speak about the SOLID principles.

SOLID defines the five basic principles of object oriented programming. When applied together, these principles will make a software easily maintainable and extendable. wiki

Index

 


S – Single responsibility principle wiki

Definition

A class should encapsulate only one responsibility, and that responsibility should be entirely encapsulated by the class.

Robert C. Martin relates the concept of “responsibility” with “reason to change”:

A class should have only one reason to change

Example

Take a look at this class:

It is obviously bearing with more than one responsibility because it has methods for a lot of different and unrelated actions. For instance, setSkin has nothing to do with calculatePathToDestination, because the color of the kitten’s fur has no effect on the calculation of a path.

From another point of view, this class may be changing for several reasons: changing the implementation of skin, changing the implementation of any of the AI branches…

The right implementation encapsulates each responsibility within objects, and the class Kitten is only dedicated to gather those objects by composition:

Now each class will have its own reason for change, and Kitten will barely do.This also means that when a class changes, none of the others will be affected by this change.

 


O – Open/closed principle wiki

Definition

A class should be open for extension, but closed for modification. This means that your super classes should be enought generic to be extended by their childs as they are.

Example

In the code below, the class Cow extends the class Animal and implements its abstact method eat:

This does not work, because cows are vegetarian, but we are forcing them to eat meat.

IMG_20160806_222008
But if we abstract the type Meat into an interface, such as Food, not only cows, but all the animals will be able to use the eat method as they prefer:

Meaning, the Animal super class is enought generic to be extended by a large and diverse amount of creatures as it is, without modifications:

And they will be happy and forever live in harmony.

IMG_20160806_222032

 


L – Liskov substitution principle wiki

Definition

In my opinion, this is one of the more complex and misunderstood principles, along with the dependency inversion principle.

It states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. Also explained this way:

If S is a subtype  of T, then objects of type T may be replaced with objects of type S.

Yeah well, this is quite abstract, let’s see it on a practical example.

Example

Here, our classes Kitten, Dog and Scorpion extend Animal and override its method pet():

If we try to replace Animal by Kitten, the system will fail, because the method pet() overriden by Kitten does not work in the same way as beHappy(). Same for Dog. And Scorpion does not even need the pet() method because seriously, did you ever try to pet a scorpion? Moreover, it breaks the YAGNI principle.

We cannot replace the type Animal by these subtypes. This violation can be solved extracting the pet() method from the superclass to an interface, this way:

So now you could replace the type Animal by any of its subtypes, and it will still work, because pet() is no longer Animal‘s method, but Touchable‘s method instead.

IMG_20160807_103310

BUT pay attention, because the code below does NOT violate the Liskov principle:

This implementation respects the principle because, as long as the precondition gluttony = 0 is true, the class Animal can be replaced by its subtype  Kitten and the system will be delivering the same result.

One of the best things about Liskov’s principle is that by applying it you are unconsciously forced to program by composition instead of inheritance, which is also a very important principle.

 


I – Interface segregation principle wiki

Definition

Many client-specific interfaces are better than one general-purpose interface.

Your interfaces should be as small and specific as possible. This will allow you to reuse them and also to avoid forcing classes to depend on methods thay they do not need.

Example

Here’s a “general-purpose” interface named IPhysicalEntity:

As you can see, it gathers everything that a physical entity may need. Here are some classes that implement this interface:

Notice that many methods were not actually needed. But if we split our huge interface into smaller ones, this way:

Then each class will only implement the interfaces that is needing at this moment, and we can get rid of unused code:

You can eventually add or remove interfaces from your classes as the specs change.

Personally, I like this principle because it makes classes more readable, for instance, extending the example above, you can quickly know the properties of an object:

It’s just like reading prose: Kittens can move (Mobile), can be petted (Touchable), can be set as a target (Targetable), and so on.

 


D – Dependency inversion principle wiki

Definition

Depend upon abstractions. Do not depend upon concretions.

Example

Easy:

This violates the rule because the object kitten is declared using a concrete type (Kitten). This is how you should have done:

We declare the object as a generic Animal, and then we assign to it an instance of the concrete type we want that animal to be.

It’s quite simple and actually I don’t think the problem with this principle is that it is misunderstood, but more like it requires a very much strong self-discipline. For instance:

Would you say that this code is SOLID?:

behavior is (very OK) declared as a generic type (Behavior). But then, in the constructor, we are assigning it an instance of KittenBehavior, which is a concretion. And we are giving this responsibilty to Kitten. From now on, our class Kitten will be eternally coupled to the KittenBehavior type.

But if we do this way:

And decide the type in the invocation:

Then Kitten will no longer depend on this concretion. This technique is called dependency injection, and allows us to indeed inject the concretion from outside the class, releasing it from a responsibility that does not deserve.

You could now change KittenBehavior by WhiteKittenBehavior, CatBehavior, or whatever XBehavior you’d like, yet Kitten will stay functional.

 

Conclusion

I’ve always been an organized person. Some people may call it OCD but well, I don’t think it’s so bad.

So you could say SOLID was an epiphany for me, because before we met I was very uncomfortable with my code but didn’t know exactly what was wrong with it. Now I know that EVERYTHING was wrong. From that day I’ve learned quite an amount of techniques and rules, and honestly, the more I discover, the dumber I feel.

Unfortunately what Kent Beck said here was true:

Adopt programming practices that “attract” correct code as a limit function, not as an absolute value. [...] you will create what mathematicians call an “iterative dynamic attractor”. This is a point in a state space that all flows converge on. Code is more likely to change for the better over time instead of for the worse; the attractor approaches correctness as a limit function.

The painfully implication of this statement is that, despite our efforts, our code will only pretend to be good, but never achieve absolute perfection.

But well, you will always be closer to perfection if you are well trained, so I’ll try to gather and demystify some of the key coding skills here. Besides, trying to clearly explain something is one of the best ways to realize that you actually don’t understand it.

In another vein, during this post I’ve been doing what we know as Refactor, a process through which the code is reestructurated without changing its external behavior, aiming to improve the quality of the software. I’ll be writing about it later in this blog, but if you feel intrigued read this.

Also in the incoming posts we’ll explain some design patterns and, buddy, you should know about them.

4 Comments on SOLID principles

  1. Rodrigo
    August 8, 2016 at 12:16 pm (12 months ago)

    Son unos muy buenos ejemplos, de lo mejor que he visto y algunos principios que no tenía muy claros los ejemplos me han aclaro algunas cosas.

    Pero como tu has dicho, el más difícil es el “Liskov substitution principle” y sigo sin tenerlo muy claro. Cuando dices que se puede sustituir Animal por Kitten (subtipo) es confuso. Se refiere a que puedes hacer:

    Animal a1 = new Animal();
    Kitten a2 = new Animal();
    Animal a3 = new Kitten();

    La clase Animal no tendría el método pet() entonces no podría ejecutarlo.

    Me gustaría que me saques de mi confusión si es posible.

    Un saludo y espero leer muchas más cosas buenas como esta.

    Reply
    • merycp
      August 11, 2016 at 2:55 pm (11 months ago)

      Hola Rodrigo,

      gracias por leerme, y me alegro que te hayan gustado los ejemplos ^^

      En cuando a lo que me comentas de Liskov, en primer lugar éste principio requiere que la clase padre pueda ser sustituida por cualquiera de sus hijos. Por lo tanto, basándonos en tu ejemplo, estaríamos hablando de:

      Animal a1 = new Kitten();
      Animal a2 = new Scorpion();

      Al principio del ejecicio esto no puede hacerse porque Kitten implementa el método pet() de una forma que produce un resultado distinto a la que produce el mismo método en Animal, y por otra parte la clase Scorpion ni siquiera estaría implementándolo.

      Por lo tanto, la clase Animal no podría sustituirse por cualquiera de sus subtipos por culpa del método pet().

      Para solucionarlo extraemos el método pet() de la clase Animal y lo colocamos en una interfaz llamada Touchable. La clase Animal no implementaría esa interfaz. En cambio, serían sus hijos los que decidirían si necesitan implementarla o no.
      Ahora podríamos sustituir cualquier instancia de Animal por cualquiera de sus hijos y todo seguiría funcionando adecuadamente.

      Espero haber resulto tu duda! Si no es así dimelo y estaré encantada de intentar explicarlo de otra forma o ser más concisa.

      Un saludo!

      Reply
      • Rodrigo
        August 15, 2016 at 12:08 pm (11 months ago)

        Lo primero gracias por contestar.

        Si he entendido bien la idea de este principio, es que si en la clase base (por ejemplo Animal) hay definido un método con una funcionalidad especifica en esa clase, si un subtipo de esa clase sobrescribe (override) ese método y cambia su funcionalidad (aplicando polimorfismo) a algo que se adapte más a su subtipo y cambiando la funcionalidad de la clase base, al intentar cambiar el tipo base por el subtipo no funcionaria de la misma manera y se violaría este principio.

        Esto sería muy difícil de cumplir ya que si tenemos una clase base y los subtipos tienen los método de la clase base y además métodos suyos propios que necesitan para funcionar al intentar crear un objeto desde la clase base no tendría acceso a esos métodos teniendo que hacer un casting para saber de qué subtipo son, esto también estaría incumpliendo el principio ya que la clase base no puede acceder a esos métodos porque no los conoce y no se podría sustituir.

        Por eso en el ejemplo que tú pones (en java), al no tener la clase base (Animal) el método pet() al crear un objeto así:

        Animal a1 = new Kitten();
        a1.pet(); // NO WORK

        Al intentar acceder al método pet() no podría porque la clase Animal no tiene ese método al no implementar la interfaz y también se incumpliría el principio.

        En algunos casos la herencia de clases sería muy difícil de hacer porque este principio es difícil de llevar a cabo. O no se podría a los subtipos darles una funcionalidad más con respecto a la clase base.

        No sé si me has entendido y se podría poner otro ejemplo y como quedaría en código que aclarara el principio y mi duda.

        Un saludo.

        Reply
        • merycp
          August 25, 2016 at 6:27 pm (11 months ago)

          Hola!

          Tienes razón, y a mi también me pareció un poco irritante al principio. Pero este principio nos hace reflexionar sobre la herencia y nos redirecciona sutilmente hacia el uso preferente de interfaces.

          En el ejemplo estoy usando polimorfismo sobre la interfaz Touchable, no sobre la superclase Animal, que como hemos visto no debe encargarse de legar el método pet() a sus subclases porque rompería nuestro amado principio.

          Esto también implica que nunca intentaríamos hacer:


          Animal a1 = new Kitten();
          a1.pet();

          para comprobar que el principio se cumple, porque pet() no es un método de Animal sino de la interfaz Touchable y por lo tanto es ajeno a todo esto.

          Sólo deberíamos centrarnos en los métodos de Animal que efectivamente sean heredados por las subclases. Por ejemplo, el método eat() que se describe en el segundo caso, es heredado y sobrescrito por Kitten, pero siempre que la precondición gluttony == 0 sea cierta, el resultado de ambos métodos es el mismo, por lo tanto en este código:


          Animal a1 = new Kitten();
          a1.setHunger(200);
          a1.eat(100);

          El método eat() de Kitten hace lo siguiente:

          @Override void eat(int foodAmount)
          {
          hunger = hunger - foodAmount + gluttony;
          }

          entonces (insisto, cumpliendo la precondición gluttony == 0), tendríamos que hunger = 200 – 100 + 0 = 100.

          Ahora sustituimos Kitten por Animal:


          Animal a1 = new Animal();
          a1.setHunger(200);
          a1.eat(100);

          El método eat() de Animal hace lo siguiente:


          void eat(int foodAmount)
          {
          hunger = hunger - foodAmount;
          }

          entonces tendríamos que hunger = 200 – 100 = 100.

          El resultado del método es el mismo en ambos casos, 100. Por lo tanto el principio de Liskov se está cumpliendo.

          Espero haber resuelto tu duda! En caso contrario no dudes en volver a preguntar, aunque tarde un tiempo siempre contesto. Y de nuevo muchas gracias por leerme y darme feedback :)

          Reply

Leave a Reply