4

As a practical example, imagine a Gripper class which represents a robotic gripper in a simulation. Gripper has a TryGrip method, which checks if there's a GrippableItem in the correct position (within the gripper's rectangle). If there is, the gripper takes control of the item until it is dropped:

class GripperItemLocator  //Struggling to find a good name
{
public:

    virtual std::unique_ptr<GrippableItem>
    FindItemInRange(Rect rect) = 0; 
};

class Gripper
{
public:

    bool
    TryGrip(GripperItemLocator& itemLocator)
    {
        auto item = itemLocator.FindItemInRange(CalcCurrentRect());
        if (item)
        {
            TakeControlOfItem(std::move(item));
        }       
    }

private:

    Rect
    CalcCurrentRect() const;

    void
    TakeControlOfItem(std::unique_ptr<GrippableItem> item);
};

In accordance with the dependency inversion principle, we have a GripperItemLocator abstraction in order to decouple the gripper from the rest of the world. If the where and how GrippableItem's are located in our program changes, this does not concern the gripper, only the class(es) that implement GripperItemLocator must change.

I'm wondering if we really need interface classes for this? Say we just have a regular class doing the same job:

class GripperItemLocator  
{
public:

    std::unique_ptr<GrippableItem>
    FindItemInRange(Rect rect)
    {
        //Immediate implementation
    }   
};

I realize this does no longer have the ability to select different implementations at run-time, but say we're 100% sure there won't ever be a need for this, we're only interested in the decoupling. Do we lose anything else that I'm overlooking, or is this just as good ?

  • 1
    DIP is merely a guideline which says details should depend upon abstractions - it has nothing to do with any particular programming language features such as interface or abstract classes. Indeed, the DIP applies equally to programming languages which have no concept of an interface. It's a (deliberately) language-agnostic bit of advice, since there are many different possible ways to build abstractions which have nothing whatsoever to do with classes -- DIP is about reducing coupling rather than about use of any particular patterns or language features. – Ben Cottrell Feb 10 at 12:30
  • 1
    When deciding to apply the DIP, it can make sense to treat data dependencies differently from behavioural dependencies. To inject different behaviour or different implementations you do need interfaces or function pointers. But to inject different data (e.g. for testing) a normal pointer or reference can be sufficient. So the question is: are you more concerned with providing the GrippableItem data or the FindItemInRange() behaviour to the Gripper::TryGrip() method? – amon Feb 10 at 14:15
  • @amon The concern is that the where and how GrippableItems are stored could require change over time if the world around the gripper changes. A second conveyor might be installed, each conveyor now holding it's own collection of GrippableItems. We want to decouple the Gripper from such changes, only GripperItemLocator would require an update to match the new situation. But we do not require run-time polymorphism, and such changes would be rare. The program would only contain a single GripperItemLocator implementation at a time. – Unimportant Feb 10 at 17:27
  • Note that your noun should be a verb here. This language has free verbs (it's not Java). Instead of having a GripperItemLocator class you can have a LocateGripperItme function. – user253751 Feb 10 at 18:23
  • (Comment 1 of 2) DIP suggests that, based on past experience, the "interface" (i.e. the way a caller (interface consumer) tells a callee (interface implementer) to do something) tend to be more stable than the caller's code and the callee's code. Thus, the "interface" lasts longer (between necessary changes), and therefore can be made "more dependable" than either. However, for software projects that change more rapidly, one may need to be prepared to eschew the best practices, i.e. allow the software to be in a more messy state than software projects that are more stable. – rwong Feb 11 at 1:16
5

The DIP is not an end in itself, it is a means to an end.

Let us, for example, assume you use the DIP for unit testing. If you want to be able to test Gripper in isolation, without the "real" GripperItemLocator, then you need a way to replace the latter by a mock. The canonical way of doing this is by using an interface, or in C++ an abstract virtual base class.

But if you say it is sufficient for you to create tests for Gripper always using a real GripperItemLocator object, then you can stick with that approach and don't provide an interface / virtual base class.

How to make this tradeoff depends heavily on how complex the classes are, how simple or hard it is to debug them when you cannot test them in isolation, and how much time you need to fix an error in case one occurs, but you don't know in which of the two classes the root cause is hidden.

|improve this answer|||||
  • If I want to test Gripper in isolation I could just provide it with a mock fixed GrippableItemLocator class in the test-program. Why does it need to be a interface/virtual base class if I don't require run-time polymorphism? – Unimportant Feb 10 at 17:36
  • @Unimportant: you are right, this is not mandatory. Using an interface/virtual base class is the standard approach "by the book", which works by a run-time mechanics in different programming languages, even when "Gripper" is part of a lib designed to follow the OCP. But if one can use a compile-time mechanics to replace GripperItemLocator by a mock, that might be ok, too. – Doc Brown Feb 10 at 19:27
  • ... however, one has to check if this does not result in huge build times - replacing each "injectable" class by a mock using a compile time mechanics results in compiling the same class for each "mock" once again, instead of compiling the class once. In larger systems, this can become a serious issue. – Doc Brown Feb 10 at 19:33
1

The 'Inversion' part of dependency inversion requires interfaces, or interface like constructs.

The idea is your class with a dependency no longer specifies what that dependency is. instead the calling code tells it what the dependency is.

The interface just says, whatever you pass me, I'm gona try calling it like this.

If you used a class or even an abstract class to define that you would want that class to be empty of implementation. Effectively an interface.

|improve this answer|||||
1

The dependency inversion principle aims at decoupling high level from low level classes with the help of an interface: high level classes use the interface, and low level classes implement the interface.

The usual C++ equivalent to an interface is an abstract class that has only pure virtual member functions. However, there are other alternatives.

In fact, any polymorphic class could be used for dependency inversion, provided the high level class would only invoke virtual member functions thereof. Example:

class GripperItemLocator  
{
public:    
    std::unique_ptr<GrippableItem>
    virtual FindItemInRange(Rect rect)    // <-- it should be virtual
    {
        //Immediate implementation
    }   
};

You could later define a derived class that overrides the virtual function. Overriding means that even the Gripper, would use the overridden function if the constructor is provided with the derived class.

class GripperFragileItemLocator  : public GripperItemLocator
{
public:

    std::unique_ptr<GrippableItem>
    FindItemInRange(Rect rect) override 
    {
        //Immediate implementation that takes extra care of fragile items
    }   
};

This is still dependency inversion: you do not depend on a single implementation, but you depend on something that is abstract, that can be specialized further.

However, be very careful. If FindItemInRange() would not be virtual, the Gripper would always invoke this function, even if you'd provide a derived GripperItemLocator with its own implementation. So it would look like dependency inversion, but it's not: In reality Gripper would fully depend on a lower level concrete class. itemLocater would then just be an argument like any other (without inverting anything).

Using an interface is therefore the safest approach: no risk of accidentally using a non virtual member, no risk of (mis-)using an abstraction that is not sufficiently abstract. The boundaries are very clear.

One word about templates

When R.C.Martin wrote his seminal article about dependency inversion in May 1996, the C++ templates did not exist.

Template provide some other way to design high level elements that do not depend on the low-level: a template just uses some lower level abstraction and makes assumptions about the interface it should provide. A style that uses this approach extensively is policy driven design.

However, while it achieves similar goals, it is significantly different from DIP: templates do de facto define the interface that the lower-level class should implement. So if the higher level depends on an abstraction, the lower level still depends on the higher level and not on an abstraction itself.

|improve this answer|||||
0

You can use a baseclass instead of an interface, and pass instances of the base class or a subclass. If you use the pattern for testing, then a stub class that you try to substitute is likely not derived from the baseclass but totally independent so you lose out.

You can use a fixed instance of a fixed class, but then dependency injection becomes rather pointless. Not saying you can’t do it, but why would you?

Of course if you started with a baseclass and figure out you should have used an interface, that’s a trivial change. Unless you used knowledge about the class that you shouldn’t have.

|improve this answer|||||
  • "You can use a fixed instance of a fixed class, but then dependency injection becomes rather pointless. Not saying you can’t do it, but why would you?" To isolate the Gripper from the rest of the world. The Gripper would no longer depend on the concrete way GrippableItems are stored and any changes to this would only require GripperItemLocator to be updated. We do not require run-time polymorphism and the program would only contain a single GripperItemLocator implementation at a time. What does a virtual base class buy me extra that a fixed class doesn't ? – Unimportant Feb 10 at 17:34
  • You don’t make any sense. If the class is fixed, there is no need for dependency injection. – gnasher729 Feb 11 at 8:11

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.