24

On Constructor Over-injection

 5 years ago
source link: https://www.tuicool.com/articles/hit/JJRNjm3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Constructor Over-injection is a code smell, not an anti-pattern.

Sometimes, people struggle with how to deal with the Constructor Over-injection code smell. Often, you can address it by refactoring to Facade Services . This is possible when constructor arguments fall in two or more natural clusters. Sometimes, however, that isn't possible.

Cross-cutting concerns

Before we get to the heart of this post, I want to get something out of the way. Sometimes constructors have too many arguments because they request various services that represent cross-cutting concerns:

public Foo(
    IBar bar,
    IBaz baz,
    IQux qux,
    IAuthorizationManager authorizationManager,
    ILog log,
    ICache cache,
    ICircuitBreaker breaker)

This Foo class has seven dependencies passed via the constructor. Three of those ( bar , baz , and qux ) are regular dependencies. The other four are various incarnations of cross-cutting concerns: logging, caching, authorization, stability. As I describe in my book , cross-cutting concerns are better addressed with Decorators or the Chain of Responsibility pattern. Thus, such a Foo constructor ought really to take just three arguments:

public Foo(
    IBar bar,
    IBaz baz,
    IQux qux)

Making cross-cutting concerns 'disappear' like that could decrease the number of constructor arguments to an acceptable level, thereby effectively dealing with the Constructor Over-injection smell. Sometimes, however, that's not the issue.

No natural clusters

Occasionally, a class has many dependencies, and the dependencies form no natural clusters:

public Ploeh(
    IBar bar,
    IBaz baz,
    IQux qux,
    IQuux quux,
    IQuuz quuz,
    ICorge corge,
    IGrault grault,
    IGarply garply,
    IWaldo waldo,
    IFred fred,
    IPlugh plugh,
    IXyzzy xyzzy,
    IThud thud)
{
    Bar = bar;
    Baz = baz;
    Qux = qux;
    Quux = quux;
    Quuz = quuz;
    Corge = corge;
    Grault = grault;
    Garply = garply;
    Waldo = waldo;
    Fred = fred;
    Plugh = plugh;
    Xyzzy = xyzzy;
    Thud = thud;
}

This seems to be an obvious case of the Constructor Over-injection code smell. If you can't refactor to Facade Services, then what other options do you have?

Introducing a Parameter Object isn't likely to help

Some people, when they run into this type of situation, attempt to resolve it by introducing a Parameter Object . In its simplest form, the Parameter Object is just a collection of properties that client code can access. In other cases, the Parameter Object is a Facade over a DI Container. Sometimes, the Parameter Object is defined as an interface with read-only properties.

One way to use such a Parameter Object could be like this:

public Ploeh(DependencyCatalog catalog)
{
    Bar = catalog.Bar;
    Baz = catalog.Baz;
    Qux = catalog.Qux;
    Quux = catalog.Quux;
    Quuz = catalog.Quuz;
    Corge = catalog.Corge;
    Grault = catalog.Grault;
    Garply = catalog.Garply;
    Waldo = catalog.Waldo;
    Fred = catalog.Fred;
    Plugh = catalog.Plugh;
    Xyzzy = catalog.Xyzzy;
    Thud = catalog.Thud;
}

Alternatively, some people just store a reference to the Parameter Object and then access the read-only properties on an as-needed basis.

None of those alternatives are likely to help. One problem is that such a DependencyCatalog will be likely to violate the Interface Segregation Principle , unless you take great care to make a 'dependency catalogue' per class. For instance, you'd be tempted to add Wibble , Wobble , and Wubble properties to the above DependencyCatalog class, because some other Fnaah class needs those dependencies in addition to Bar , Fred , and Waldo .

Fundamentally, Constructor Over-injection is a code smell. This means that it's an indication that something is not right; that there's an area of code that bears investigation. Code smells aren't problems in themselves, but rather symptoms.

Constructor Over-injection tends to be a symptom that a class is doing too much: that it's violating the Single Responsibility Principle . Reducing thirteen constructor arguments to a single Parameter Object doesn't address the underlying problem. It only applies deodorant to the smell.

Address, if you can, the underlying problem, and the symptom is likely to disappear as well.

Guidance, not law

This is only guidance. There could be cases where it's genuinely valid to have dozens of dependencies. I'm being deliberately vague, because I don't wish to go into an elaborate example. Usually, there's more than one way to solve a particular problem, and occasionally, it's possible that collecting many services in a single class would be appropriate. (This sounds like a case for the Composite design pattern, but let's assume that there could be cases where that's not possible.)

This is similar to using cyclomatic complexity as a guide. Every now and then, a big, flat switch statement is just the best and most maintainable solution to a problem, even when it has a cyclomatic complexity of 37...

Likewise, there could be cases where it's genuinely not a problem with dozens of constructor arguments.

Constructor Over-injection is a code smell, not an anti-pattern. It's a good idea to be aware of the smell, and address it when you discover it. You should, however, deal with the problem instead of applying deodorant to the smell. The underlying problem is usually that the class with the many dependencies has too many responsibilities. Address that problem, and the smell is likely to evaporate as well.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK