我很难理解工厂方法对客户端代码的实现。我了解抽象工厂的整体用法,但是我的问题是我希望工厂找出在运行时实例化的正确对象,但是我看到的每个实现都涉及将枚举或其他值传递给构造函数。
这是我目前的设计
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>();
这可能吗?我想知道(或找到正确的解决方案),或者我是否走在正确的轨道上,但肯定受到知识空白的阻碍。
答案 0 :(得分:3)
通常,您不应该告诉工厂要创建哪种混凝土类型。您应该给它提供自行做出决定所需的信息。现在,我并不是说这不可能是1:1的关系,只是呼叫者不应该告诉工厂进行特定的具体类型。
想象一下,您有一个具有Student
属性的Grade
对象。您还拥有一个工厂,该工厂生产ISchool
,以及具体的实现ElementarySchool
,MiddleSchool
和HighSchool
。现在您可以使用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 Principle的SOLID(和一点单一责任原则(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
上)