【发布时间】:2016-12-03 09:32:11
【问题描述】:
主题
这是实现工厂模式的简化示例,以限制对构造函数的直接访问并防止非法方法从其他程序集。
换句话说,我想限制其他开发人员只能使用这个工厂创建他们的对象。 (在对象创建过程中必须做的事情不是特定于一个对象,而是与所有对象相关,因此原因)
解决方案
我有这个委托,用于在我的工厂内创建对象实例。
internal delegate T Instantiate<out T>(/*parameters*/) where T : Vehicle;
正如您所见,这个委托是内部的,所有与Vehicle 及其sub-types 相关的构造函数也是内部的。
internal interface IVehicle{/*...*/}
public class Vehicle
{
//constructor is not protected. so this cant be intertied from outside assembly.
internal Vehicle(/*parameters*/){ }
}
public class Car : Vehicle
{
internal Car(/*parameters*/) : base(/*parameters*/) { }
}
这就是诀窍。
/// <summary>
/// Choose from one of predefined containers to create your vehicle from factoy.
/// </summary>
/// <typeparam name="T">type of vehicle</typeparam>
public class InstanceContainer<T> where T : Vehicle
{
internal InstanceContainer(Instantiate<T> instance) // internal
{
GetInstance = instance;
}
// delegate which creates new instance of T
internal Instantiate<T> GetInstance { get; }
}
这使得其他人能够选择他们的委托,但不能访问实际委托。这里有一些他们可以选择的选项。
/// <summary>
/// Choose this to create car from factory.
/// </summary>
public static InstanceContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);
private static readonly Instantiate<Car> CreateCar = (/*parameters*/) => new Car(/*parameters*/);
// ... others options like Plane, Ship etc
示例
这是从工厂创建对象的方法之一。
public static T CreateVehicle<T>(/*parameters,*/ InstanceContainer<T> instance) where T : Vehicle
{
T obj;
//...
// At some point:
obj = instance.GetInstance(/*parameters*/);
//...
return obj;
}
现在这里是如何使用它。 (从其他程序集说创建一个Car)
VehicleFactory.CreateVehicle(/*parameters,*/ VehicleFactory.CarInstance);
这工作正常,它很好地遵守并且运行良好直到现在。
注意:VehicleFactory 是持有实例、方法、委托的公共静态类...
问题
现在我想制作InstanceContainer co-variant,这样我就可以抽象地传递实例了。
// turns long name into short one!
using VehicleInstance = Factories.VehicleFactory.InstanceContainer<Factories.Goods.Vehicle>
//...
//...
VehicleInstance instance;
// instance will be choosen based on some conditions. like this:
if(true)
instance = VehicleFactory.CarInstance; // we got magic co-variant here!
else
instance = VehicleFactory.PlaneInstance;
VehicleFactory.CreateVehicle(/*parameters,*/ instance);
有什么方法可以使InstanceContainer 变体但不将其委托暴露给其他程序集?
这是我迄今为止尝试过的:
public interface IContainer<out T>
{
// empty!
}
internal class InstanceContainer<T> : IContainer<T> where T : Vehicle
{
internal InstanceContainer(Instance<T> instance)
{
GetInstance = instance;
}
internal Instance<T> GetInstance { get; }
}
这次IContainer 是公共接口,InstanceContainer 是内部接口。这意味着我们必须使用IContainer 代替安全访问。小事必须改变:
// works fine!
public static IContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);
public static T CreateVehicle<T>(/*parameters,*/ IContainer<T> instance)
{
T obj;
//...
// At some point:
obj = ((InstanceContainer<T>)instance).GetInstance(/*parameters*/); // runtime error.
//...
return obj;
}
问题与结论
问题出现在运行时。我们不能将协变接口转换回其实际的类实现。
抱歉,问题太长了。但我知道没有主题,title 的答案会非常简单:
“你不能。因为泛型变体被固定到接口。接口被固定到公共实现。它不能是内部的。这是 c# 限制的事情:(“
我也尝试过显式实现,但这也不起作用。你基本上以公共接口结束,无论如何都会暴露内部委托。
那么有没有更好的方法来实现这种工厂模式,还是我使用了错误的设计模式?可能是我想多了,解决方法很简单。
感谢您的宝贵时间和回答。
【问题讨论】:
-
容器的用途是什么?为了能够使用不同的构造函数参数集创建某种类型的实例?
-
容器的目的是把delegate隐藏在里面,是的。一些构造函数采用不同的参数,但委托是相同的。 (所以一些参数可以被忽略或不像
(data, type) => new Car(data))......如果委托对其他程序集可见,那么他们可以在没有工厂的情况下创建新对象,这是我试图避免的。解决方案没有协方差 btw @Evk -
为什么不将构造函数委托存储在按车辆类型键入的字典中,并使用它来实例化类型为 T 的车辆?
_factories[typeof(T)](parameters). -
谢谢。那是比我更好的解决方案。我将使用它而不是容器。但我想如果我尝试使用协方差,
typeof(T)将始终是Vehicle。 @Evk
标签: c# interface covariance factory-pattern internal