如何为抽象工厂实现客户端代码?

时间:2019-01-09 06:10:32

标签: c# model-view-controller asp.net-core factory abstract-factory

我很难理解工厂方法对客户端代码的实现。我了解抽象工厂的整体用法,但是我的问题是我希望工厂找出在运行时实例化的正确对象,但是我看到的每个实现都涉及将枚举或其他值传递给构造函数。

这是我目前的设计

using System;

namespace FactoryTest.Jobs
{
    public class ExchangeProvider1 : IExchangeProvider
    {
        public void Buy()
        {
            Console.WriteLine("Buying on Exchange1!");
        }
    }
}

using System;

namespace FactoryTest.Jobs
{
    public class ExchangeProvider2 : IExchangeProvider
    {
        public void Buy()
        {
            Console.WriteLine("Buying on Exchange2");
        }
    }
}

  public interface IExchangeFactory
{

}

   public interface IExchangeProvider
{
    void Buy();
}

  public class ExchangeFactory : IExchangeFactory
{
    public static IExchangeProvider CreateExchange<T>() where T : IExchangeProvider
    {
        return Activator.CreateInstance<T>();
    }

    public static IExchangeProvider CreateExchange(string exchangeName)
    {
        return (IExchangeProvider) Activator.CreateInstance<IExchangeProvider>();
    }
}

问题是我试图让工厂根据用户在Web表单中填写的详细信息来构建正确的提供程序。在点击create时,我想在工厂实例化正确的提供程序并运行正确的逻辑。但是通过这种实现,我被迫做类似的事情

var provider = ExchangeFactory.CreateExchange<Exchange1>();

当我真的希望能够在运行时从Web表单获取用户的Exchange类型并将其传递给工厂时

//Receive IExchangeType from user submitting web form
var provider = ExchangeFactory.CreateExchange<IExchangeType>();

这可能吗?我想知道(或找到正确的解决方案),或者我是否走在正确的轨道上,但肯定受到知识空白的阻碍。

2 个答案:

答案 0 :(得分:3)

通常,您不应该告诉工厂要创建哪种混凝土类型。您应该给它提供自行做出决定所需的信息。现在,我并不是说这不可能是1:1的关系,只是呼叫者不应该告诉工厂进行特定的具体类型。

想象一下,您有一个具有Student属性的Grade对象。您还拥有一个工厂,该工厂生产ISchool,以及具体的实现ElementarySchoolMiddleSchoolHighSchool。现在您可以使用3种方法:CreateElementarySchool()CreateMiddleSchool()CreateHighSchool(),但是调用者必须决定要使用哪种方法。

一种更好的方法是拥有一种使用一些信息来创建学校的方法。例如:CreateSchoolForGrade(grade)。在内部,工厂将具有逻辑来确定哪种混凝土类型与等级相匹配。

在您的情况下,如果您在网络表单上有两种类型可供选择,则可以接受该类型(假设选项为Empire或Rebels)。您可以有一个枚举:

public enum Faction
{
    Empire,
    Rebels
}

然后是工厂方法:

public IFaction CreateFaction(Faction faction)
{
    switch (faction)
    {
        case Faction.Empire:
            return new EmpireFaction();
        case Faction.Rebels:
            return new RebelsFaction();
        default:
            throw new NotImplementedException();
    }
}

现在,假设您退休了EmpireFaction,将其替换为EmpireFactionV2。您只需要修改工厂,而调用者则不在乎:

public IFaction CreateFaction(Faction faction)
{
    switch (faction)
    {
        case Faction.Empire:
            return new EmpireFactionV2();
        case Faction.Rebels:
            return new RebelsFaction();
        default:
            throw new NotImplementedException();
    }
}

答案 1 :(得分:1)

如评论中所述,另一个答案是违反O/C PrincipleSOLID(和一点单一责任原则(SRP))。

一种更动态的方法是注入所有交换实例并选择正确的交换实例。波纹管示例基于类名(不是全限定名,但是可以轻松更改)。

public interface IExchange
{
    void Buy();
}

public class Exchange1 : IExchange
{
    public void Buy() => Console.WriteLine("Buying on Exchange1");
}

public class Exchange2 : IExchange
{
    public void Buy() => Console.WriteLine("Buying on Exchange2");
}

public interface IExchangeFactory
{
    IExchange CreateExchange(string exchangeName);
}

// All exchanges are instantiated and injected
public class ExchangeFactory : IExchangeFactory
{
    private readonly IEnumerable<IExchange> exchanges;

    public ExchangeFactory(IEnumerable<IExchange> exchanges)
    {
        this.exchanges = exchanges ?? throw new ArgumentNullException(nameof(exchanges));
    }

    public IExchange CreateExchange(string exchangeName)
    {
        var exchange = exchanges.FirstOrDefault(e => e.GetType().Name == exchangeName);
        if(exchange==null)
            throw new ArgumentException($"No Exchange found for '{exchangeName}'.");

        return exchange;
    }
}

可以通过在DI中注册进一步的实现来轻松扩展它,而无需在工厂中进行任何代码更改

service.AddScoped<IExchange, Exchange3>();
service.AddScoped<IExchange, Exchange4>();

在高性能场景(每秒每秒,有1000个请求)中,注入的服务是作用域/瞬态的,或者创建此额外实例的内存/ GC压力很高,可以使用提供程序模式以仅创建真正需要的交换:

public interface IExchangeProvider
{
    IExchange CreateExchange(string exchangeName);
}

public class Exchange1Provider : IExchangeProvider
{
    public IExchange CreateExchange(string exchangeName)
    {
        if(exchangeName == nameof(Exchange1))
        {
            // new it, resolve it from DI, use activation whatever suits your need
            return new Exchange1();
        }

        return null;
    }
}

public class Exchange2Provider : IExchangeProvider
{
    public IExchange CreateExchange(string exchangeName)
    {
        if (exchangeName == nameof(Exchange2))
        {
            // new it, resolve it from DI, use activation whatever suits your need
            return new Exchange1();
        }

        return null;
    }
}

public class LazyExchangeFactory : IExchangeFactory
{
    private readonly IEnumerable<IExchangeProvider> exchangeProviders;

    public LazyExchangeFactory(IEnumerable<IExchangeProvider> exchangeProviders)
    {
        this.exchangeProviders = exchangeProviders ?? throw new ArgumentNullException(nameof(exchangeProviders));
    }

    public IExchange CreateExchange(string exchangeName)
    {
        // This approach is lazy. The providers could be singletons etc. (avoids allocations)
        // and new instance will only be created if the parameters are matching
        foreach (IExchangeProvider provider in exchangeProviders)
        {
            IExchange exchange = provider.CreateExchange(exchangeName);

            // if the provider couldn't find a matcing exchange, try next provider
            if (exchange != null)
            {
                return exchange;
            }
        }

        throw new ArgumentException($"No Exchange found for '{exchangeName}'.");
    }
}

此方法与第一种方法类似,不同之处在于您通过添加新的IExchangeProvider来扩展它。两种方法都允许您扩展交换而无需更改ExchangeFactory(或在高性能方案LazyExchangeFactory上)