主题
这是实现工厂模式的简化示例,其方式是限制对构造函数的直接访问,并防止以非法方式从其他程序集创建对象。
换句话说,我想限制其他开发人员只能使用此工厂创建他们的对象。(在对象创建过程中必须做一些事情,这些事情不是特定于一个对象,而是与所有对象相关,因此原因)
<小时 />解决方案
我有这个委托,用于在我的工厂内创建对象实例。
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);
这工作正常,它符合良好,并且到目前为止运行良好。
注意:车辆工厂是公共静态类,保存实例、方法、委托...
<小时 />问题
现在我想做InstanceContainer
协变,这样我就可以抽象地传递实例。
// 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;
}
问题与结论
运行时出现问题。 我们不能将协变接口转换回其实际的类实现。
对不起,长问题。 但我知道没有主题,标题的答案会很简单:
"你不能。 因为泛型变体固定到接口。 接口固定到公共实现。 它不能是内部的。 这就是C#极限:(">
我也尝试了显式实现,但这也不起作用。 你基本上最终会得到公共接口,无论如何都会公开内部委托。
那么有更好的方法来实现这样的工厂模式还是我使用了错误的设计模式? 也许我想多了,解决方案很简单。
感谢您抽出时间和答案。
我会简化您的解决方案:
internal delegate Vehicle VehicleConstructor(string data, string type);
public static class Factory {
private static readonly Dictionary<Type, VehicleConstructor> _factories = new Dictionary<Type, VehicleConstructor>() {
{typeof(Car), (data, type) => new Car(data)},
{typeof(Plane), (data, type) => new Plane(data, type)}
};
public static T CreateVehicle<T>(string data, string type) where T : Vehicle {
if (!_factories.ContainsKey(typeof(T)))
throw new Exception("Cannot create Vehicle of type " + typeof(T).Name);
return (T) _factories[typeof(T)](data, type);
}
public static Vehicle CreateVehicle(Type vehicleType, string data, string type) {
if (!_factories.ContainsKey(vehicleType))
throw new Exception("Cannot create Vehicle of type " + vehicleType.Name);
return _factories[vehicleType](data, type);
}
}
然后,您可以同时执行以下操作:
var car = Factory.CreateVehicle<Car>("data", "type");
而这个:
var v = Factory.CreateVehicle(true ? typeof(Car) : typeof(Plane), "data", "type");