[OOP] Five Principles of Object Orientation (SOLID) - Dependency Inversion Principle (DIP)
[OOP] Five Principles of Object Orientation (SOLID) - Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that an object should depend on high-level modules rather than low-level modules.
That sounds a bit complicated. What exactly is a high-level module, and what's a low-level module?
- High-level module: an abstract form of an object, such as an interface, or an abstract concept
- Low-level module: a concretely implemented object
The definitions of high/low-level modules are as above. Substituting this definition into the Dependency Inversion Principle, we can rephrase it as: an object should depend on interfaces rather than on other objects. In other words, this can be interpreted to mean that, as much as possible, inheritance of objects should occur through interfaces.
Let's use an example to see the difference in code between not following the Dependency Inversion Principle and following it.
Everyone has probably played a game at some point in their childhood about maple leaves. Although it's since been shut down, the author was a big fan of it too, and even as an adult, would return to play whenever a vacation-season event came around.
That game was an RPG, and like any RPG, it had a variety of classes along with a matching skill/weapon system.
JAVA
import java.util.Random; /** * One-handed sword object * * @author RWB * @since 2021.08.17 Tue 01:36:44 */ public class OneHandSword { private final String NAME; private final int DAMAGE; /** * OneHandSword constructor function * * @param name: [String] weapon name * @param damage: [int] damage */ public OneHandSword(String name, int damage) { NAME = name; DAMAGE = damage; } /** * Attack damage-returning function * * @return [int] attack damage (damage +-5) */ public int attack() { return DAMAGE + new Random().nextInt(10) - 5; } /** * Object string-returning function * * @return [String] name */ @Override public String toString() { return NAME; } }
Among the many weapons is a one-handed sword, implemented here as the OneHandSword object. A character could equip a weapon like this. When creating an instance, the weapon's name and damage are provided as input.
JAVA
/** * Character object * * @author RWB * @since 2021.08.17 Tue 00:46:15 */ public class Character { private final String NAME; private int health; private OneHandSword weapon; /** * Character constructor function * * @param name: [String] name * @param health: [int] health * @param weapon: [OneHandSword] weapon */ public Character(String name, int health, OneHandSword weapon) { NAME = name; this.health = health; this.weapon = weapon; } /** * Attack damage-returning function * * @return [int] attack damage */ public int attack() { return weapon.attack(); } /** * Damaged function * * @param amount: [int] damage taken */ public void damaged(int amount) { health -= amount; } /** * Weapon-changing function * * @param weapon: [OneHandSword] weapon */ public void chageWeapon(OneHandSword weapon) { this.weapon = weapon; } /** * Character info-printing function */ public void getInfo() { System.out.println("Name: " + NAME); System.out.println("Health: " + health); System.out.println("Weapon: " + weapon); } }
This is the Character object implementing the game character. Some of the basic actions a game character can take are implemented, and when an instance is created, the character's name, health, and weapon are provided as input.
But as everyone knows, one-handed swords aren't the only weapon type. Melee weapons alone can include two-handed swords, daggers, spears, axes, blunt weapons, and more, in a wide variety. Yet this Character object is structured so that it can't use anything except a one-handed sword to begin with. That's because when a Character instance is created, it has a dependency on OneHandSword. The attack() method, which handles the attack action, also depends on OneHandSword.
In this situation, using any weapon other than a one-handed sword requires changing Character's code. In other words, this violates the Open-Closed Principle discussed earlier. The bigger problem is that you'd have to do this every single time the weapon changes.
If the code above had properly followed the Dependency Inversion Principle, none of this would be a concern. The biggest problem with the code above is that it depends on an already fully implemented low-level module. In other words, it needs to be refactored to depend on an abstract, high-level module instead.
JAVA
/** * Attack interface * * @author RWB * @since 2021.08.17 Tue 02:07:19 */ public interface Attackable { /** * Abstract attack function * * @return [int] attack damage */ int attack(); /** * Abstract object string-returning function * * @return [String] name */ @Override String toString(); }
First, we create the high-level module, the Attackable interface. It declares an abstract function attack() that returns attack damage, and an abstract function toString() that returns the weapon's name. From now on, every weapon object capable of attacking will implement this interface.
JAVA
import java.util.Random; /** * One-handed sword object * * @author RWB * @since 2021.08.17 Tue 01:36:44 */ public class OneHandSword implements Attackable { private final String NAME; private final int DAMAGE; /** * OneHandSword constructor function * * @param name: [String] weapon name * @param damage: [int] damage */ public OneHandSword(String name, int damage) { NAME = name; DAMAGE = damage; } /** * Attack damage-returning function * * @return [int] attack damage (damage +-5) */ @Override public int attack() { return DAMAGE + new Random().nextInt(10) - 5; } /** * Object string-returning function * * @return [String] name */ @Override public String toString() { return NAME; } }
This is the one-handed sword object OneHandSword, now implementing Attackable. Aside from now implementing the interface, nothing much else has changed.
JAVA
/** * Character object * * @author RWB * @since 2021.08.17 Tue 00:46:15 */ public class Character { private final String NAME; private int health; private Attackable weapon; /** * Character constructor function * * @param name: [String] name * @param health: [int] health * @param weapon: [Attackable] weapon */ public Character(String name, int health, Attackable weapon) { NAME = name; this.health = health; this.weapon = weapon; } /** * Attack damage-returning function * * @return [int] attack damage */ public int attack() { return weapon.attack(); } /** * Damaged function * * @param amount: [int] damage taken */ public void damaged(int amount) { health -= amount; } /** * Weapon-changing function * * @param weapon: [Attackable] weapon */ public void chageWeapon(Attackable weapon) { this.weapon = weapon; } /** * Character info-printing function */ public void getInfo() { System.out.println("Name: " + NAME); System.out.println("Health: " + health); System.out.println("Weapon: " + weapon); } }
This is the game character Character object. You can see that the parameter type has changed from OneHandSword to the higher-level module Attackable. The same is true for every other weapon-related method.
Since the dependency has shifted from a single concrete low-level module to a high-level module, Character can now handle any object that implements Attackable. Since it's assumed throughout the game system that every attackable weapon implements Attackable, it can now use any weapon capable of attacking.
Thanks to this change, Character's code no longer needs to change when the weapon changes, so the Open-Closed Principle is also upheld.
The Dependency Inversion Principle is a principle aimed at pursuing code extensibility and reusability. A flexible, unimplemented interface offers much greater potential for extension than a rigid, concrete object.
Compared to the other principles, the Dependency Inversion Principle is somewhat less critical, since it's essentially a byproduct of the other principles. As mentioned above, following the Open-Closed Principle naturally leads to following the Dependency Inversion Principle as well. The same goes for following the Single Responsibility Principle, where 1 object = 1 responsibility, and the Interface Segregation Principle, which pursues splitting functionality into interfaces.
When creating objects, appropriately distinguish between what should be implemented as a concrete object and what should be implemented as an interface, so that you build correct dependency relationships. It would also be a great approach to follow the principles just mentioned at the same time, killing two birds with one stone.
This chapter concludes the series on object orientation. Despite having used JAVA, an object-oriented language, all this time, I've come to realize just how little I actually understood about object orientation. And just how non-object-oriented my coding style really was....
Perhaps the single biggest characteristic of object orientation is inheritance. Given that most of the five principles of object orientation are directly or indirectly related to inheritance, it's fair to say that inheritance is the very identity of object orientation. That's exactly why inheritance is such an important yet difficult concept to understand in object orientation. And using it well is even harder.
Of course, fully realizing the intent of object orientation and adhering to every established principle would be extremely difficult even for a senior-level developer. Even I find myself thinking, "Am I really supposed to design while keeping all of this in mind?" — and that's because most development tends to focus more on the outcome of the behavior than the implementation process itself.
Solid planning, along with the deliberation that comes with it, does enable robust design, but there's only so much time you can spend on it. No matter how solidly a project is being designed, if you fail to meet the development deadline, not only the final product but all the deliberation that went into producing it risks being written off as wasted time.
Since these principles need to be implemented within a fixed amount of time, mastering object orientation well will require a great deal of effort.

![[SSL] SSL Certificates](https://user-images.githubusercontent.com/50317129/129755999-c5d6c474-d5c0-442a-b7c5-37b3cdf703a9.png)