问题和答案
好的,这个问题始于抽象工厂的命名问题。通常情况下,使用“正式”名称来表示你正在做什么(例如,Factory、Decorator等),并加上具体实现的描述(例如,Snickers、Mars、MotifWidget等)。
因此,你创建一个MSSQLConnection
,这是你要描述的具体内容,以及一个Factory
,这意味着它遵循工厂模式的特性。
好的,到目前为止我们已经讨论了命名和原始问题。现在是时候谈谈有趣的东西了。讨论转向如何在C#中实现抽象工厂,这是另一个话题。我在实现所有设计模式的C#方面做了相当多的工作,在这里我将分享一些关于工厂的细节。以下是:
抽象工厂和工厂
抽象工厂基本上是一个基类或接口和一个具体实现的组合。如果你共享大量代码,则需要一个基类;如果不需要,则需要一个接口。
我通常区分“工厂”和“抽象工厂”。一个工厂是用于创建某种类型对象的东西,而“抽象工厂”是用于创建任意类型对象的东西。因此,实现一个抽象工厂就是一个工厂。这对下一部分信息很重要。
工厂模式
支持RTTI的语言能够实现工厂模式。工厂模式是用于创建对象的东西。最简单的实现是一个只包含创建对象方法的类,例如:
public void CreateConnection()
{
return new SqlConnection();
}
你通常使用它来抽象事物。例如,在HTML解析器中生成XML节点的东西,会根据HTML标签创建某种类型的节点。
工厂通常根据运行时信息做出决策。因此,可以将工厂模式泛化,实现类似以下内容:
public T Create(string name)
{
}
使用RTTI很容易创建一个通用的工厂模式,为每个名称存储一个Type即可。查找名称,使用反射创建对象。完成。
而且,相较于手动制作所有工厂,你需要编写更少的代码。因为所有实现都是相同的,你可以将其放在基类中,并在静态构造函数中填充字典。
抽象工厂基本上是创建对象的工厂集合,与工厂模式相同。唯一共享的是接口(例如Create),或者可以使用继承来创建抽象。
实现非常简单,我就不多说了。
让我们回到GoF的例子。他们谈论了MotifFactory和PMFactory。在未来,我们还会遇到其他UI组件,我们需要ASPNETFactory或SilverlightFactory。然而,未来是未知的,如果没有必要,我们宁愿不要发布旧的DLL——毕竟这不够灵活。
如果我们想向工厂添加新方法,那么第二个问题就出现了。这意味着这样做将涉及更改所有工厂。正如您所料,我不想在多个地方更改此内容。
幸运的是,我们可以解决这两个问题。接口是相同的(甚至可以泛化),因此我们可以在运行时简单地向工厂添加新功能。
我们可以使用属性告诉类应该由某个工厂实例化,而不是告诉工厂要创建什么对象。我们还可以在装载程序集时扫描所有类型,因此如果加载了一个程序集,我们可以动态地构建新的工厂。
为此我牺牲了编译时检查,但由于工厂模式通常使用运行时信息,这并不一定是问题。
总之,这是我的工厂代码:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class FactoryClassAttribute : Attribute
{
public FactoryClassAttribute(object key, Type factoryType)
{
if ((factoryType.IsGenericType &&
factoryType.GetGenericTypeDefinition() == typeof(Factory<,>)) ||
factoryType.IsAbstract ||
factoryType.IsInterface)
{
throw new NotSupportedException("Incorrect factory type: you cannot use GenericFactory or an abstract type as factory.");
}
this.Key = key;
this.FactoryType = factoryType;
}
public object Key { get; private set; }
public Type FactoryType { get; private set; }
}
public abstract class Factory<Key, Intf> : IFactory<Key, Intf>
where Intf : class
{
protected Factory() : this((a) => (a)) { }
protected Factory(Func<Key, object> typeConversion)
{
this.typeConversion = typeConversion;
}
private Func<Key, object> typeConversion;
private static object lockObject = new object();
private static Dictionary<Type, Dictionary<object, Type>> dict = null;
public virtual Intf Create(Key key)
{
Dictionary<Type, Dictionary<object, Type>> dict = Init();
Dictionary<object, Type> factoryDict;
if (dict.TryGetValue(this.GetType(), out factoryDict))
{
Type t;
return (factoryDict.TryGetValue(typeConversion(key), out t)) ? (Intf)Activator.CreateInstance(t) : null;
}
return null;
}
public virtual Intf Create(Key key, params object[] constructorParameters)
{
Dictionary<Type, Dictionary<object, Type>> dict = Init();
Dictionary<object, Type> factoryDict;
if (dict.TryGetValue(this.GetType(), out factoryDict))
{
Type t;
return (factoryDict.TryGetValue(typeConversion(key), out t)) ? (Intf)Activator.CreateInstance(t, constructorParameters) : null;
}
return null;
}
public virtual IEnumerable<Key> EnumerateKeys()
{
Dictionary<Type, Dictionary<object, Type>> dict = Init();
Dictionary<object, Type> factoryDict;
if (dict.TryGetValue(this.GetType(), out factoryDict))
{
foreach (object key in factoryDict.Keys)
{
yield return (Key)key;
}
}
}
private void TryHook()
{
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(NewAssemblyLoaded);
}
private Dictionary<Type, Dictionary<object, Type>> Init()
{
Dictionary<Type, Dictionary<object, Type>> d = dict;
if (d == null)
{
lock (lockObject)
{
if (dict == null)
{
try
{
TryHook();
}
catch (Exception) { }
ScanTypes();
}
d = dict;
}
}
return d;
}
private void ScanTypes()
{
Dictionary<Type, Dictionary<object, Type>> classDict = new Dictionary<Type, Dictionary<object, Type>>();
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
AddAssemblyTypes(classDict, ass);
}
dict = classDict;
}
private void AddAssemblyTypes(Dictionary<Type, Dictionary<object, Type>> classDict, Assembly ass)
{
try
{
foreach (Type t in ass.GetTypes())
{
if (t.IsClass && !t.IsAbstract &&
typeof(Intf).IsAssignableFrom(t))
{
object[] fca = t.GetCustomAttributes(typeof(FactoryClassAttribute), false);
foreach (FactoryClassAttribute f in fca)
{
if (!(f.Key is Key))
{
throw new InvalidCastException(string.Format("Cannot cast key of factory object {0} to {1}", t.FullName, typeof(Key).FullName));
}
Dictionary<object, Type> keyDict;
if (!classDict.TryGetValue(f.FactoryType, out keyDict))
{
keyDict = new Dictionary<object, Type>();
classDict.Add(f.FactoryType, keyDict);
}
keyDict.Add(f.Key, t);
}
}
}
}
catch (ReflectionTypeLoadException) { }
}
private void NewAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
lock (lockObject)
{
Dictionary<Type, Dictionary<object, Type>> classDict = new Dictionary<Type, Dictionary<object, Type>>(dict);
dict = null;
Thread.MemoryBarrier();
AddAssemblyTypes(classDict, args.LoadedAssembly);
dict = classDict;
}
}
}
DefaultChocolateFactory()
会是最合理的选择。 - NibblyPig