Dependency Inversion Principle insides

846 views Asked by At

I have been reading about solid OOP principles (Dependency Inversion Principle) and did not quite get how does this work.

When one class knows explicitly about the design and implementation of another class, changes to one class raise the risk of breaking the other class.

let say I have student which depends on Course if I will change course how it will affect to student. is it the same to use DI I mean DI replace new operator so whats then? student still depends on course
can you give some examples please.
thanks

public class Student {
.....
private Course course = new Course(); 
}

updated 1

(scenario) if I will assume that class has only default constructor and it will never use any instance variables to instantiate like new Course(name, .......)

updated 2

Example

public class Copy {
    @Autowired
    private Writer writer;
.....
}

public interface Writer{
    void write();
}


public class PrinterWriter implements Writer {
.....
}

public class DiskWriter implements Writer {
....
}

Now what happens is that our copy module needs to know about a printer and a disk, you can imagine those magical if-else statements that come to rescue us in these situations. As the new requirements emerge along the way, you probably add more and more dependencies to this copy module. At the end of the day, you would end up with a very complex, hard to maintain and hard to understand design.

Can you show in this example where specifically dependency inversion eliminates use of magical if-else statements with simple example please

3

There are 3 answers

0
Florian Salihovic On

I'd add a level of indirection: class Curriculum. The class coordinates the participants of a course and may be the single point of change.

interface CourseSystem {

  enum Role { PROFESSOR, STUDENT, STAFF };

  /**
   * Abstraction over a course. It does not know
   */
  interface Course {
    boolean addParticipant(Participant participant)
  }

  interface Curriculum {
    boolean register(Participant participant);
  }

  interface Participant {
    default void setCurriculum(final Curriculum curriculum) {
      curriculum.register(this);
    }
  }
}

From here you could implement concrete Professor, Student or Staff classes, which have dedicated privileges etc.

1
AudioBubble On

Take an scenario where, its certain that Course has to modify the constructor. Lets say, a new parameter is getting added, and there is no default constructor in Course class.

public class Course{
  public Course(String name){}
}

Now Student class will have compiler error.

public class Student {
   private Course course = new Course(); //ERROR !! no such constructor exist 
}

With DI, this is how Student class can be implemented using constructor:

public class Student {
   private Course course;
   public Student(Course course){
      this.course=course;
   }
}

So here, irrespective of changes in Course, class Student is intact. Same can be done using property mutators or field getter-setter methods.

Edited: There are several other cases, where your Student class would require changes. Requirement for introducing new Course types Optional and Mandatory or Prerequisite. Creating instance of Course with values from db, or some-other data-source. Code re-factoring by tools like IDE.

1
Ali Dehghani On

Prologue

Dependency Inversion Principle (DIP) was first introduced in one of Bob Martin's papers in a C++ report. In that paper he enumerated five following principles as the The Principles of Object Oriented Design, aka SOLID principles:

  1. The Single Responsibility Principle (SRP)
  2. The Open Closed Principle (OCP)
  3. The Liskov Substitution Principle (LSP)
  4. The Interface Segregation Principle (ISP)
  5. The Dependency Inversion Principle (DIP)

You can read more about these principles in The Principles of OOD article.

Definition

The formal definition uncle bob gave in the paper was:

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.

Example

One of the examples that uncle bob gave in his paper was the Copy Program, that reads from a keyboard and writes to a printer:

enter image description here

Here the copy module is depend upon two other modules, so far seems a very reasonable design. As a matter of fact, these two used modules are nicely reusable. Any other abstraction that needs to read from a keyboard or write to a printer could possibly reuse their provided functionalities.

However, the copy module is not reusable in any context that does not involve a keyboard or a printer (By the way, those are very implementation specific). For example, suppose that instead of just writing to a printer, we sometimes need to write to a disk, too:

enter image description here

Now what happens is that our copy module needs to know about a printer and a disk, you can imagine those magical if-else statements that come to rescue us in these situations. As the new requirements emerge along the way, you probably add more and more dependencies to this copy module. At the end of the day, you would end up with a very complex, hard to maintain and hard to understand design.

So, what was wrong with our poor copy module? The problem was instead of depending on Abstractions, it depends on Concrete implementations of readers and writers. In order to solve this problem, the copy module should depend on abstract definitions of a Reader and a Writer:

enter image description here

Now that our copy module depends upon two other abstractions, we can easily switch between different implementations by passing those implementations to the copy module.

Further Reading

The original paper link seems broken now but you can read more about DIP here.