Reputation: 901
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
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
Upvotes: 0
Views: 898
Reputation:
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.
Upvotes: 3
Reputation: 3961
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.
Upvotes: 0
Reputation: 48193
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:
You can read more about these principles in The Principles of OOD article.
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.
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:
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:
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:
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.
The original paper link seems broken now but you can read more about DIP here.
Upvotes: 2