我开始研究一个工厂类,它将id映射为一个类型,一个抽象类,以便我可以为每个组扩展一个工厂。
constructor参数是该组的所有实现者通用的接口类型。
abstract class Factory
{
private Dictionary<int, Type> idsToTypes;
private Type type;
public Factory(Type _type)
{
idsToTypes = new Dictionary<int, Type>();
type = _type;
}
protected Object GetProduct(int id, params object[] args)
{
if (idsToTypes.ContainsKey(id))
{
return Activator.CreateInstance(idsToTypes[id], args);
}
else
{
return null;
}
}
public void RegisterProductType(int id, Type _type)
{
if (_type.IsInterface || _type.IsAbstract)
throw new ArgumentException(String.Format("Registered type, {0}, is interface or abstract and cannot be registered",_type));
if (type.IsAssignableFrom(_type))
{
idsToTypes.Add(id, _type);
}
else
{
throw new ArgumentException(String.Format("Registered type, {0}, was not assignable from {1}",_type,type));
}
}
然后,我注意到两个扩展工厂看起来都一样,最后使用泛型,我进入了此类:
class GenericSingletonFactory<T> : Factory
{
static public GenericSingletonFactory<T> Instance = new GenericSingletonFactory<T>();
private GenericSingletonFactory()
: base(typeof(T)) { }
public T GetObject(int id, params object[] args)
{
Object obj = base.GetProduct(id, args);
if (obj != null)
{
T product = (T)obj;
return product;
}
else return default(T);
}
}
所以我可以这样使用:
GenericSingletonFactory<ISomething>.Instance.RegisterProductType(1, typeof(ImplementingISomething));
ISomething something = GenericSingletonFactory<ISomething>.Instance.GetObject(1);
似乎还可以,但是我在这里缺少什么吗?这是做这种事情的好方法吗?
我有点厌倦了它会以某种方式在运行时崩溃...
#1 楼
有几点考虑:基
Factory
类完成了所有工作,因此我将其简单地合并到通用方法中。您的单例实例存储在公共字段中。这意味着程序的任何部分都可以轻松替换它。您应该将其设置为只读,或者(甚至更好)通过readonly属性将其公开。
如果您不对工厂进行抽象(即将
Instance
属性公开为接口),则意味着没有办法在程序中使用任何其他工厂。这有两个可能的结果:您可以每次都跳过访问
Instance
并使用纯静态方法(Factory<T>.Instance.DoStuff(...)
变为Factory<T>.DoStuff(...)
,它稍短一些),或者您可以确定要在接口(例如
IFactory<T>
)后面隐藏工厂的实现,在这种情况下,具体工厂将存储在单独的类的实例中,这将完全将调用更改为类似DAL.GetFactory<T>().DoStuff(...)
(成为实例方法)。这样就可以提供更大的灵活性,因为您可以将DoStuff
传递给不需要知道静态IFactory<T>
类的代码的一部分。但这需要额外的重新设计。如果在
DAL
方法中添加新的通用参数,则可以在编译时使用RegisterProductType
子句将类型限制为派生类型。获取编译错误比获取运行时错误要好得多。确定要将构造函数参数传递给
where
方法吗?这部分可能有点不寻常(尽管在某些情况下,您可能希望通过传递参数来实例化您的类)。有什么特定的派生类型可以接受与其他类型不同的参数? GetProduct
表示您正在强迫所有调用者知道您的方法将使用哪个构造函数。前四点(简化)将导致类似以下内容:
public class Factory<T>
{
private Factory() { }
static readonly Dictionary<int, Type> _dict = new Dictionary<int, Type>();
public static T Create(int id, params object[] args)
{
Type type = null;
if (_dict.TryGetValue(id, out type))
return (T)Activator.CreateInstance(type, args);
throw new ArgumentException("No type registered for this id");
}
public static void Register<Tderived>(int id) where Tderived : T
{
var type = typeof(Tderived);
if (type.IsInterface || type.IsAbstract)
throw new ArgumentException("...");
_dict.Add(id, type);
}
}
用法:
Factory<IAnimal>.Register<Dog>(1);
Factory<IAnimal>.Register<Cat>(2);
// this is the "suspicious" part.
// some instances may require different parameters?
Factory<IAnimal>.Create(2, "Garfield");
第五点值得考虑。如果将工厂更改为此:
public class Factory<T>
{
private Factory() { }
static readonly Dictionary<int, Func<T>> _dict
= new Dictionary<int, Func<T>>();
public static T Create(int id)
{
Func<T> constructor = null;
if (_dict.TryGetValue(id, out constructor))
return constructor();
throw new ArgumentException("No type registered for this id");
}
public static void Register(int id, Func<T> ctor)
{
_dict.Add(id, ctor);
}
}
那么您可以像这样使用它:
Factory<IAnimal>.Register(1, () => new Dog("Fido"));
Factory<IAnimal>.Register(2, () => new Cat("Garfield", something_else));
// no need to pass parameters now:
IAnimal animal = Factory<IAnimal>.Create(1);
这将所有构造逻辑委托给初始化时间,这允许针对不同对象类型的复杂场景。例如,您可以使它在每次调用
Activator.CreateInstance
时返回一个单例:// each call to Factory<IAnimal>.Create(3) should return the same monkey
Factory<IAnimal>.Register(3, () => Monkey.Instance);
#2 楼
我对C#不太熟悉,所以只有两个一般性说明。而不是像这样的结构:
if (idsToTypes.ContainsKey(id))
{
return Activator.CreateInstance(idsToTypes[id], args);
}
else
{
return null;
}
您可以这样写:
if (idsToTypes.ContainsKey(id))
{
return Activator.CreateInstance(idsToTypes[id], args);
}
return null;
在这种情况下,不需要
else
关键字。使用保护子句
if (!type.IsAssignableFrom(_type)) {
throw new ArgumentException(String.Format("Registered type, {0}, was not assignable from {1}", _type, type));
}
idsToTypes.Add(id, _type);
参考文献:重构中用保护子句替换嵌套条件:改进现有代码的设计;展平箭头代码
评论
\ $ \ begingroup \ $
您可能需要在插入字典后添加“返回”;)
\ $ \ endgroup \ $
–威廉·范·朗普特(Willem van Rumpt)
2012年1月26日上午11:50
\ $ \ begingroup \ $
谢谢@WillemvanRumpt!这个例子是错误的,我已经改变了它:)
\ $ \ endgroup \ $
–palacsint
2012年1月26日12:05
评论
您是否考虑过为您的DAL检查NHibernate?它具有自己的构造,用于将id映射到类型实例。除此之外,您的泛型类不应从非泛型类继承。而是使您的Dictionary成为此类的成员,并以T作为类型参数以避免转换。抱歉打错了,写在手机上。我认为,如果为此编写并运行一些测试,就会知道它是否有效,并且比从随机陌生人的好话中获得的信心要强得多。
大卫,您好,我当然测试过了。有用。我在问这种实现是否不会落入一些晦涩的最终案例或并行性问题,也许很难维护,也许工作很慢并且有更快的方法来实现相同的目标...无论如何,这就是为什么我在这里问这个问题:)
您不想使用诸如Entity Framework 4或NHibernate之类的OR映射器的任何充分理由吗?
不幸的是,我们正在生产的主项目没有ORM,这个新项目需要与之交互,而且我不知道在卫星项目中而不是在主项目中使用ORM是否是一个好主意。并且在主项目中更改为ORM将永远不会通过管理...