如何为内部成员提供协方差?

时间:2016-12-03 09:32:11

标签: c# interface covariance factory-pattern internal

主题

这是以限制直接访问到构造函数的方式实现工厂模式的简化示例,并阻止非法方式从中创建对象其他集会。

换句话说,我想限制其他开发人员只能使用这个工厂创建他们的对象。 (在创建对象时必须完成的事情不是特定于一个对象而是与所有对象相关,因此原因

解决方案

我有这个委托,用于在我的工厂内创建对象实例。

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 共变,这样我就可以抽象地传递实例。

// 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;
}

问题和结论

问题出现在运行时。我们不能将co-variant接口强制转换回它的实际类实现。

很抱歉。但我知道没有主题标题的答案非常简单:

  

&#34;你不能。因为通用变体固定到接口。接口固定到公用工具。它不能是内部的。那个c#限制的东西:(&#34;

我也尝试过显式工具,但这也不起作用。你基本上最终得到公共接口,无论如何都会暴露内部委托。

那么有更好的方法来实现这样的工厂模式还是我使用了错误的设计模式?也许我过度思考,解决方案很简单。

感谢您抽出宝贵的时间和答案。

1 个答案:

答案 0 :(得分:2)

我会简化您的解决方案:

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");