在这个新项目中,我需要通过数据库中的数据在运行时创建对象,现在我有两组类,每组实现一个不同的接口。

我开始研究一个工厂类,它将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);


似乎还可以,但是我在这里缺少什么吗?这是做这种事情的好方法吗?
我有点厌倦了它会以某种方式在运行时崩溃...

评论

您是否考虑过为您的DAL检查NHibernate?它具有自己的构造,用于将id映射到类型实例。除此之外,您的泛型类不应从非泛型类继承。而是使您的Dictionary成为此类的成员,并以T作为类型参数以避免转换。抱歉打错了,写在手机上。

我认为,如果为此编写并运行一些测试,就会知道它是否有效,并且比从随机陌生人的好话中获得的信心要强得多。

大卫,您好,我当然测试过了。有用。我在问这种实现是否不会落入一些晦涩的最终案例或并行性问题,也许很难维护,也许工作很慢并且有更快的方法来实现相同的目标...无论如何,这就是为什么我在这里问这个问题:)

您不想使用诸如Entity Framework 4或NHibernate之类的OR映射器的任何充分理由吗?

不幸的是,我们正在生产的主项目没有ORM,这个新项目需要与之交互,而且我不知道在卫星项目中而不是在主项目中使用ORM是否是一个好主意。并且在主项目中更改为ORM将永远不会通过管理...

#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