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

How to implement the client code for an 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 表单中填写的详细信息构建正确的提供程序。点击创建时,我想让工厂实例化正确的提供者和 运行 正确的逻辑。但是有了这个实现,我被迫做类似

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

当我真的希望能够在 运行 时间从网络表单中获取用户的交换类型并将其传递给工厂时

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

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

通常您不应该告诉工厂要创建哪个具体类型。你应该给它提供它自己做出决定所需的信息。现在,我并不是说这不可能是 1:1 关系,只是调用者不应该告诉工厂创建特定的具体类型。

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

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

在您的情况下,如果您在网络表单上有一组 2 种类型可供选择,您可以接受该类型(假设选项是 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();
    }
}

如评论中所述,另一个答案违反了 O/C Principle (and a bit of Single Responsibility Principle (SRP)) of SOLID

一种更动态的方法是注入交换的所有实例并选择正确的一个。下面的示例基于 class 名称(不是全名,但很容易更改)。

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 注册进一步的实现来轻松扩展,w/o 工厂中的任何代码更改

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

在高性能场景(每秒 1000 个请求 )中,注入的服务 scoped/transient 或 memory/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 来扩展它。这两种方法都允许您扩展交换 w/o 对 ExchangeFactory 的更改(或在高性能场景 LazyExchangeFactory 中)