SOLID – (DIP) The Dependency Inversion Principle

Previous post in this series:
SOLID – (ISP) The Interface Segregation Principle

A. HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS

B. ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.

The Dependency Inversion Principle, Robert C. Martin

The DIP is part and parcel with the OCP and LSP. Let’s start with a simple bit of source code based on Uncle Bob’s work.

Example 1: Copy Program (based on Figure 1 and Listing 2 of source material)

public class ReadKeyboard
{
	public string Read()
	{
		return Console.ReadLine();
	}
}

public enum OutputDevice { Printer, File }

public class CopyToDevice
{
	public void Copy(OutputDevice Device)
	{
		ReadKeyboard rk = new ReadKeyboard();
		while (true)
		{
			string result = rk.Read();
			if (result == string.Empty)
				break;
			if (Device == OutputDevice.File)
				WriteToFile(result);
			else if (Device == OutputDevice.Printer)
				WriteToPrinter(result);
		}
	}

	private void WriteToPrinter(string result)
	{
		/* throw new NotImplementedException(); */
	}

	private void WriteToFile(string result)
	{
		/* throw new NotImplementedException(); */
	}
}

It should be obvious now that this violates the OCP. Our CopyToDevice is dependent on ReadKeyboard and only gets input from the keyboard. Not only that but anytime we want to support a new output device we have to change CopyToDevice to support it. If our high level module, CopyToDevice, were to utilize abstractions rather then being dependent on the low level modules (i.e. ReadKeyboard, OutputDevice) then we wouldn’t have to worry about violating the OCP. Of course to do this the low level modules would have to implement abstractions that we could use in the high level, which therefore proves point A in the definition of the DIP.

Below is a solution that does not violate the OCP and adheres to the DIP.

Example 2: The OO Copy Program (based on Figure 2 and Listing 3 of source material)

public abstract class Reader
{
	public abstract string Read();
}

public abstract class Writer
{
	public abstract bool Write(string Value);
}

public class KeyboardReader : Reader
{
	public override string Read()
	{
		return Console.ReadLine();
	}
}

public class CopyToDevice
{
	public void Copy(Reader Reader, Writer Writer)
	{
		while (true)
		{
			string result = Reader.Read();
			if (result == string.Empty)
				break;
			Writer.Write(result);
		}
	}
}

I’ve left the higher level Writer as an exercise to the reader. CopyToDevice can work without be dependent on any specific writer or reader and won’t have to be modified anytime new devices are added. As Uncle Bob would say, “There will be no interdependencies to make the program fragile or rigid. And Copy() itself can be used in many different detailed contexts. It is mobile.”

Here is another sample relating to abstracting away the details so that your code doesn’t get locked into rigidity.

Example 3: Naive Button/Lamp Model (based on Figure 5 and Listing 5 of source material)

public class Lamp
{
	public void TurnOn()
	{
		ActivateElectricity();
	}

	public void TurnOff()
	{
		DeactivateElectricity();
	}

	public bool IsActivated { get; private set; }

	private void ActivateElectricity() { throw new NotImplementedException(); }
	private void DeactivateElectricity() { throw new NotImplementedException(); }
}

public class Button
{
	Lamp lamp;
	public Button(Lamp Lamp)
	{
		this.lamp = Lamp;
	}

	public void ChangeDetected()
	{
		if (lamp.IsActivated)
			lamp.TurnOff();
		else
			lamp.TurnOn();
	}
}

What’s interesting to me is that the above isn’t necessarily wrong or bad. Again this gets down to ignoring the rules on the paint can. If you have a very small project with a very small scope and per the requirements you never need to do anything other then what is above then there is nothing wrong with it. But in most cases that won’t happen. The problems here are obvious, we need to abstract away a lot of the details.

Example 4: Naive Button/Lamp Model (based very loosely on Figure 6 and Listing 6 of source material)

public abstract class ButtonClient
{
	public abstract void TurnOn();
	public abstract void TurnOff();
	public abstract void Toggle();
}

public abstract class Button
{
	public ButtonClient ButtonClient { get; private set; }
	public Button(ButtonClient ButtonClient)
	{
		this.ButtonClient = ButtonClient;
	}

	public abstract bool GetState();

	public virtual void ChangeDetected()
	{
		if (GetState())
			ButtonClient.TurnOff();
		else
			ButtonClient.TurnOn();
	}
}

public class Lamp : ButtonClient
{
	public override void TurnOn() { throw new NotImplementedException(); }

	public override void TurnOff() { throw new NotImplementedException(); }

	public override void Toggle()
	{
		if (IsActivated)
			TurnOff();
		else
			TurnOn();
	}

	public bool IsActivated { get; private set; }
}

public class ToggleButton : Button
{
	bool isDown;
	public ToggleButton(ButtonClient ButtonClient)
		: base(ButtonClient)
	{
		this.isDown = false;
	}

	public override bool GetState()
	{
		return isDown;
	}

	public override void ChangeDetected()
	{
		isDown = !isDown;
		this.ButtonClient.Toggle();
	}
}

Here we can see pretty clearly part B of the DIP that the details of the implementations are dependent on the abstractions.

To me, however, this last example is completely broken. It works under the assumption that a Button has a Lamp. And this doesn’t make any sense. Quite clearly a lamp has a button. I think this would be done cleaner with events (the observer pattern). This isn’t just because this is how it is actually done in the UI but because, again, the “has a” definition of objects.

Or maybe I’m over-analyzing this example. There is no doubt in my mind that this last example does a great job in demonstrating part B so maybe I just need to take it at that.

Again, higher level modules should not depend on abstractions and abstractions should not depend on details.

That’s it for the DIP and each of the individual principles. As I like to do, I’ll do a summary post next week with links to each of the posts in this series.

Thanks,
Brian

Leave a Reply