我有一个有效的解决方案,但出于教育目的,我想了解是否有更好/更清洁/正确的方法。
问题:在我的“客户端”应用程序中,我有一个字典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
表示的对象实际上可以是多继承的。理论上,对象可以同时为DoorSensor
和DigitalRelay
。
我目前不关心任何这些情况(例如车库门我简化了上面的例子;我确实没有公开过DoorSensor,只是一个GarageDoorOpener包含用于感测的BOTH属性(例如Status
)如果我关心的话,这会给我的整个计划带来一丝皱纹。因为这个项目只适合我:-)我将宣布我不关心并记录它。
答案 0 :(得分:2)
我会提出以下简单的想法:
PremiseObject
的构造函数被声明为内部。这种方式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
,因此程序集中的任何其他内容都可以实例化它们。
我怀疑使用支持单例实例的现成依赖注入容器可以满足许多要求。它感觉很接近,但可能不太相同。 (可能StrutureMap,Ninject,Castle Windsor或Unity没有特定顺序)