In this post I am covering the last of the S.O.L.I.D. Object Oriented Design Principles and probably the most famous one, the Dependency Inversion principle (a.k.a. IoC: Inversion of Control). Many design patterns are based on this principle, such as the well-known Strategy pattern [GoF]. Others like Dependency Injection [Martin Fowler] are based on this principle as well. IoC containers are current trends in OOP frameworks, Unity and Ninject are good examples.
The Dependency Inversion principle targets Abstraction. Abstraction should not depend upon details, details should depend upon abstraction. Dependency Inversion supports the implementation of the Open-Closed principle, because we can extend our detail implementations by extracting common behavior in abstract classes or interfaces, without changing the client classes.
Depending upon abstractions:
Every dependency in the design should target an interface, or an abstract class. No dependency should target a concrete class. The famous phrase of "program to the interface" practically describes the purpose of this design principle, especially because it provides low-coupling references between objects. By depending on concrete implementations our design can be prone to break other principles such as Open-Closed, Liskov Substitution and Interface Segregation. Abstraction is the key.
The principle is pictured in the following UML class diagram:
Each detail implementation is a specialization of the abstract interfaces, the high level policy deals with these abstractions only.
Layering:
All well structured object oriented architectures have clearly defined layers, with each layer providing some coherent set of services through a well-defined and controlled interface.
Abstract layers.
Code Sample:
Despite the following code is in C# it can be easily ported to Java or other OOP language.We start by defining the class Lamp whose public methods are TurnOn and TurnOff.
1: public class Lamp
2: {
3: public void TurnOn()
4: {
5: Console.WriteLine("Lamp is ON");
6: }
7:
8: public void TurnOff()
9: {
10: Console.WriteLine("Lamp is OFF");
11: }
12: }
Now, we define the class Button, this class receives a Lamp object in its constructor. Button class has a public method Push whose implementation turns the lamp on and off depending on the push state of the button.
1: public class Button
2: {
3: public Button(Lamp lamp)
4: {
5: this.Lamp = lamp;
6: this.IsPushed = false;
7: }
8:
9: protected Lamp Lamp { get; set; }
10:
11: protected bool IsPushed { get; set; }
12:
13: public void Push()
14: {
15: this.IsPushed = !this.IsPushed;
16: if (this.IsPushed)
17: {
18: this.Lamp.TurnOn();
19: }
20: else
21: {
22: this.Lamp.TurnOff();
23: }
24: }
25: }
We define a main Program class that instantiates a Lamp object and passes it to a Button when this last is created. The Button instance calls the Push method twice.
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: Lamp lamp = new Lamp();
6: Button button = new Button(lamp);
7: button.Push();
8: button.Push();
9: Console.ReadLine();
10: }
11: }
When executing this program we see the following results:
Everything looks good. We have the result that we want. We might have some problems if we want to switch the implementation of our Lamp. Our Lamp class is a concrete class, we may not want to override its behavior when we need to implement different types of lamps, this risk could break the Liskov Subtitution principle. A bigger problem would be if we want to use the same button for other devices. What if we want to use the same button to turn on and to turn off a blender? Currently, our Button only understands the definition of a Lamp.
To overcome the previous problem, we can refactor our design. We start by abstracting interfaces as the Dependency Inversion principle recommends. We define the IButton and IButtonClient interfaces as follows.
1: interface IButtonClient
2: {
3: void TurnOn();
4: void TurnOff();
5: }
6:
7: interface IButton
8: {
9: IButtonClient ButtonClient { get; set; }
10: string GetState();
11: void Push();
12: }
Our Lamp class is modified to implement the IButtonClient interface.
1: class Lamp : IButtonClient
2: {
3: public void TurnOn()
4: {
5: Console.WriteLine("Lamp is ON");
6: }
7:
8: public void TurnOff()
9: {
10: Console.WriteLine("Lamp is OFF");
11: }
12: }
Now, we implement Button concrete classes: SquaredButton (line 1) and PyrotechnicButton (line 34). Both implement the IButton interface.
1: class SquaredButton : IButton
2: {
3: public SquaredButton()
4: {
5: this.State = false;
6: }
7:
8: public IButtonClient ButtonClient { get; set; }
9:
10: protected bool State { get; set; }
11:
12: public string GetState()
13: {
14: if (this.State)
15: return "The button is pushed";
16: else
17: return "The button is released";
18: }
19:
20: public void Push()
21: {
22: this.State = !this.State;
23: if (this.State)
24: {
25: this.ButtonClient.TurnOn();
26: }
27: else
28: {
29: this.ButtonClient.TurnOff();
30: }
31: }
32: }
33:
34: class PyrotechnicButton : IButton
35: {
36: public PyrotechnicButton()
37: {
38: this.State = false;
39: }
40:
41: public IButtonClient ButtonClient { get; set; }
42:
43: public bool State { get; set; }
44:
45: public string GetState()
46: {
47: if (this.State)
48: return "The button is pushed";
49: else
50: return "The button is released";
51: }
52:
53: public void Push()
54: {
55: this.State = !this.State;
56: if (this.State)
57: {
58: this.ButtonClient.TurnOn();
59: Console.WriteLine("It is on Fire!");
60: }
61: else
62: {
63: this.ButtonClient.TurnOff();
64: Console.WriteLine("no fire..");
65: }
66: }
67:
68: }
Our class Program defines a static method ShowDependencyInversion which receives two parameters of the types IButtonClient and IButton. Main calls this static method with combinations of concrete classes of IButtonClient and IButton.
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: ShowDependencyInversion(new Lamp()
6: , new SquaredButton());
7: Console.WriteLine();
8:
9: ShowDependencyInversion(new Lamp()
10: , new PyrotechnicButton());
11: Console.ReadLine();
12: }
13:
14: public static void ShowDependencyInversion(
15: IButtonClient buttonClient, IButton button)
16: {
17: button.ButtonClient = buttonClient;
18: button.Push();
19: button.Push();
20: }
21: }
When executing the program we get these results:
It is clear now, that in our design, details are based upon abstractions. Client classes only deal with these abstractions, and we can "inject" concrete objects that implement such interfaces.
Now, with this design, we can use these buttons in other devices, like in a blender. We define the class Blender as follows.
1: class Blender : IButtonClient
2: {
3: public void TurnOn()
4: {
5: Console.WriteLine("The Blender is ON");
6: }
7:
8: public void TurnOff()
9: {
10: Console.WriteLine("The Blender is OFF");
11: }
12: }
In the Program class, we can instantiate Blender objects with our two different kinds of Buttons.
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: ShowDependencyInversion(new Blender()
6: , new SquaredButton());
7: Console.WriteLine();
8:
9: ShowDependencyInversion(new Blender()
10: , new PyrotechnicButton());
11: Console.ReadLine();
12: }
13:
14: public static void ShowDependencyInversion(
15: IButtonClient buttonClient, IButton button)
16: {
17: button.ButtonClient = buttonClient;
18: button.Push();
19: button.Push();
20: }
21: }
When we execute the program we have the following results.
Our design has been extended and classes are reusable due to deal with abstractions only. We can easily invert the objects that implement such behaviors.
Conclusions:
We have seen how the Dependency Inversion principle helps us to extend and maintain our designs, and how this is related to other principles, especially to the Open-Closed one. By programming to the interface, we make our classes less dependent to specific implementations, giving us low-coupled references.This is my last post of the S.O.L.I.D. Object Oriented Design Principles series, I hope you enjoy it.
See you soon!
[RobertMartin96] SRP: The Dependency Inversion Principle, Robert Martin, 1996