使用类类型列表(或类似概念)来限制有效输入

时间:2017-08-17 19:06:05

标签: c# .net

我有一些继承自抽象基类Airplane的类,例如:

Airplane

-> F15
-> F16
-> Boeing747

假设我想创建另一个类AirplaneFactory,它接受​​可以构建的可能飞机的列表(在构造函数中):

class AirplaneFactory {
    public AirplaneFactory(List<Type> airplaneTypes) {
        ....
    }
}

如何将这些类型限制为仅Airplane和继承的类?最终目标是创建不同的AirplaneFactory实例,这些实例只能“构建”指定的特定飞机子集。

我想将它限制在类本身,而不必通过使用enum或使用字符串表示来复制工作。

6 个答案:

答案 0 :(得分:1)

以下是两种可能的实施方式:

 public class AirplaneFactory
 {
    private List<Type> _types = new List<Type>();

    //Implementation 1: Use an internal method to validate all items passed.
    public AirplaneFactory(IEnumerable<Type> airplaneTypes) 
    {
        AddTypes(airplaneTypes);
    }

    private void AddTypes(IEnumerable<Type> airplaneTypes)
    {
        var targetType = typeof(Airplane);            
        foreach (var item in airplaneTypes)
        {
            if (!item.IsSubclassOf(targetType))
                throw new ArgumentException(string.Format("{0} does not derive from {1}", item.FullName, targetType.FullName));
            _types.Add(targetType);
        }
    }        

    //Implementation 2: Use a method to individually add the supported types
    public AirplaneFactory()
    {

    }

    //This method adds types one by one and validates the type
    public void AddType<T>() where T : Airplane
    {
        _types.Add(typeof(T));
    }               
}

(注意使用IEnumerable<T>而不是具体列表)

测试它:

  //Implementation 1: It will throw an error when FooClass is checked internally
  var factory = new AirplaneFactory(new[] 
  { 
       typeof(F15), 
       typeof(F16), 
       typeof(Boeing747), 
       typeof(FooClass) 
   });

  //Implementation 2:
  AirplaneFactory factory = new AirplaneFactory();
  factory.AddType<F15>();
  factory.AddType<F16>();
  factory.AddType<Boeing747>();
  //factory.AddType<FooClass>(); //This line would not compile.

<强>更新

如果您抽象出飞机类型集合的概念,则有第三种可能的实现方式:

public class AirplaneTypeCollection : IEnumerable<Type>
{
    List<Type> _types = new List<Type>();
    public AirplaneTypeCollection()
    {

    }
    public void AddType<T>() where T: Airplane
    {
        _types.Add(typeof(T));
    }

    public IEnumerator GetEnumerator()
    {
        return _types.GetEnumerator();
    }

    IEnumerator<Type> IEnumerable<Type>.GetEnumerator()
    {
        return _types.GetEnumerator();
    }
}

然后你的工厂收到类作为构造函数的参数:

public AirplaneFactory(AirplaneTypeCollection types)
{

}

答案 1 :(得分:1)

为了最好的编译时间安全性,工厂类本身可以是通用的。通过约束泛型定义中的类型,程序总是假设只添加(或创建)了正确的类型。

尽管在构造函数中指定类型可能会加快创建新的子类型,但只能在运行时完成检查,并有异常帮助

通过对平面进行子类化,可以将工厂调整为特定的子类型。 例如使用以下设置:

public abstract class Airplane{}
public abstract class Fighter:Airplane{}

public class F15 : Fighter{}
public class F16 : Fighter{}
public class Boeing747 : Airplane{}

public class AirplaneFactory<T> where T : Airplane
{
    List<T> list = new List<T>();
    public void Add(T plane) => list.Add(plane); //"Add" used as a general example, but something like "Create" can be used as well. If T itself should be creatable directly, the constraint 'where T: Airplane, new()' can be used
}

可以使用以下代码,最后一行给出编译器错误:

var generalFact=new AirplaneFactory<Airplane>();
generalFact.Add(new F15()); //valid
generalFact.Add(new Boeing747()); //valid
var fighterFact = new AirplaneFactory<Fighter>();
fighterFact.Add(new F15()); //valid
fighterFact.Add(new Boeing747()); //Invalid!

因为您可能需要更多的子类,然后继承允许,您可以使用接口。

e.g。

public interface IAirplane{}
public interface IFighter:IAirplane{}
public interface IVertical:IAirplane{}
public abstract class Airplane:IAirplane{}

public class F15 : Airplane, IFighter{}
public class F16 : Airplane, IFighter{}
public class Boeing747 : Airplane{}
public class F14: Airplane,IFighter,IVertical{}

public class AirplaneFactory<T> where T : IAirplane
{
    List<T> list = new List<T>();
    public void Add(T plane) => list.Add(plane);
}

并使用带有接口的工厂:

var generalFact=new AirplaneFactory<IAirplane>();
generalFact.Add(new F15()); //valid
generalFact.Add(new Boeing747()); //valid
var fighterFact = new AirplaneFactory<IFighter>();
fighterFact.Add(new F15()); //valid 
var verticalFact=new AirplaneFactory<IVertical>();
verticalFact.Add(new F14()); //valid
verticalFact.Add(new F15()); //Invalid

当然,因为它是一个工厂,所以需要创建函数,而不是“添加”函数。但是对于通用工厂,总是需要额外的规格。但这可以通过重用工厂约束的方法来完成:

public class AirplaneFactory<T> where T : IAirplane
{
    List<T> list = new List<T>();
    public void Add(T plane) => list.Add(plane);

    public PlaneType Create<PlaneType>()
        where PlaneType:class,T,new()
    {
        var res = new PlaneType();
        Add(res);
        return res;
    }
}

例如

verticalFact.Create<F14>(); //valid
verticalFact.Create<F15>(); //Invalid!

答案 2 :(得分:0)

您可以使用AddAirplaneType方法向工厂添加类型,而不是类型列表。

public void AddAirplaneType<TAirplane>() where TAirplane : Airplane
{
    if(!airplaneTypes.Contains(typeof(TAirplane))
        airplaneTypes.Add(typeof(TAirplane));
}

这不是完美的,但在编译之前是可管理的。在将其作为实例提供后检查类型只能导致两件事:

  1. 你必须默默跳过错误的类型。
  2. 你必须抛出异常。
  3. 我不喜欢这两种方式,所以我会使用Add-Method,也许是多个甚至可以同时支持多种类型的方法。

    AddAirplaneType<TAirplane1, TAirplane2, TAirplane3, TAiplane4>()
        where TAirplane1 : Airplane
        where TAirplane2 : Airplane
        where TAirplane3 : Airplane
        where TAirplane4 : Airplane
    

    你得到了这个概念。

答案 3 :(得分:0)

这是你如何做到的。在工厂使用泛型:

public abstract class Airplane
{
}

public class F15 : Airplane
{
}
public class F16 : Airplane
{
}
public class B747 : Airplane
{
}

public class AirplaneFactory<T> where T : Airplane, new()
{
    public List<T> Inventory => new List<T>();

    public T Make() { return new T(); }
}

static class Program
{
    static void Main(string[] args)
    {
        var b747_factory = new AirplaneFactory<B747>();
        var a_b747 = b747_factory.Make();
        b747_factory.Inventory.Add(a_b747);
    }
}

答案 4 :(得分:0)

如果您想要一个具有预定义“原型”集的类工厂,可以从中复制属性,请执行以下操作:

static class Program
{
    static void Main(string[] args)
    {
        var b747_100 = new B747("100", false, 333400);
        var b747_300 = new B747("800", false, 439985);
        var b747_300sp = new B747("300sp", true, 287500);

        var factory = new AirplaneFactory<B747>(b747_100, b747_300, b747_300sp);

        var a_b747_300sp = factory.Make("300sp");
        // makes a cargo version of the B474
        var a_b747_800 = factory.Make("800");
        // makes a passenger version of the B474
        var a_b747_400 = factory.Make("400");
        // makes nothing. No prototype for 400 series

        var copy_of_a_b747_800 = a_b747_800.Clone();
    }
}

/// <summary>
/// Base class. Each airplane has a model moniker.
/// </summary>
public abstract class Airplane
{
    protected Airplane(string model, int engineCount)
    {
        this.Model=model;
        this.EngineCount=engineCount;
    }
    /// <summary>
    /// Create a new airplane with properties from another (Copy constructor).
    /// </summary>
    /// <param name="other"></param>
    protected Airplane(Airplane other)
    {
        this.Model=other.Model;
        this.EngineCount=other.EngineCount;
    }
    public string Model { get; }
    public int EngineCount { get; }
    public abstract float Mass { get; }
}

public class F16 : Airplane, ICloneable
{
    public F16(F16 prototype) : base(prototype)
    {
        this.Mass=prototype.Mass;
    }
    public F16(string model, float mass) : base(model, 1)
    {
        this.Mass=mass;
    }
    public override float Mass { get; }

    #region ICloneable Members
    public F16 Clone() { return new F16(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }   
    #endregion

}
public class B747 : Airplane, ICloneable
{
    public B747(string model, bool isCargo, float mass) : base(model, 4)
    {
        this.IsCargo=isCargo;
        this.Mass=mass;
    }
    public B747(B747 prototype) : base(prototype)
    {
        this.IsCargo=prototype.IsCargo;
        this.Mass=prototype.Mass;
    }
    public bool IsCargo { get; }
    public override float Mass { get; }

    #region ICloneable Members
    public B747 Clone() { return new B747(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }
    #endregion

}

public class AirplaneFactory<T> where T : Airplane
{
    /// <summary>
    /// Use reflection to get the copy constructor for each type 'T'
    /// </summary>
    static ConstructorInfo ctor = typeof(T).GetConstructor(new[] { typeof(T) });
    /// <summary>
    /// Hold a table of monikers and planes of type T
    /// </summary>
    readonly IDictionary<string, T> models;
    /// <summary>
    /// Create a factor with some built in plane models
    /// </summary>
    /// <param name="airplaneModels">The models that the factory can make</param>
    public AirplaneFactory(params T[] airplaneModels)
    {
        this.models=airplaneModels.ToDictionary((x) => x.Model);
    }
    /// <summary>
    /// Public API exposes a readonly table so users can't add whatever planes they want.
    /// </summary>
    public IReadOnlyDictionary<string, T> Models { get { return models as IReadOnlyDictionary<string, T>; } }
    /// <summary>
    /// Add a plan for new planes. If the model already exists then do nothing
    /// </summary>
    /// <param name="prototype">The plane prototype to implement</param>
    /// <returns>True if the plans are added</returns>
    public bool ImplementNewPlans(T prototype)
    {
        if(!models.ContainsKey(prototype.Model))
        {
            models.Add(prototype.Model, prototype);
            return true;
        }
        return false;
    }
    /// <summary>
    /// Create a new plane from the prototype stored in 'Models'.
    /// </summary>
    /// <param name="model">The model moniker</param>
    /// <returns>A new plane if the have a prototype, null otherwise</returns>
    public T Make(string model)
    {
        if(models.ContainsKey(model))
        {
            return ctor.Invoke(new object[] { models[model] }) as T;
        }
        return null;
    }
}

注意:我使用泛型用于平面类型,构造函数的反射和字典用于保存允许的平面模型

答案 5 :(得分:0)

不是将Airplane类型的列表传递给AirplaneFactory构造函数,而是传递一个Func<Airplane>列表(创建平面的委托)。

sealed class AirplaneFactory
{
    public Airplane(IEnumerable<Func<Airplane>> airplane_types)
    {

    }
}

并像这样使用:

//Can only make F15 and Boeing747 instances:
new AirplaneFactory(List<Func<Airplane>> { () => new F15(), () => new Boeing747() });