Reputation: 3470
I am trying to understand and apply SOLID principles. Regarding the Dependency Inversion Principle, does it means that composition/aggregation to an object is forbidden? So that an interface must always be used to access another class method?
I mean that:
class ServiceClass {
void serviceClasshelper();
}
class MainClass {
void MainClass(ServiceClass service); // To use serviceClasshelper
}
Must be changed to:
class ServiceInterface {
virtual void interfaceHelper() =0;
}
class ServiceClass : public ServiceInterface {
void serviceClasshelper();
void interfaceHelper() { serviceClasshelper(); };
}
class MainClass {
void MainClass(ServiceInterface service); // Uses interfaceHelper
}
I think (or at least I hope) that I understand the principle. But wonder if it can be rephrased like that. Indeed, stuff I read about DIP suggest to use interfaces.
Thanks!
Upvotes: 2
Views: 818
Reputation: 5707
Basically, the main idea of DIP is:
- 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.
As you can see, it says should and not must. It does not forbid you from doing anything.
If your classes composite of / aggregate to
other specific classes
(not interfaces / abstract classes
), it is fine! Your code will still compile and run, no warnings will be displayed telling you: "hey, you are violating DIP". So I think the answer to your question is: No, it doesn't.
Imagine your system is composed of a thousand classes, you can just apply DIP to 2 classes and have one of them depends on the 3rd specific class (not interfaces / abstract classes
). As long as your problem is solved, nothing matters. So try to keep your solution short & simple -> easy to understand. Trust me you will find it valuable 1 month after when you look back at your solution.
DIP is a guideline which tells you what to do when you face a specific set of problems in order to solve them. It is not a magical guideline, it comes at a cost: complexity. The more you apply DIP the more complex your system will be. So use it wisely. To further support this point, I recommend you to take a look at this reference (taken from Head First: Design Patterns
book). Access this link (it is a PDF file) and at the top bar navigate to page 635 / 681
. Or if you are lazy enough, just read the below quote:
Your Mind on Patterns
The Beginner uses patterns everywhere. This is good: the beginner gets lots of experience with and practice using patterns. The beginner also thinks, “The more patterns I use, the better the design.” The beginner will learn this is not so, that all designs should be as simple as possible. Complexity and patterns should only be used where they are needed for practical extensibility.
As learning progresses, the Intermediate mind starts to see where patterns are needed and where they aren’t. The intermediate mind still tries to fit too many square patterns into round holes, but also begins to see that patterns can be adapted to fit situations where the canonical pattern doesn’t fit.
The Zen mind is able to see patterns where they fit naturally. The Zen mind is not obsessed with using patterns; rather it looks for simple solutions that best solve the problem. The Zen mind thinks in terms of the object principles and their trade-offs. When a need for a pattern naturally arises, the Zen mind applies it knowing well that it may require adaptation. The Zen mind also sees relationships to similar patterns and understands the subtleties of differences in the intent of related patterns. The Zen mind is also a Beginner mind — it doesn’t let all that pattern knowledge overly influence design decisions.
Finally, I will point you to a Gang of Four design pattern that make use of DIP: Strategy
Example problem: A Character
can use 3 types of weapon: Hand
, Sword
, & Gun
. He (Character
) can swap his current weapon whenever he wants.
Analysis: This is a very typical problem. The tricky part is how to handle weapon swapping at run-time.
Candidate solution with Strategy: (just a sketchup):
weapon = new Hand();
weapon.Attack(); // Implementation of Hand class
weapon = new Sword();
weapon.Attack(); // Implementation of Sword class
weapon = new Gun();
weapon.Attack(); // Implementation of Gun class
Other design patterns & frameworks that make use of DIP:
Upvotes: 2
Reputation: 16182
Yes, that is right, DIP says that you need to depend on the abstraction (the interface) and not on the concretion (the class with actual implementation).
The idea is that if you depend on ServiceClass
, you depend on this specific implementation and you can't easily replace it with another implementation. For example, you have AnotherServiceClass
, but to use it instead of ServiceClass
, you would have to inherit it from the ServiceClass
which can be not desirable or even not possible. While having the interface dependency, you can easily do this.
Update: here is more specific example illustrating the idea above
// Service class does something useful (sendNetworkRequest)
// and is able to report the results
class ServiceClass {
void reportResults();
void sendNetworkRequest();
}
// Main logger collects results
class MainLogger {
void registerService(ServiceClass service);
}
We have the ServiceClass
which we pass to the MainLogger::registerService
, the logger (for example) calls the service->reportResults()
periodically and saves to the file.
Now, imagine that we have another service:
// Service class does something useful (calculateYearlyReport)
// and is able to report the results
class AnotherServiceClass {
void reportResults();
void calculateYearlyReport();
}
If we just use concrete classes here and inherit the AnotherServiceClass
from the ServiceClass
, we will be able to pass it to the MainLogger::registerServcie
, but besides violating DIP
, we will also violate LSP
(as the subclass can not be used as a substitute of the BaseClass) and ISP
(as we clearly have different interfaces here - one to report results and another to do the useful job) and you'll also violate one more good rule to prefer composition over inheritance.
Another disadvantage of passing the instance of ServiceClass
directly as now you can't guarantee that MainLogger
doesn't rely on the internal structure (access other methods / members, etc).
One more point is the ease of the code maintenance - when you see that interface is passed, you just need to review the "protocol" of the communication between two objects. While with concrete class, you will actually need to go through the implementation to understand how the passed object is used.
So in general it is better to follow the SOLID and other OOP principles where possible, it makes code cleaner and easier to support and let's you avoid mistakes which can be hard to fix later.
Upvotes: 1