Dependency Inversion principle

The last of the SOLID principles is based on two statements, that Wikipedia states in this form:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

As for the first statement, we should clarify what we understand by high-level and low-level modules. The terminology is related to the importance of the actions performed by the module.

Let's put it simply: if a module holds the business logic of a Customers class, and another includes the format that a list of the Customers class uses in a report, the first one would be high-class and the second would be low-class.

The second statement speaks by itself. If an abstraction depends on details, the usage as a definition contract is compromised.

In the case of our sample, we still have some code that will not grow appropriately: the SportsCar creation method depends much on what the user writes in the ComboBox. There are several situations that could show this inconvenience: writing the wrong name in the brand selection procedure, adding future new brands, and so on. There is some boilerplate code in the UI that we can improve.

A final version of the sample

Without pretending that the sample is perfect (at all), the creation procedure can be extracted from the UI and delegated to another class (CarFactory) that would be responsible for calling the appropriate constructor depending on the brand. (We'll see that this technique is actually implemented using one of the design patterns we'll study later on.)

In this way, the responsibility of calling the proper constructor would be on CarFactory, and additional brands can be added more easily.

In addition, our SportsCar class will now exclusively take care of its state and business logic related to the state and not the details of Photo associations or MaxSpeed values, which seem adequate for a factory.

So, we will now have a new class (located in the same file as the SportsCar file), containing these details:

public class CarFactory
{
  SportsCar carInstance;
  public SportsCar CreateCar(string car)
  {
    switch (car)
    {
      case "Ferrari":
        carInstance = new SportsCar(car);
        carInstance.MaxSpeed = 230;
        carInstance.Photo = Properties.Resources.BMW;
        break;
      case "BMW":
        carInstance = new SportsCar(car);
        carInstance.MaxSpeed = 180;
        carInstance.Photo = Properties.Resources.BMW;
        break;
      case "Mercedes":
        carInstance = new SportsCarWithN(car);
        carInstance.MaxSpeed = 200;
        carInstance.Photo = Properties.Resources.Mercedes;
        break;
      default:
        break;
    }
    return carInstance;
  }
}

With this new version, the SportsCar class is reduced to a minimum: it declares constants, its event, its state (properties), and the only action required (Accelerate). The rest is in the hands of the CarFactory class.

The user interface is also simplified in the creation method, since it doesn't need to know which brand the user selected in order to call either constructor; it simply calls the constructor inside CarFactory and checks the result of the process in order to assign the event handlers required to show the car's notifications:

private void cboPickUpCar_SelectedIndexChanged(object sender, EventArgs e)
{
  var factory = new CarFactory();
  theCar = factory.CreateCar(cboPickUpCar.Text);
  // Event common to all cars
  theCar.LegalLimitCondition += TheCar_LegalLimitCondition;
  // Event specific to cars of type SportsCarWithN
  if (theCar is SportsCarWithN) {
    ((SportsCarWithN)theCar).SpeedLimit += TheCar_SpeedLimit;
  }
  // refresh car's properties
  txtMaxSpeed.Text = theCar.MaxSpeed.ToString();
  pbPhoto.Image = theCar.Photo;
  updateUI();
}

The runtime behavior is just the same as earlier. The difference is that with this decoupling of components, maintenance and growing are much easier.

Let's imagine that a change happens and the application now has to deal with a new type of brand: Ford, which also incorporates SpeedLimit notifications.

The only work to do is to add a picture of a Ford (a Ford GT, not to detract from the other cases…) and retouch CarFactory to add the new case structure and its values:

case"Ford":
  carInstance = new SportsCarWithN(car);
  carInstance.MaxSpeed = 210;
  carInstance.Photo = Properties.Resources.Ford;
  break;

In the UI, only one thing is required: adding the new Ford string to the selection ComboBox, and it's ready. Now, we'll be offered the new brand, and when we select it, the behavior will be as expected:

A final version of the sample

Generally speaking, there are many ways in which the DIP principle can lead to a solution. One of them is through a dependency container, which is a component, which serves or provides you with some code, injecting it when required.

Some popular dependency containers for C# are Unity and Ninject, to name just a couple. In the code, you instruct this component to register certain classes of your application; so, later on, when you need an instance of one of them, it is served to your code automatically.

Other frameworks implement this principle as well, even if they're not purely object oriented. This is the case with AngularJS, in which, when you create a controller that requires access to a service, you ask for the service in the controller's function declaration, and the internal DI system of Angular serves a singleton instance of the service without the intervention of the client's code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.201.217