我有一些继承自抽象基类Airplane
的类,例如:
Airplane
-> F15
-> F16
-> Boeing747
假设我想创建另一个类AirplaneFactory
,它接受可以构建的可能飞机的列表(在构造函数中):
class AirplaneFactory {
public AirplaneFactory(List<Type> airplaneTypes) {
....
}
}
如何将这些类型限制为仅Airplane
和继承的类?最终目标是创建不同的AirplaneFactory
实例,这些实例只能“构建”指定的特定飞机子集。
我想将它限制在类本身,而不必通过使用enum
或使用字符串表示来复制工作。
答案 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));
}
这不是完美的,但在编译之前是可管理的。在将其作为实例提供后检查类型只能导致两件事:
我不喜欢这两种方式,所以我会使用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() });