6

Just a thought and a question to the Stack Overflow and Microsoft Development Community about the OO software design principles called SOLID. What is the difference between the Liskov Substitution Principle and the Dependency Inversion Principle please ? I have thought about this for a while and I'm not sure of the difference. Please could you let me know ? Any thoughts / feedback very welcome.

Carlos Cavero
  • 3,011
  • 5
  • 21
  • 41
TaherHassan
  • 81
  • 1
  • 2
  • This site explains it well enough: https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design#toc-liskov-substitution-principle – auburg Oct 09 '19 at 08:51
  • The tag wikis are relevant. See [SOLID](https://stackoverflow.com/tags/solid-principles/info) and [DIP](https://stackoverflow.com/tags/dependency-inversion/info). – jaco0646 Oct 10 '19 at 13:17

3 Answers3

8

Liskov's substitution Principle states the following:

A class should be directly substitutable with its base-class

What this means is that if a child class extends a parent class, it should be directly substitutable. Image viewable here

If you take Bird for example. Not all birds fly - but some do.

Lets look at a java example:

Bird  ostrich = new Ostrich();

If I am going to treat my ostrich as a Bird (is base/parent class), and we have functionality in Bird for that ostrich to fly, even though they shouldn't!

So we can separate out the structure: Refactored hierarchy for Liskov's

Dependency Inversion is easier when thought of as a separate principle.

If we have a class call a class. It makes it very hard to change it later and it is going to need us to change source code. Again lets look at a code example.

public class App {
    public static void main(String[] args) {
        Greeting greeting = new Greeting();
        greeting.sayHello(new Friend()); 
        greeting.sayHello(new Enemy());
    }
}

public class Greeting {
    public void sayHello(Person person) {
         person.greet();
    }
}

public interface Person {
    public void greet();
}

public class Friend implements Person {
    public void greet() {
        System.out.println("Hello my friend");
    }
}

public class Enemy implements Person {
    public void greet() {
        System.out.println("Go away");
    }
}

Here we pass a parent object (Person) of both of items (Friend and Enemy). If we passed through a Friend object, we would need a separate identical method for the Enemy method. We can use the Open/Closed principle to have a single method which can call either Friend or Enemy or anything in the future which might extend Person. Dependency inversion is that rather than the sayHello() method creating a class, the Parent object is passed through. This means that which Parent object we call is dependent on App, rather than sayHello() determining which object to call.

It is better practice to use dependency inversion. The class greet() calls is not set in stone, as long as the class being passed is that which implements Person.

What this means is that instead of App being dependent on Friend. Whether Friend or Enemy gets called is dependent on App.

Passing responsibility up like this means that our code can be easily maintained and changed. Using Context and Dependency Injection, it is possible for configuration files to determine which type of object we want to use when a specified interface is referenced.

  • Hi Christoph, Many thanks for your in depth answer. That's terrific ! In response I just wanted to say that in your example class Bird might implement the fly function as a virtual function that has an empty implementation for Ostrich ? Then we are dealing with the abstraction of Bird which I think is a similar concept to your example of App dealing with the abstraction of Person ? Many thanks and best regards, Taher Hassan – TaherHassan Oct 09 '19 at 15:13
  • Hi Taher, you are 100% correct that we could just implement an "empty logic" in ostrich with a fly method. But imagine this on a large scale - passing backwards and forwards empty functionality and having functionality which doesn't do anything. It would compile, but I wouldn't consider it best practice - and that really all the SOLID principles are, guidelines to best practice and maintainability. – christophperrins Oct 09 '19 at 23:27
3

In one sense, the LSP and the DIP are opposites.

  • The LSP governs the relationship between classes in an inheritance hierarchy
    (i.e. classes that are parent and child).
  • The DIP governs relationships among classes outside an inheritance hierarchy
    (i.e. classes that are not parent and child).

For a visual, consider six potential relationships among two parent and two child classes.

  • LSP is concerned with relationships 1 and 2.
    • 1 and 2 are OK only if Child1 and Child2 are subtypes and not just subclasses.
  • DIP is concerned with relationships 3, 4, 5, and 6.
    • 3 is OK.
    • 6 is prohibited.
    • 4 and 5 can point upwards but not down.

object relationships

jaco0646
  • 15,303
  • 7
  • 59
  • 83
0

Substitution principle means that you use abstractions. Abstractions are good because they make your code more universal, reusable and valuable. For example, if your code works with an abstract Vehicle instead of a more concrete Car, then you can also use the same code without any changes also with other vehicles such as Bicycle, Truck or even Ship and it will continue to work without problems. Of course, to make this "substitution" work, you must ensure that all Vehicles follow the same contract, which usually means that they implement a common interface or extend a common superclass.

Dependency injection makes your code better because it cuts the unneccessary ties between classes. This, again, makes those classes reusable in different scenarios. For example, if your class is getting data from a file, then you will run into problems the next time your data will be in a database. Your class will suddenly become a swamp of if-then-else cases that try to deal with different things, and it will be a nightmare to maintain. Dependency injection frees your class from this responsibility - i.e. your class just "needs data" and it is the responsibility of the caller to make sure that the data is connected (aka "injected") into it. Technically, dependency injection is simply just setting a variable, such as myObject.dataSource = databaseDataSource, or myObject.dataSource = fileDataSource.

jurez
  • 4,436
  • 2
  • 12
  • 20
  • Many thanks for your comments Jurez. I completely understand the Liskov Substitution Principle and now I think I understand what the Dependency Inversion Principle is driving at. Many thanks again for your visual representation and example. These principles enable excellent structuring of the system dealing with loosely coupled abstractions wherever possible in a layered structure with underlying inheritance hierarchies and polymorphism. Very interesting discussion! Many thanks and best regards, Taher – TaherHassan Mar 29 '21 at 12:46