简体   繁体   中英

How to inject a dependency into a base class if child doesn't know it?

I have an abstract base class Command that depends on ICommandLogger interface:

public abstract class Command
{
    public Command(ICommandLogger cmdLogger) { /* ... */ }
}

And now all the inheritors are look like:

public class ConcreteCommand : Command
{
     public ConcreteCommand(CommandLoggersNamespace.ICommandLogger cmdLogger)
         : base(cmdLogger)
     {
     }
}

I don't like they are forced to know about ICommandLogger (which they do not use).

How to get around of this? Or what are the reasons for complete redesign?

There's a problem in your design that causing you all this trouble. First of all, logging is a cross-cutting concern and you should prevent polluting classes with that. Second, if you let the base class implement logging, what is the next cross-cutting concern that will be added on the logger base class. The second you start adding another cross-cutting concern, the base class will violate the Single Responsibility Principle . Your base class will eventually grow to a big unmanageable class with lots of dependencies, and lot's of reasons to change.

Instead, try adding logging as a decorator. Your design however, prevents you from effectively doing that, since you will probably have dozens of concrete commands and they would all need their own decorator. But the core problem with your design is that you mix data and behavior. Let the command be nothing more than a class containing some data (DTO) and extra the commands logic into its own class; let's call that the command handler .

On top of that, let command handlers implement this interface:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

This will look like this:

public class MoveCustomerCommand
{
    public Guid CustomerId;
    public Address NewAddress;
}

public class MoveCustomerCommmandHandler : ICommandHandler<MoveCustomerCommand>
{
    public void Handle(MoveCustomerCommand command)
    {
        // behavior here.
    }
}

What's interesting about this design is that since all business logic is now hidden behind one narrow interface , and this interface is generic, it becomes very easy to extend the behavior of the system by wrapping handlers with decorators. For instance a logging decorator:

public class LoggingCommandHandlerDecorator<TCommand> 
    : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decoratee;

    public LoggingCommandHandlerDecorator(
        ICommandHandler<TCommand> decoratee, ILog log)
    {
        this.decoratee = decoratee;
        this.log = log;
    }

    public void Handle(TCommand command)
    {
        this.log.Log("Executing " + typeof(TCommand).Name + ": " +
            JsonConvert.Serialize(command));

        try
        {
            this.decoratee.Handle(command);
        }
        catch (Exception ex)
        {
            this.log.Log(ex);
            throw;
        }
    }
}

Since this LoggingCommandHandlerDecorator<TCommand> is generic, it can be wrapped around any ICommandHandler<TCommand> . This allows you to let consumers take a dependency on some ICommandHandler<TCommand> (such as ICommandHandler<MoveCustomerCommand> ) and you can add crosscutting concerns to all business logic without changing a single line of code.

This will in most cases remove the need to use a base class completely.

You can read more about this type of design here .

If you are doing dependency injection via the constructor, there's no way around this. An alternative is dependency injection via property setters. Personally, I prefer the constructor method, because to me, that communicates that this class requires this dependency whereas a property-injected dependency communicates an optional dependency.

If you go the constructor route, and your base class takes a lot of dependencies, you can alleviate some of the pain by creating an aggregate service so that the base only needs one parameter injected instead of many.

If they don't use the command logger you may try to not set any at all, like this:

public ConcreteCommand()
    : base(null)
{
}

If this does not work (throws an Exception ) you may try to implement a phony command logger and instantiated that one:

public ConcreteCommand()
    : base(new MyPhonyCommandLogger())
{
}

If you don't want those phony instances around, have a single instance statically available:

public ConcreteCommand()
    : base(MyPhonyCommandLogger.Instance)
{
}

I don't like they are forced to know about ICommandLogger (which they do not use).

Well they do; unless you instantiate an object of type ICommandLogger in the abstract class and provide a parameterless constructor, you're currently forcing inheritors to know about it.

Well another way of looking at it is that the ConcreteComand class does have a dependency on ICommandLogger . It inherited it when it derived from Command.

  • Therefore there is no way around what you are doing that makes sense other than for ConcreteCommand to accept the dependency on behalf of its base class - think about it not in terms of " ConcreteCommand has a Command " but more like " ConcreteCommand is a Command "

  • Think about how you'd handle wanting to override the base class logging behaviour if you somehow were able to get he base class dependency on ICommandLogger to somehow "sneak past" the construction of ConcreteCommand ...

  • If you want to be able to provide "base like" functionality ( base.Log("foo") ), etc) and you absolutely do not want ConcreteComand to know about ICommandLogger , then you can always switch to "has a" type scenario - whereby Command is just a member variable of ConcreteCommand (a silly reason approach in this case IMHO!)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM