Как зарегистрировать шаблон Service Factory, используя базовый интерфейс в Castle Windsor - PullRequest
0 голосов
/ 30 мая 2019

Я столкнулся с проблемой и не могу понять, как ее решить.У меня в основном шаблон фабрики, настроенный так:

Это мой базовый интерфейс

public interface IReportedIssueManager
{
    Task<BaseManagerResponse> ManageRequest(ReportedIssueRequest request);
}

А затем у меня есть класс фабрики, который будет обрабатывать, какой сервис возвращать на основе определенного типа.(createReportedXIssueManager, или createReportedYIssueManager, или createReportedZIssueManager).В моем контроллере я создал бы экземпляр этого класса, вызвал бы этот метод и прошел бы через тип:

public class ReportedIssueFactory : IReportedIssueFactory
    {
        private readonly ICreateReportedXIssueManager createReportedXIssueManager;
        private readonly ICreateReportedYIssueManager createReportedYIssueManager;
        private readonly ICreateReportedZIssueManager createReportedZIssueManager;

        public ReportedIssueFactory(
            ICreateReportedXIssueManager createReportedXIssueManager,
            ICreateReportedYIssueManager createReportedYIssueManager,
            ICreateReportedZIssueManager createReportedZIssueManager)
        {
            this.createReportedXIssueManager = createReportedXIssueManager;
            this.createReportedYIssueManager= createReportedYIssueManager;
            this.createReportedZIssueManager= createReportedZIssueManager;
        }

        public async Task<IReportedIssueManager> ReportIssue(int issueTypeId)
        {
            var issueType = (IssueType)issueTypeId;
            switch(issueType)
            {
                case IssueType.Listing:
                    return createReportedXIssueManager;
                case IssueType.Post:
                    return createReportedYIssueManager;
                case IssueType.User:
                    return createReportedZIssueManager;
                default:
                    throw new ValidationException(ReportedIssuesServiceResources.UnknownIssueType);
            }
        }
    }

, и каждая из этих служб настроена так:

public interface ICreateReportedXIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedXIssueRequest request);
    }

and 

public interface ICreateReportedYIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedYIssueRequest request);
    }

and 

public interface ICreateReportedZIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedZIssueRequest request);
    }

и, наконец, в моем контроллере я бы назвал что-то вроде этого:

var manager = await _reportedIssueFactory.ReportIssue(IssueTypeId);
var response = await manager.ManageRequest(request);

Моя проблема в том, что, поскольку это фабричный шаблон, я думаю, что Windsor ожидает службу с тем же именем, что и базовый интерфейс?

Я получаю следующую ошибку:

Нет компонента для поддержки службы System.Threading.Tasks.Task`1 [[..... ReportedIssues.IReportedIssueManager]]

Кто-нибудь знает, как правильно зарегистрироваться в Виндзоре в этом случае?Любая помощь с благодарностью, спасибо!

1 Ответ

4 голосов
/ 30 мая 2019

На основании сообщения об ошибке вы пытаетесь ввести Task<IReportedIssueManager> вместо своей фабрики. Это может быть все, что вам нужно знать.

Вам также не нужно иметь «базовый интерфейс», а затем другой интерфейс для каждой реализации. Смысл одного интерфейса в том, что он может иметь несколько реализаций. Если у нас есть несколько реализаций одного интерфейса, то объявление наследуемых интерфейсов, по одной на реализацию, является излишним.

Другими словами, все, что возвращает ваш завод, будет реализацией IReportedIssueManager, приведенной к IReportedIssueManager. Нисходящий код не будет знать, реализует ли он также ICreateReportedXIssueManager, ICreateReportedYIssueManager или ICreateReportedZIssueManager. Там может быть никаких оснований для существования этих интерфейсов. Даже если есть причина, это не имеет значения, если вызывающий ожидает и получает IReportedIssueManager.


Виндзор предоставляет способ создания фабрики. Это не намного более кратко, но это приводит к тому, что каждый контейнер создается каждый раз, когда он создается.

Во-первых, независимо от того, как вы реализуете фабрику, нет причин для ее асинхронности. Это просто создание объекта. Async предназначен для более длительных процессов ввода-вывода, где поток может быть освобожден, чтобы сделать что-то еще, пока не получен какой-либо ответ. Вот синхронный интерфейс:

public interface IReportedIssueFactory
{
    // I used the enum here, but you could use the int instead.
    IReportedIssueManager CreateIssueManager(IssueType issueType);
}

В вашей регистрации зависимостей добавьте TypedFactoryFacility в свой контейнер:

container.AddFacility<TypedFactoryFacility>();

Затем зарегистрируйте все реализации IReportedIssueManager, дав им имена (что будет важно позже). Поскольку дело в том, что все они реализуют IReportedIssueManager, для целей этой фабрики не имеет значения, если они классы реализуют другие интерфейсы. Зарегистрируйте их в соответствии с их конкретными типами.

В этом примере я использую имена типов для именования зависимостей только потому, что он избегает добавления константы "волшебная строка". Фактические имена могут быть любыми по вашему выбору, но они не важны, за исключением того, что вы используете их в другом месте.

container.Register(
    Component.For<IReportedIssueManager, CreateReportedXIssueManager>()
        .Named(typeof(CreateReportedXIssueManager).FullName),
    Component.For<IReportedIssueManager, CreateReportedYIssueManager>()
        .Named(typeof(CreateReportedYIssueManager).FullName),
    Component.For<IReportedIssueManager, CreateReportedZIssueManager>()
        .Named(typeof(CreateReportedZIssueManager).FullName)
);

Затем создайте класс, который будет принимать входные данные (в данном случае issueTypeId) и возвращает имя правильной зависимости:

public class IssueManagerSelector : DefaultTypedFactoryComponentSelector
{

    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        var issueType = (IssueType)arguments[0];
        switch (issueType)
        {
            case IssueType.Listing:
                {
                    return typeof(CreateReportedXIssueManager).FullName;
                }
            case IssueType.Post:
                {
                    return typeof(CreateReportedYIssueManager).FullName;
                }
            case IssueType.User:
                {
                    return typeof(CreateReportedZIssueManager).FullName;
                }
            default:
                // So I didn't have to create an exception type as I tested.
                throw new Exception("Unknown type");
        }
    }
}

Наконец, зарегистрируйте в своем контейнере следующее:

container.Register(Component.For<IReportedIssueFactory>()
    .AsFactory(new IssueManagerSelector()));

Вам не нужно создавать реализацию IReportedIssueFactory. Вы можете просто внедрить интерфейс, и Windsor предоставит реализацию.

Будет выполнено следующее: - Когда вы вызываете CreateIssueManager, он передаст ваш IssueType аргумент методу GetComponentName, равному IssueManagerSelector. - Этот метод выберет имя компонента и вернет его. - Затем фабрика разрешит компонент с этим именем и вернет его.

Фабрика работает по договоренности. Виндзор предполагает, что метод интерфейса фабрики, который возвращает что-то, является методом «создания». Вот почему нам не нужно было давать ему конкретное имя.

Вот модульный тест, чтобы убедиться, что он работает как положено:

[TestMethod]
public void WindsorFactoryTest()
{
    var container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(
        Component.For<IReportedIssueManager, CreateReportedXIssueManager>()
            .Named(typeof(CreateReportedXIssueManager).FullName),
        Component.For<IReportedIssueManager, CreateReportedYIssueManager>()
            .Named(typeof(CreateReportedYIssueManager).FullName),
        Component.For<IReportedIssueManager, CreateReportedZIssueManager>()
            .Named(typeof(CreateReportedZIssueManager).FullName)
    );
    container.Register(Component.For<IReportedIssueFactory>()
        .AsFactory(new IssueManagerSelector()));

    var factory = container.Resolve<IReportedIssueFactory>();
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.Listing), typeof(CreateReportedXIssueManager));
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.Post), typeof(CreateReportedYIssueManager));
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.User), typeof(CreateReportedZIssueManager));

}

Наконец, вот Виндзорская документация .

...