Простой Factory с параметром, используя Unity 2.0

Скажем, у меня есть Simple Factory (SimpleProductFactory), который использует параметр условия, чтобы определить, как создать Product следующим образом:

public static class SimpleProductFactory
{
    public static Product MakeProduct(Condition condition)
    {
        Product product;
        switch(condition)
        {
            case Condition.caseA:
                product = new ProductA();
                // Other product setup code
                break;
            case Condition.caseA2:
                product = new ProductA();
                // Yet other product setup code
                break;
            case Condition.caseB:
                product = new ProductB();
                // Other product setup code
                break;
        }
        return product;
    }
}

Этот Factory используется некоторым клиентом, который обрабатывает данные времени выполнения, содержащие условие, подобное этому:

public class SomeClient
{
    // ...
    public void HandleRuntimeData(RuntimeData runtimeData)
    {
        Product product = SimpleProductFactory.MakeProduct(runtimeData.Condition);
        // use product...
    }
    // ...
}

public class RuntimeData
{
    public Condition Condition { get; set; }
    // ...
}

Как я могу достичь одного и того же поведения конструкции с помощью Unity 2.0?
Важная часть состоит в том, что условие (Condition) определяет, как создать и настроить Product, и что условие известно только во время выполнения и отличается для каждого вызова MakeProduct(...). ( "Другой код настройки продукта" относится к некоторым материалам делегата, но также может обрабатывать другие инициализации и должен быть частью конструкции.)

Как должна быть сделана регистрация контейнера Product (или IProduct inteface)?
Должен ли я использовать конструкцию InjectionFactory? Как это сделать?

// How do I do this?
container.RegisterType<Product>(???)

Что мне нужно сделать, чтобы обеспечить условие в клиентском коде?

<ы > Наивный клиентский код (из предыдущего редактирования), чтобы выделить последний вопрос, который объясняет формулировки пары ответов:

public class SomeClient
{
    // ...
    public void HandleRuntimeData(RuntimeData runtimeData)
    {
        // I would like to do something like this,
        // where the runtimeData.Condition determines the product setup.
        // (Note that using the container like this isn't DI...)
        Product product = container.Resolve<Product>(runtimeData.Condition);
        // use product...
    }
    // ...
}

(Я прочитал много подобных вопросов здесь, в Stackoverflow, но не смог подгонять их и их ответы к моим потребностям.)

Ответ 1

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

Измените свой factory на фактический объект, а не просто на контейнер для статических методов, и добавьте его.

Ответ 2

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

Однако для случая, когда вы описываете Unity (и некоторые другие рамки для инъекций), есть функция, которая называется "автоматическая factory". Он использует .NET Func <TResult> делегировать. Это функция .NET, поэтому она не привязывает ваш класс к Unity.

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

unity.RegisterType<IOpenFileService, OpenFileService>();

Затем зарегистрируйте для него автоматический factory.

unity.RegisterType<Func<IOpenFileService>>();

Затем он может быть введен в любой класс, который ему нужен.

unity.RegisterType<ViewModelBase, OptionsFileLocationsViewModel>(
    new InjectionConstructor(new ResolvedParameter<Func<IOpenFileService>>());

Если теперь вы разрешаете экземпляр OptionsFileLocationsViewModel, ему не будет введен экземпляр IOpenFileService, а с помощью функции, которая, если вызванная, вернет экземпляр IOpenFileService.

private readonly Func<IOpenFileService> openFileServiceFactory;

private string SelectFile(string initialDirectory)
{
    var openFileService = this.openFileServiceFactory();
    if (Directory.Exists(initialDirectory))
    {
        openFileService.InitialDirectory = initialDirectory;
    }
    else
    {
        openFileService.InitialDirectory =
            System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
    }

    bool? result = openFileService.ShowDialog();
    if (result.HasValue && result.Value)
    {
        return openFileService.FileName;
    }

    return null;
}

Надеюсь, что это краткое объяснение моих побудит вас решить вашу проблему.

Ответ 3

вы можете определить уникальные имена для своих регистраций;

container.RegisterType<Product ,ProductA>("ProductA");
container.RegisterType<Product, ProductB>("ProductB");

или в файле конфигурации;

<register type="Product" mapTo="ProductA" name="ProductA" />
<register type="Product" mapTo="ProductB" name="ProductB" />

то вы можете разрешить экземпляр на основе регистрации:

string productName = "ProductB";
Product product = container.Resolve<Product>(productName);

Вы также можете использовать для имен следующие классы:

public class ProductTypes
{
    public static string ProductA
    {
        get
        {
            return "ProductA";
        }
    }

    public static string ProductB
    {
        get
        {
            return "ProductB";
        }
    }
}

то;

container.RegisterType<Product,ProductA>(ProductTypes.ProductA);
container.RegisterType<Product,ProductB>(ProductTypes.ProductB);

и разрешить его;

Product product = null;

switch(condition)
{
    case Condition.caseA:
        product = container.Resolve<Product>(ProductTypes.ProductA);
        // Other product setup code
        break;
    case Condition.caseA2:
        product = container.Resolve<Product>(ProductTypes.ProductA2);
        // Yet other product setup code
        break;
    case Condition.caseB:
        product = container.Resolve<Product>(ProductTypes.ProductB);
        // Other product setup code
        break;
}

return product;

Ответ 4

Как описывает @ChrisTavares, и как описано в этом ответе, решение состоит в том, чтобы просто вставить factory в клиентский SomeClient. Далее, чтобы следовать Принцип инверсии зависимостей (DIP), клиент должен зависеть только от абстрактного factory, такого как интерфейс factory IProductFactory.

На самом деле это просто вопрос простой инъекции зависимостей (DI). Использование Unity просто как посредник DI для обработки разрешений (конструкторов). Только бетон factory ProductFactory должен быть зарегистрирован в контейнере единства. Создание продуктов полностью обрабатывается factory, где совершенно нормально использовать ключевое слово 'new'.

container.RegisterType<IProductFactory, ProductFactory>();

Вот как могло бы выглядеть решение:

public interface IProductFactory
{
    IProduct MakeProduct(Condition condition);
}

internal class ProductFactory : IProductFactory
{
    public IProduct MakeProduct(Condition condition)
    {
        IProduct product;
        switch (condition)
        {
            case Condition.CaseA:
                product = new ProductA();
                // Other product setup code
                break;
            case Condition.CaseA2:
                product = new ProductA();
                // Yet other product setup code
                break;
            case Condition.CaseB:
                product = new ProductB();
                // Other product setup code
                break;
            default:
                throw new Exception(string.Format("Condition {0} ...", condition));
        }
        return product;
    }
}

public class SomeClient
{
    private readonly IProductFactory _productFactory;

    public SomeClient(IProductFactory productFactory) // <-- The factory is injected here!
    {
        _productFactory = productFactory;
    }

    // ...
    public void HandleRuntimeData(RuntimeData runtimeData)
    {
        IProduct product = _productFactory.MakeProduct(runtimeData.Condition);
        // use product...
    }
    // ...
}

public class RuntimeData
{
    public Condition Condition { get; set; }
    // ...
}

public interface IProduct
{ //...
}
internal class ProductB : IProduct
{ //...
}
internal class ProductA : IProduct
{ //...
}
public enum Condition { CaseA, CaseA2, CaseB }