Question

I understand that having 3 or more parameters as dependency is a sign that the class may be doing too many things, however there are cases that the class really needs more than 3. My question is, is consolidating the dependencies in to just one parameter an acceptable practice?

E.g.,

Instead of Foo needing to instantiate and pass its dependencies like this:

$dep1 = new Dep1;
$dep2 = new Dep2;
$dep3 = new Dep3;

Foo($dep1, $dep2, $dep3);

all it needs to do is something like this:

$dependencies = new foosDependencies;
Foo($dependencies);

foosDependencies looks something like this:

class foosDependencies
{
    public $dep1;
    public $dep2;
    public $dep3;

    public function __construct()
    {
        $this->dep1 = new Dep1;
        $this->dep2 = new Dep2;
        $this->dep3 = new Dep3;
    }
}

and Foo like this:

class Foo
{
    private $dependencies;

    public function __construct(foosDependencies $dependencies)
    {
        // Foo now has access to the ff.
        $dep1 = $dependencies->dep1;
        $dep2 = $dependencies->dep2;
        $dep3 = $dependencies->dep2;
    }
}

Now if ever you need more dependencies, you just add it in foosDependencies without the need to modify the constructor parameters of Foo.

Is this is an acceptable method?

Was it helpful?

Solution

The Service Locator Pattern is what you are describing. This pattern involves passing a single object to a new object. This new object gets all of it's dependencies from this single object.

Most times this is an anti pattern, however there is an exception for dependency injection containers, which are basically a "service locator." Generally you'll see service locators at high levels of the application, where an entire application instance is being configured.

Once you get deeper than this global level, service locators become an anti pattern, because of the complexity of creating the service locator object. This makes it difficult to isolate these lower level components for unit testing, and the dependencies for these objects become difficult to understand.

So, there are valid use cases for both, however I would recommend against using this "service locator" object much further down than configuring the whole application itself.

OTHER TIPS

The characteristic you are looking for is called cohesion. The simple heuristic to check if the cohesion is high, remove one dependency and check if other operations (methods) can be still performed.

As stated before, there's no reason to necessarily stop yourself at 3 dependencies. There's no hard and fast rule that dictates that classes with more than 3 dependencies are absolutely evil. Sure, a large number of dependencies can be a code smell, but that's not to say that there's never a valid reason to have them. It simply serves as a warning that you may need to start considering a slight refactor if a class is becoming too bloated.

Like Thomas stated in his answer, cohesion is one possible goal. And in the case of DI, you really should be striving for high cohesion and loose coupling. If there are dependencies that can be removed from a class and certain functionality is unaffected, that may be a sign that the specified functionality can be moved elsewhere. However, this is not always the case!

You may simply have certain Manager classes that simply require a larger than normal amount of dependencies, but the most important part is to just vet the code you're working on and apply the principals and "rules" responsibly where necessary.

Licensed under: CC-BY-SA with attribution
scroll top