没有开关或 If/Then 的工厂模式
Posted
技术标签:
【中文标题】没有开关或 If/Then 的工厂模式【英文标题】:Factory Pattern without a Switch or If/Then 【发布时间】:2016-02-25 23:35:09 【问题描述】:我正在寻找一个如何实现工厂类的简单示例,但没有使用 Switch 或 If-Then 语句。我能找到的所有示例都使用一个。例如,如何修改这个简单的示例(如下)以使实际工厂不依赖于 Switch?在我看来,这个例子违反了打开/关闭原则。我希望能够添加具体类(“经理”、“职员”、“程序员”等),而无需修改工厂类。
谢谢!
class Program
abstract class Position
public abstract string Title get;
class Manager : Position
public override string Title
get return "Manager";
class Clerk : Position
public override string Title
get return "Clerk";
class Programmer : Position
public override string Title
get return "Programmer";
static class Factory
public static Position Get(int id)
switch (id)
case 0: return new Manager();
case 1: return new Clerk();
case 2: return new Programmer();
default: return new Programmer();
static void Main(string[] args)
for (int i = 0; i <= 2; i++)
var position = Factory.Get(i);
Console.WriteLine("Where id = 0, position = 1 ", i, position.Title);
Console.ReadLine();
更新:
哇!感谢大家!我学到了很多。在回顾了所有的反馈之后,我混合了一些答案并想出了这个。我愿意就更好的方法进行进一步对话。
class Program
public interface IPosition
string Title get;
class Manager : IPosition
public string Title
get return "Manager";
class Clerk : IPosition
public string Title
get return "Clerk";
class Programmer : IPosition
public string Title
get return "Programmer";
static class PositionFactory
public static T Create<T>() where T : IPosition, new()
return new T();
static void Main(string[] args)
IPosition position0 = PositionFactory.Create<Manager>();
Console.WriteLine("0: " + position0.Title);
IPosition position1 = PositionFactory.Create<Clerk>();
Console.WriteLine("1: " + position1.Title);
IPosition position2 = PositionFactory.Create<Programmer>();
Console.WriteLine("1: " + position2.Title);
Console.ReadLine();
另一个编辑:
也可以使用未知类型创建接口的实例:
static class PositionFactory
public static IPosition Create(string positionName)
Type type = Type.GetType(positionName);
return (IPosition)Activator.CreateInstance(type);
然后可以如下调用:
IPosition position = PositionFactory.Create("Manager");
Console.WriteLine(position.Title);
【问题讨论】:
你可以看看Abstract Factory Pattern 并使用依赖注入来传递正确的工厂来完成这项工作。 我会推荐 Ninject 或 Autofac 之类的东西 这是一个典型的依赖注入案例。任何 IoC 容器(Unity、Ninject 等)最基本的用途就是将其用作美化工厂。 @Adimeus ...我希望看到一个使用依赖注入和 IoC 的示例。可以请你提供一份吗? @CaseyCrookston - 我更新了我的答案,以便它根据您在其他 cmets 中的请求使用接口。 【参考方案1】:这个怎么样(不需要字典,注意如果你尝试Create<Position>()
,你会得到一个语法错误):
EDIT - 更新为使用显式实现的 IPosition 接口。只有 IPosition 的实例可以访问成员函数(例如,<implementation of Manager>.Title
不会编译)。
EDIT #2 正确使用接口时,Factory.Create 应该返回 IPosition 而不是 T。
using System;
using System.Collections.Generic;
class Program
interface IPosition
string Title get;
bool RequestVacation();
class Manager : IPosition
string IPosition.Title
get return "Manager";
bool IPosition.RequestVacation()
return true;
class Clerk : IPosition
int m_VacationDaysRemaining = 1;
string IPosition.Title
get return "Clerk";
bool IPosition.RequestVacation()
if (m_VacationDaysRemaining <= 0)
return false;
else
m_VacationDaysRemaining--;
return true;
class Programmer : IPosition
string IPosition.Title
get return "Programmer";
bool IPosition.RequestVacation()
return false;
static class Factory
public static IPosition Create<T>() where T : IPosition, new ()
return new T();
static void Main(string[] args)
List<IPosition> positions = new List<IPosition>(3);
positions.Add(Factory.Create<Manager>());
positions.Add(Factory.Create<Clerk>());
positions.Add(Factory.Create<Programmer>());
foreach (IPosition p in positions) Console.WriteLine(p.Title);
Console.WriteLine();
Random rnd = new Random(0);
for (int i = 0; i < 10; i++)
int index = rnd.Next(3);
Console.WriteLine("Title: 0, Request Granted: 1", positions[index].Title, positions[index].RequestVacation());
Console.ReadLine();
【讨论】:
我非常喜欢这种简单性。但是当我尝试它时,我得到一个空白控制台。让我稍微消化一下。 那里没有输出仓位内容的代码。只需添加一个 foreach(Position p in position)... 如何将 id 编号映射到 new 实例?我看到您可以按索引从positions
中提取一个实例,大致对应于 id,但是如果您需要多个 单独的 `Manager' 实例怎么办?
如果我可以直接打电话给Factory.Create<Manager>()
,我为什么不直接打电话给new Manager()
呢?工厂模式的目标是减少组件之间的耦合。
@Bradley Uffner:你问,“我为什么不直接调用 new Manager() 呢?”我可能在这里忽略了一些东西,但是调用 new Manager() 是 OP 中的示例所做的。当然,这个例子过于简单化了。在现实世界的场景中,抽象类(或接口)位置会涉及更多。使用 Clay 在这里提供的答案,我可以这样做: Position position0 = Factory.Create您可以使用自定义属性和反射。
[PositionType(1)]
class Manager : Position
public override string Title
get
return "Manager";
[PositionType(2)]
class Clerk : Position
public override string Title
get
return "Clerk";
然后,您可以在您的工厂中获取所有继承自 Position
的类,并找到具有正确值的 PositionType
属性的类。
static class Factory
public static Position Get(int id)
var types = typeof(Position).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Position)))
.ToList();
Position position = null;
foreach(var type in types)
type.GetCustomAttributes<PositionTypeAttribute>();
if(type.PositionId == id)
position = Activator.CreateInstance(type) as Position;
break;
if(position == null)
var message = $"Could not find a Position to create for id id.";
throw new NotSupportedException(message);
return position;
【讨论】:
好的,我可以看到这个工作。但是反思似乎有点过头了?我喜欢 Adimeus 上面的建议,即使用抽象工厂模式并使用依赖注入来传递正确的工厂来完成工作。我只是还不确定如何做到这一点。 @CaseyCrookston,我想这取决于你对矫枉过正的定义。我刚刚查看了抽象模式工厂实现的 UML 图和示例代码,看起来你最终会得到更多(可以说)更难维护的代码。 我们在消息处理中使用与此类似的方法。我们有可以处理许多事件类型的事件处理程序。他们宣传他们可以使用属性处理的内容。我认为“反思成本”被夸大了。我已经完成了基准测试,并且反射确实不像“常识”所表明的那样昂贵。此外,鉴于类型的数量基本上是固定的(您需要部署一个新程序集来更改它),您可以在系统启动时缓存所有这些信息,并且在重新启动之前不需要再次加载它(例如在重新部署之后)。 反射并不慢,如果你缓存结果并且只在没有任何内容时使用它。反射的问题更多在于你只会遇到运行时错误。 @Adimeus 当您将东西编译成委托时,性能方面并没有太多需要担心的缺点。 @CSharpie,你对运行时错误是完全正确的。当使用类型动态创建东西时,您的代码中肯定需要更多的保护条件。我提供的代码在这方面应该是相当有弹性的(主要是因为它只使用它自己发现的类型)但是我过去写过一些基于反射的代码,这些代码依赖于存储在数据库中的字符串。如果你不小心,这些东西可能会非常脆弱。【参考方案3】:public class PositionFactory
private Dictionary<int, Type> _positions;
public PositionFactory()
_positions = new Dictionary<int, Type>();
public void RegisterPosition<PositionType>(int id) where PositionType : Position
_positions.Add(id, typeof(PositionType));
public Position Get(int id)
return (Position) Activator.CreateInstance(_positions[id]);
这样使用:
var factory = new PositionFactory();
factory.RegisterPosition<Manager>(0);
factory.RegisterPosition<Clerk>(1);
Position p = factory.Get(0); //Returns a new Manager instance
【讨论】:
好的,谢谢!我已经盯着它看了一段时间,以确保我理解正在发生的一切。问题:这是否符合抽象工厂模式的要求? 不,这只是常规工厂模式的一个简单示例。如果你有多个PositionFactory
的实现,它可以用作抽象工厂的一部分。抽象工厂只是工厂的工厂,因此您可以使用与此非常相似的模式来创建抽象工厂。事实上,您可以将两个答案都作为PositionFactory
的2 个不同实现发布,因为它们使用几乎 相同的接口,并且有一个不同的工厂返回任一类型的PositionFactory
。
OP 和这个例子的工作方式不同。 OP 为每个PositionType
预先确定了一个代码,此示例允许任何代码具有任何位置
True Sten,但我并不在乎。我只是在寻找创建任何位置实例的最佳方法,而不必在工厂类中将它们全部列出。
我会用接口更新我的答案。这也将允许我演示即使一个 Create为什么要把事情复杂化? 这是一个简单的解决方案:
using System;
using System.Collections.Generic;
class Program
interface IPosition
string Title get;
class Manager : IPosition
public string Title
get return "Manager";
class Clerk : IPosition
public string Title
get return "Clerk";
class Programmer : IPosition
public string Title
get return "Programmer";
class Factory
private List<IPosition> positions = new List<IPosition>();
public Factory()
positions.Add(new Manager());
positions.Add(new Clerk());
positions.Add(new Programmer());
positions.Add(new Programmer());
public IPosition GetPositions(int id)
return positions[id];
static void Main(string[] args)
Factory factory = new Factory();
for (int i = 0; i <= 2; i++)
var position = factory.GetPositions(i);
Console.WriteLine("Where id = 0, position = 1 ", i, position.Title);
Console.ReadLine();
以下是完全不使用工厂类的方法:
using System;
using System.Collections.Generic;
class Program
interface IPosition
string Title get;
class Manager : IPosition
public string Title
get return "Manager";
class Clerk : IPosition
public string Title
get return "Clerk";
class Programmer : IPosition
public string Title
get return "Programmer";
static void Main(string[] args)
List<IPosition> positions = new List<IPosition> new Manager(), new Clerk(), new Programmer(), new Programmer() ;
for (int i = 0; i <= 2; i++)
var position = positions[i];
Console.WriteLine("Where id = 0, position = 1 ", i, position.Title);
Console.ReadLine();
【讨论】:
这需要在添加新位置时修改工厂类。它也只能分发每个位置的一个实例。 好吧,那么您只需将添加位置移到工厂类之外。它们确实需要添加到某处......在这种情况下,您甚至不需要工厂类。使用列表就可以了 除非它实际上是 实例化它返回的东西,否则我不会将其视为工厂。这更像Service Locator Pattern
,只是没有“服务”
这与 Clay Ver Valen 的建议非常接近。
很公平。在那种情况下,布拉德利提供的字典答案是我见过的在类似情况下使用最多的答案。以上是关于没有开关或 If/Then 的工厂模式的主要内容,如果未能解决你的问题,请参考以下文章