5

I'm refactoring a project I did for my work and I'm trying to apply the SOLID principles to make the architecture cleaner. I've run into an issue with the Dependency Inversion Principle that I can't seem to solve.

The Dependency Inversion Principle states that higher level modules shouldn't depend on lower level ones, but both should depend on abstractions. In the context of my program, I have an App class that runs the program, and a class for the MLX90614 infrared Sensor that I'm using to take object and ambient temperatures. In the initial release, the app simply depended directly on the MLX90614 driver class. But this violates the DIP.

//MLX90614.h
class MLX90614
{
    public:
    readTemperature();
}

//app.h
#include "mlx90614.h"
class App
{
   public:
   //do stuff
   private:
   MLX90614 sensor;
}

To fix this, I decided to create an abstract ISensor interface to be consumed by App and implemented by MLX90614.

//sensor.h
class ISensor
{
    public:
    virtual public readTemperature() = 0;
}

class MLX90614 : public ISensor
{
    //implements ISensor
}

//app.h
#include "sensor.h"
class App
{
    private:
    ISensor *sensor;
}

This works to remove the concrete dependency from App, but I find a few problems with this method that no tutorial on DIP seems to cover.

The first is that now MLX90614 has new introduced dependencies. This means that if I ever want to re-use the driver in a completely different application, which is highly likely, I'll have to drag the entire interface system along with it. This seems like a bit of the issue of "wanting a banana but getting the gorilla and the forest with it." If I were to re-use this code and release it as a separate library to the public there would be no use for the application specific ISensor interface, which just seems like bad package design to me.

To make matters worse, many tutorials specifically state that to fully adhere to DIP, the interface must be packaged with the higher level component (this satisfies the second rule). This makes things a whole lot more convoluted and messy, as now MLX90614 is tightly coupled with the higher level App module, and in order to re-use the driver I have to drag not only the interface, but the entire application along with it! This seems like a programming nightmare!

//mlx90614.h
#include "app.h"
class MLX90614 : public ISensor
{
    //Implements ISensor, but now is completely dependent on the App module,
    //which we definitely don't want.
}

//app.h
class ISensor
{
    //do stuff
}

class App
{
    private:
    ISensor *sensor;
}

Because of this, I fail to see how dependency inversion does more good than harm to my system. If I want the lower level modules to be highly re-useable then it seems odd to make them dependent on an entire higher-level system that's completely irrelevant to their implementation. And considering how in most systems I've programmed the lower level libraries are always far more re-useable than the upper level implementations, I fail to see how dependency inversion is useful at all.

  • While I respect you wanting to learn the DIP, I do not know if it is necessary for your application. If you only have one sensor and one app, what do you gain by using interfaces? If you have more than one app or more than one sensor, then this becomes important. – Mike Mar 29 '18 at 14:34
  • I'm thinking that in the future the sensor could be replaced with another sensor that uses its own package. Or if I want to test the rest of the application without having a sensor I can create a mock "test sensor" that can be used instead that just returns a random double value for App to use. – Ryan Mullin Mar 29 '18 at 14:55
13

I fail to see how dependency inversion is useful at all.

That's because you've failed to use it for anything yet.

You're bemoaning the fact that DIP is work, adds complexity, and does nothing for you. All of which are true at the moment. What you're failing to understand is that DIP isn't about now. It's about change.

You're predicting that using DIP will mean you can't reuse your sensor code without dragging stuff along with it. Here you have the relationship backwards.

You don't create the interface ISensor for MLX90614. You create it for the App that depends on having a sensor and doesn't care if it's MLX90614 or something else. Where before your app knew about MLX90614 directly now it is simply publishing an interface detailing what it needs from it's sensor. Whatever it is. That's a good thing. The app may live longer than MLX90614 does.

When it comes time to use MLX90614 in another app you should use a different interface. One that shows what the other app needs. Not everything MLX90614 does. That's the interface segregation principle.

For some reason you insist that MLX90614 needs to be able to be moved from app to app untouched by the experience. Maybe you're thinking of putting it in a library. If so I'd consider the Object Adapter Pattern to keep from getting it's stubbornness all over my app.

enter image description here

Do what you like with your banana. But the gorilla is the one that decides what it wants to eat and how to peel it.

  • Let's say I want to release the code for the MLX90614 as a standalone library for others to use with their devices and projects. I probably don't want to be depending on the application specific interfaces for the standalone library. In that case, would it be better to create the standalone class first, then create an Adapter when it's time to insert it into my application? Or is this adding an unneeded complexity? – Ryan Mullin Mar 29 '18 at 7:59
  • I would hope that the interface would be just as reusable as the MLX90614 itself. The interface will describe what a sensor does. That should be true whatever application you're using sensors in. – Simon B Mar 29 '18 at 8:26
  • 3
    You don't create the interface ISensor for MLX90614. You create it for the App that depends on having a sensor and doesn't care if it's MLX90614 or something else. explains everything. – Rahul Agarwal Mar 29 '18 at 8:54
  • Since MLX90614 is a standalone library and is unaware of the application specific interface(s), I think OP is asking how it can possibly implement said interface(s). Which strikes me as a very good question. – user949300 Mar 29 '18 at 11:11
  • 1
    @user949300 better? – candied_orange Mar 29 '18 at 21:35
1

If you want to have ISensor implemented by multiple sensors and consumed by multiple applications, then you have something that looks like this:

enter image description here

At this point, you have two choices:

1) Package ISensor together with Sensor 1 and Sensor 2. All applications consuming one or more sensors now reference that package.

2) Package ISensor by itself. Now all applications and all sensor packages reference the package for ISensor. Applications also must reference any concrete implementations that they want to use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.