1

Let’s say we have the following

abstract class HungryAnimal {

    private MovementStrategy movementStrategy;

    public HungryAnimal(MovementStrategy movementStrategy) {
        this.movementStrategy = movementStrategy;
    }

    public void searchFood() {
        while (/* no food found */) {
            ...
            movementStrategy.move();
            ...
        }
    }
}

and the movement strategies

interface MovementStrategy {
    void move();
}

class WalkStrategy implements MovementStrategy {
    void move() {
        // Walking
    }
}

class SwimStrategy implements MovementStrategy {
    void move() {
        // Swimming
    }
}

Since each hungry animal has one specific way to move we directly instantiate the according movement strategies in the concrete animals’ constructors:

class Wolf extends HungryAnimal {
    public Wolf() {
        super(new WalkStrategy());
    }
}

class Shark extends HungryAnimal {
    public Shark() {
        super(new SwimStrategy());
    }
}

While in this concrete example this solution seems to be okay it might become a problem for testing because we cannot easily mock our movement strategies.

If instead our concrete animal classes would look like this

class Wolf extends HungryAnimal {
    public Wolf(MovementStrategy movementStrategy) {
        super(movementStrategy);
    }
}

class Shark extends HungryAnimal {
    public Shark(MovementStrategy movementStrategy) {
        super(movementStrategy);
    }
}

we could use constructor injection to inject the proper dependencies while maintaining testability.

QUESTION

As nobody stops the injector from injecting the SwimStrategy for the wolf or the WalkStrategy for the shark who is actually responsible for making sure that the “right” dependencies are injected? Is this some kind of contract you would document in a comment at the top of the class definition? Or would it be right not to have MovementStrategy as a dependency for Wolf and Shark but turn SwimStrategy and WalkStrategy into interfaces that extend MovementStrategy and instead use these as dependencies for the concrete animals?

I’m struggling to find a reasonable argument regarding how strict one should be when defining the types of dependencies. I really appreciate some insights about best practices or helpful metrics to narrow down this question!

3
  • 3
    Are you sure you actually want to use a mock here? If a Wolf must have a WalkStrategy, then the strategy is only an implementation detail of that class, and you should test it as such. It might also be worth saying that the point of the Strategy pattern is to select a correct strategy at runtime. If you know what strategy to use at compile time, maybe it's not the correct pattern to use. Feb 11, 2018 at 20:07
  • If Wolf and Shark exist only as a () constructor onto HungryAnimal, does it matter that you can't mock their dependencies?
    – Caleth
    Feb 12, 2018 at 9:50
  • Hi Vincent, thanks for your comment! I agree that WalkStrategy is an implementation detail. Why does this mean that I should not be able to mock it? If Wolf had some public methods that use the WalkStrategy it might be very handy to just use a simple fake implementation to test them. As for the strategy pattern: You are right, this was some poor naming on my side.
    – Antimon
    Feb 12, 2018 at 22:46

3 Answers 3

3

I think in this case one should not care so much about the enforcement of the correct WalkStrategy, but making it more likely to use the correct strategy than to use a wrong one.

Providing a Wolf() and a Shark() constructor with no arguments, with the correct movement strategy encoded there as default, makes it easy for a user of those classes to instantiate them correctly. For mocking purposes (or DI in general), you could additionally provide constructors Wolf(MovementStrategy movementStrategy) and Shark(MovementStrategy movementStrategy), with a comment that these are only for testing.

As a result, you don't forbid to inject the wrong dependencies, but you make it unlikely for someone to initialize those objects accidentally wrong.

Of course, it is possible to design the classes in a way where a Wolf can only get a WalkStrategy injected, and a Shark only a SwimStrategy, but before you do so, ask yourself if it is really worth the hassle, or if that is not a step towards overdesign.

2
  • Hi Doc Brown, thank you for your answer. I really like your idea to provide additional constructors for DI! Nevertheless, I still feel a bit uncomfortable to let a class lie about what it really needs. Please, see my comment under Mark Seemann’s answer to know what I mean. But of course you are right, paying attention to potential overdesign is certainly important.
    – Antimon
    Feb 12, 2018 at 22:28
  • @Antimon: a constructor "not telling the full story up to all details" is not "lying", the latter would be "telling a wrong story". Full type safety is IMHO overrated, but if it really bothers you, Mark Seemann scetched already how to solve this by making SwimStrategy and WalkStrategy interfaces and change the signature of the constructors accordingly.
    – Doc Brown
    Feb 12, 2018 at 23:01
2

As I see it, the purpose of Dependency Injection is to enable unrelated concerns to vary independently of each other. You inject services into an object in order to change parts of its behaviour. It may be worthwhile to keep the Liskov Substitution Principle (LSP) in mind:

One should be able to replace one implementation of a contract with another without changing the correctness of the system.

If injecting a SwimStrategy into a Wolf is incorrect, then the LSP is violated.

In general, when designing with DI, any combination of dependencies is possible. Typically, only one combination is the desired one, in terms of how the composed application behaves, so you're still left with the overall responsibility of making sure that everything is composed together correctly.

If, for instance, you have two 'email sender' implementations, SmtpSender and a Null Object implementation called NullSender, you could compose your application with both. It wouldn't change the correctness of the system (the rest of the program would still run correctly), but using SmtpSender has the desired outcome that users receive emails, whereas using NullSender would mean no emails are sent.

You can address issues of misconfiguration in various ways; code reviews being one, and full system tests being another.

Concretely, I agree with the comment from Vincent Savard that if wolves can only walk, and sharks can only swim, then that's an implementation detail. I do understand, however, that those examples are only placeholders for more complex code.

First, you may want to reuse WalkStrategy in more than one animal. In addition to Wolf, you could also have a Jaguar class. The simplest way to reuse those, then, would be something like:

class Wolf {
    private WalkStrategy walkStrategy;

    public Wolf() {
        this.walkStrategy = new WalkStrategy();
    }

    // ...
}

class Jaguar {
    private WalkStrategy walkStrategy;

    public Jaguar() {
        this.walkStrategy = new WalkStrategy();
    }

    // ...
}

This reuses WalkStrategy without the risk of substituting a SwimStrategy for a WalkStrategy.

If you truly need those movement strategies to be injected strategies, but you want to prevent the accidental use of SwimStrategy with Wolf, then define two different interfaces:

interface WalkStrategy {
    void move();
}

class WalkStrategyImp implements WalkStrategy {
    void move() {
        // Walking
    }
}

interface SwimStrategy {
    void move();
}

class SwimStrategyImp implements SwimStrategy {
    void move() {
        // Swim
    }
}

Then, define Wolf so that it takes a WalkStrategy object via its constructor, and Shark so that it takes a SwimStrategy via its constructor:

class Wolf {
    public Wolf(WalkStrategy walkStrategy) {
        // ..
    }
}

class Shark {
    public Shark(SwimStrategy swimStrategy) {
        // ..
    }
}

This effectively prevents you from misconfiguring Wolf or Shark.

Notice that I very deliberately made sure that there's no common super-class. (As a side-advice, do yourself the favour to design without inheritance. After having designed systems without inheritance for more than a decade, I can tell you not only is it possible, it's also better.)

In this particular example, though, you're now left with dreaded Imp classes, which indicates that perhaps you're having a problem with the Reused Abstractions Principle. Since this entire Wolf and Shark code is a stand-in (I suppose) for some real code, I can't tell if this is the case, and if it is, what to do about it.


Addendum:

On the Liskov Substitution Principle

I shall not presume to be an expert on the LSP, not having read Barbara Liskov's original paper; I've only read Robert C. Martin's various explanations of it, and base the following on those.

As I understand it, though, the LSP says that if you replace a super-type with a sub-type, this mustn't change the correctness of the system. On a fundamental level, this means that if the super-type doesn't cause the hosting application to crash, then a sub-type isn't allowed to do this either.

More specifically, as Bertrand Meyer tried to formalise, a sub-type mustn't tighten the preconditions established by the super-type, nor must it loosen the post-conditions.

In languages like Java and C# (with which I'm most familiar), pre- and post-conditions are implicit, and not part of the language. Thus, it may not matter if the super-type is an interface; there may still be an implied contract.

Dependency Injection is essentially a consequence of several of the SOLID principles taken together; the Dependency Inversion Principle states what an abstraction is, the Open Closed Principle (OCP) that objects should be modifiable via some sort of extensibility mechanism, and the LSP governs what that extensibility mechanism can and cannot allow.

The way I interpret this (and the way it's worked for me for more than a decade) is that the client owns the abstractions, but doesn't get to choose the sub-type. As long as the sub-type behaves according to the LSP, we must consider the composition as fundamentally sound.

Please note that the LSP says nothing about whether or not a sub-type can change the behaviour of the overall system. It can; that's the whole point of the OCP.

To stay in the terminology of the OP, the clients (Shark and Wolf) own the abstraction (MovementStrategy). If they take a MovementStrategy as a dependency via their constructor, according to the LSP, any implementation would be valid as long as it doesn't break the contract of MovementStrategy.

If, in reality, Shark can only take a SwimStrategy, it would, indeed, be lying if it, via its constructor, were to advertise that it would accept any MovementStrategy.

On desired behaviour

Discussing object-oriented design through the examples of animals is ultimately counter-productive, so I'll now depart this domain in favour of more well-known problems.

Consider an enterprise system that, among many other things, sends emails to users. Using Dependency Injection, you've designed an EmailSender interface that enables client code to send emails. You have a real implementation called SmtpSender, but in order to support testing in various environments, you may also have a FileSender that saves emails to disk for later inspection, and a NullSender that simply does nothing.

Assuming that all three implementations adhere to the EmailSender contract, you can compose your client classes with any one of those three implementations, and it's not going to change the correctness of the system.

What's the desired behaviour, then? Well, in a unit test, it may be that you use a NullSender (or, perhaps, a Test Spy) in order to prevent any residual persistent state to build up on your development machine. In a staging environment, you may want to use the FileSender, and in the real production environment, you want to use SmtpSender.

Thus, depending on circumstances, all compositions have the desired behaviour in their respective contexts.

How, then, do you know that you've configured your dependencies correctly?

I like to use code reviews, so that every change to the dependency configuration is done by one developer, and reviewed by another. It doesn't guarantee that no mis-configurations will happen, but at least, two pair of eyes find more errors than one pair of eyes.

Another error-prevention technique can be to rely on (automated) systems tests. After deploying a new version of the software to an environment, perform an action that should cause an email to be sent (to a test account), and then check that the expected email arrived in the account's inbox.

3
  • Hi Mark, once again thanks a lot for your answer! I like that you bring up the LSP! Subtypes should be interchangeable and indeed all concrete movement strategies adhere to the contract of MovementStrategy which shall be equivalent to fulfilling a property called M (knows how to move). But doesn’t this ultimately mean that Wolf and Shark simply lie about what they need when they request M but what they need is W (knows how to walk) or S (knows how to swim)? Why do you mention code reviews and system tests anyway? Doesn’t this just fight the symptoms of a vague dependency design?
    – Antimon
    Feb 12, 2018 at 22:16
  • @Antimon I've edited my answer in an attempt to answer those questions. Feb 13, 2018 at 14:15
  • Hi and sorry for the delay. You addition clears things up, thank you! My statement about the LSP (“subtypes should be interchangeable”) was too sloppy, of course (well, strictly even wrong!) but I promise that we have the same interpretation about it. After all, the main point for me is that even if a class hierarchy adheres to the LSP this does not say anything about whether it fits the requirements of a client or not. Since I favor strong typing there was unfortunately nothing among the answers that convinced me to be more liberal about class dependencies in the future ;-) P.S. Useful links!
    – Antimon
    Feb 17, 2018 at 1:05
1

1) you make swim and walk separate, possibly inheriting from a common movement base. Then shark and wolf can ask for what they need.

2) you don’t inject the movement strategy. This can be done by just using it in the constructor, by providing a wolf to the IOC container, or by not having wolves at all - a wolf is just an instance of a container that can walk and bite and whatever.

3) you don’t care if sharks can walk. In the real world, having this sort of interface is meant to provide flexibility in the face of the unknown. By trying to constrain things you’re just complicating your code and you leave yourself ill equipped for the coming mutated shark overlords.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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