StructureMap регистрирует универсальные типы для всех возможных конкретных реализаций - PullRequest
8 голосов
/ 19 января 2011

У меня есть следующее:

public interface ICommand { }
public class AddUser : ICommand
{
    public string Name { get; set; }
    public string Password { get; set; }
}

public interface ICommandHandler<T> : IHandler<T> where T : ICommand
{
    void Execute(T command);
}

public class AddUserHandler : ICommandHandler<AddUser>
{
    public void Execute(AddUser command)
    {
        Console.WriteLine("{0}: User added: {1}", GetType().Name, command.Name);
    }
}

public class AuditTrailHandler : ICommandHandler<ICommand>
{
    public void Execute(ICommand command)
    {
        Console.WriteLine("{0}: Have seen a command of type {1}", GetType().Name, command.GetType().Name);
    }
}

Я бы хотел использовать сканирование для регистрации ICommandHandler <>, чтобы в контейнере были следующие типы:

  • ICommandHandler<AddUser> с типом бетона AddUserHandler
  • ICommandHandler<AddUser> с конкретным типом AuditTrailHandler

Я попробовал это с реализацией IRegistrationConvention, и в какой-то момент у меня это заработало, но я просто не могу понять, как я это сделал.

Цель состоит в том, чтобы иметь возможность выполнить несколько обработчиков для конкретной реализации ICommand, например:

// A method in CommandDispatcher
public void SendCommand<T>(T command) where T : ICommand {
   var commandHandlers = container.GetAllInstances<ICommandHandler<T>>();
   foreach (var commandHandler in commandHandlers) {
       commandHandler.Execute(command);    
   }
}

Я хочу, чтобы AuditTrailHandler<ICommand> выполнялся для всех конкретных реализаций ICommand, поэтому необходимо регистрировать их для всех типов ICommand.

Вторичной целью было бы, если бы я мог вставить коллекцию ICommandHandlers<ICommand> в мой CommandDispatcher вместо контейнера, но я думаю, что это невозможно с той структурой, которая у меня есть сейчас. Докажите, что я не прав, если у вас есть идеи.

РЕДАКТИРОВАТЬ - решено

Я добавил не универсальный интерфейс, который реализует мой универсальный интерфейс, а затем я также добавил Abstract CommandHandler<T>, поэтому мне не нужно реализовывать методы CanHandle или Execute (object) во всех моих обработчиках.

Это рабочая структура:

public interface ICommandHandler
{
    void Execute(object command);
    bool CanHandle(ICommand command);
}

public interface ICommandHandler<T> : ICommandHandler, IHandler<T> where T : ICommand
{
    void Execute(T command);
}

public abstract class AbstractCommandHandler<T> : ICommandHandler<T> where T : ICommand
{
    public abstract void Execute(T command);
    public void Execute(object command)
    {
        Execute((T)command);
    }

    public virtual bool CanHandle(ICommand command)
    {
        return command is T;
    }
}

И так как это изначально был вопрос StructureMap, вот Сканирование, чтобы добавить это:

Scan(x =>
        {
            x.AssemblyContainingType<MyConcreteHandler>();
            x.IncludeNamespaceContainingType<MyConcreteHandler>();
            x.AddAllTypesOf<ICommandHandler>();
            x.WithDefaultConventions();
        });

Это позволяет мне вставлять IEnumerable в мой CommandDispatcher и выполнять так:

public void SendCommand<T>(T command) where T : ICommand
    {
        var commandHandlersThatCanHandle = commandHandlers.Where(c => c.CanHandle(command));
        foreach (var commandHandler in commandHandlersThatCanHandle)
        {
            commandHandler.Execute(command);    
        }
    }

Это позволяет мне выполнять CommandHandlers, которые поддерживают AddUser (например, AddUserHandler), но я также могу выполнять обработчик, который поддерживает ICommand (например, AuditTrailHandler).

Это сладко!

1 Ответ

4 голосов
/ 19 января 2011

Чтобы все обработчики команд были введены в диспетчер команд, создайте новый неуниверсальный интерфейс ICommandHandler, из которого происходит ICommandHandler<T>. У него есть метод Execute, который принимает объект. Недостатком является то, что каждый из ваших обработчиков команд должен реализовать этот метод для вызова типизированной перегрузки:

public class AddUserHandler : ICommandHandler<AddUser>
{
    public void Execute(object command)
    {
        Execute((AddUser)command);
    }
    public void Execute(AddUser command)
    {
        Console.WriteLine("{0}: User added: {1}", GetType().Name, command.Name);
    }
}

Это позволит диспетчеру команд иметь зависимость конструктора от IEnumerable<ICommandHandler>, который автоматически заполнится StructureMap.

В SendCommand у вас есть 2 способа получить соответствующий набор обработчиков. Простой фильтр по типу:

commandHandlers.OfType<ICommandHandler<T>>

или добавьте CanHandle(object command) к интерфейсу ICommandHandler:

commandHandlers.Where(x => x.CanHandle(command))

Второй подход требует больше кода, но дает вам немного больше гибкости, позволяя подключать обработчик не только по типу. Это может облегчить решение вашей первой проблемы, так как AuditTrailHandler всегда возвращает true из CanHandle.

...