内置工厂的字典?

时间:2011-01-27 00:23:27

标签: c#

我有一个有效的解决方案,但出于教育目的,我想了解是否有更好/更清洁/正确的方法。

问题:在我的“客户端”应用程序中,我有一个字典Dictionary<String, PremiseObject>,其中键(String)是资源的不可变URL(它实际上是一个REST URL)。 PremiseObject是一整套派生类的基本类型;因此,Dictionary实际上包含一系列类,这些类都派生自PremiseObject。

关键要求是我想尝试“保证”在字典外部没有创建PremiseObjects。

解决方案:我有以下函数从字典中获取对象。它要么访问现有实例,要么不存在,创建它:

public PremiseObject GetOrCreateServerObject(string premiseObjectType, string location)
{
    PremiseObject po = null;
    if (!premiseObjects.TryGetValue(location, out po))
    {
        string classname;
        // Create an instance of the right PremiseObject derived class:
        po = // gobbly-gook that is not relevant to this question.
        premiseObjects.Add(location, po);
    }
    else
    {
        Debug.WriteLine("Already exists: {0}", location);
    }
    return po;
}

来电者这样做:

DoorSensor door = 
         (DoorSensor)server.GetOrCreateServerObject("DoorSensor", 
                                                    "http://xyz/FrontDoor");

效果很好。但我认为有一种模式或设计可以优雅地允许我更多地封装“字典中包含的每个对象的单个实例”。

例如,来电者可以这样做:

DoorSensor door = null;
if (!server.ServerObjects.TryGetValue("DoorSensor",
                                      "http://xyz/FrontDoor", 
                                      out door))
    Debug.WriteLine("Something went very wrong");

我真的不怎么称呼这种模式。我的ServerObjects按位置“单实例化”。而我的GetOrCreateServerObject就像是懒惰创建的工厂。

但是有可能创建的实例不会被放入字典中,这可能会导致问题。

就像我说的,我的作品......干杯!

更新1/26/2011 10:13 PM - 我刚刚意识到一个潜在的问题:在服务器端,location/URL表示的对象实际上可以是多继承的。理论上,对象可以同时为DoorSensorDigitalRelay

我目前不关心任何这些情况(例如车库门我简化了上面的例子;我确实没有公开过DoorSensor,只是一个GarageDoorOpener包含用于感测的BOTH属性(例如Status)如果我关心的话,这会给我的整个计划带来一丝皱纹。因为这个项目只适合我:-)我将宣布我不关心并记录它。

5 个答案:

答案 0 :(得分:2)

我会提出以下简单的想法:

  1. PremiseObject的构造函数被声明为内部。
  2. 特殊工厂对象负责创建(或返回已创建的)实例。这本词典是工厂的一部分。
  3. 客户位于另一个程序集中。
  4. 这种方式PremiseObject只能由客户通过工厂创建。这样,您可以保证每个location只存在一个对象实例。


    这个想法的一个变体是将PremiseObject的构造函数声明为private,并将工厂声明为friend;但是(与C ++不同)C#没有friend概念。

答案 1 :(得分:1)

如果你真的想要确保没有创建没有放置在字典中的PremiseObject实例,你可以使构造函数都是私有的,并创建一个静态构造函数(对于每个子类)作为一个参数您要引用的Dictionary对象。此静态构造函数将检查字典对象以确保不存在现有实例,然后根据需要返回新实例或现有实例。所以像这样:

public class PremiseObject
{
    public static Dictionary<string, PremiseObject> PremiseObjects { get; private set; }

    static PremiseObject()
    {
        PremiseObjects = new Dictionary<string, PremiseObject>();
    }
}

public class DerivedPremiseObject : PremiseObject
{
    private DerivedPremiseObject()
    {
    }

    public static DerivedPremiseObject GetDerivedPremiseObject(string location)
    {
        DerivedPremiseObject po = null;
        if (!PremiseObject.PremiseObjects.TryGetValue(location, out po))
        {
            po = new DerivedPremiseObject();
            PremiseObject.PremiseObjects.Add(location, po);
         }
        return po;
    }
}

您可以使用各种类似的策略。关键是以某种方式使构造函数成为私有的,并且只允许通过一个强制类构造逻辑的静态方法来访问构造函数。

答案 2 :(得分:1)

好的,您可以使用通用方法避免参数和强制转换(在消费者代码中)。

public abstract class PremiseObject
{
    protected PremiseObject()
    {

    }

    public string Location { get; set; }

    public static void GetSensor<T>(string location, out T sensor)
             where T : PremiseObject, new()
    {
        PremiseObject so;
        if(_locationSingltons.TryGetValue(location, out so))
        {
            sensor = (T) so; // this will throw and exception if the 
            // wrong type has been created. 
            return;
        }
        sensor = new T();
        sensor.Location = location;
        _locationSingltons.Add(location, sensor);
    }

    private static Dictionary<string, PremiseObject> _locationSingltons 
             = new Dictionary<string, PremiseObject>();
}

然后调用代码看起来更好一些:

    DoorSensor frontDoor;
    PremiseObject.GetSensor("http://FrontDoor/etc", out frontDoor);

所以我喜欢调用约定 - 如果你想避免抛出异常,你可以将返回类型更改为bool并以这种方式表示失败。就个人而言,我会说你想要的是一个例外。

您可能更喜欢没有out参数的调用 - 但是如果您这样做,那么您必须为方法调用提供类型 - 无论如何定义工厂方法将如下所示:

public static T GetSensor<T>(string location) where T : PremiseObject, new()
{
    PremiseObject so;
    if (_locationSingltons.TryGetValue(location, out so))
    {
        return (T)so; // this will throw and exception if the 
        // wrong type has been created. 

    }
    T result = new T();
    result.Location = location;
    _locationSingltons.Add(location, result);
    return result;
}

然后调用代码如下所示:

var frontDoor2 = PremiseObject.GetSensor<DoorSensor>("http://FrontDoor/etc");

我喜欢这两种方法,因为没有必要重复。 PremiseObject的类型只被声明一次 - 不需要定义类型的字符串。

答案 3 :(得分:0)

也许你可以使PremiseObject成为一个单例,然后你不必担心字典中的每个对象都是单个实例吗?

答案 4 :(得分:0)

在一般情况下,在构造函数上设置访问修饰符应该不允许外部任何人创建对象(禁止反射)。但是,这些必须是internal,因此程序集中的任何其他内容都可以实例化它们。

我怀疑使用支持单例实例的现成依赖注入容器可以满足许多要求。它感觉很接近,但可能不太相同。 (可能StrutureMapNinjectCastle WindsorUnity没有特定顺序)