如何实现抽象工厂的客户端代码?
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
的工厂,以及 ElementarySchool
、MiddleSchool
和 HighSchool
的具体实现。现在你可以有 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
中)
我很难理解使用工厂方法实现客户端代码。我了解抽象工厂的整体用途,但我的问题是我希望工厂找出正确的对象以在 运行 时间实例化,但我看到的每个实现都涉及将枚举或其他一些值传递给构造函数。
这是我目前的设计
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
的工厂,以及 ElementarySchool
、MiddleSchool
和 HighSchool
的具体实现。现在你可以有 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
中)