I have a number of classes inheriting from an abstract base class Airplane
, examplified:
Airplane
-> F15
-> F16
-> Boeing747
Suppose I want to create another class, AirplaneFactory
that takes a list (in the constructor) of possible airplanes it can build:
class AirplaneFactory {
public AirplaneFactory(List<Type> airplaneTypes) {
....
}
}
How do I limit those types to only Airplane
and inherited classes? The end goal is to create different AirplaneFactory
instances that can only "build" a specific subset of airplanes as specified.
I would like to limit it to the classes themselves without having to duplicate the work by using an enum
or using String representations for classes.
There are two different types of input validation approaches: whitelist validation (sometimes referred to as inclusion or positive validation) and blacklist validation (sometimes known as exclusion or negative validation).
Allow list validation is appropriate for all input fields provided by the user. Allow list validation involves defining exactly what IS authorized, and by definition, everything else is not authorized. If it's well structured data, like dates, social security numbers, zip codes, email addresses, etc.
Which Input Validation Should You Use? In general, it is best to perform input validation on both the client side and server side. Client-side input validation can help reduce server load and can prevent malicious users from submitting invalid data.
Here are two possible implementations:
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));
}
}
(Notice the use of IEnumerable<T>
instead of a concrete list)
Testing it:
//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.
UPDATE:
There is a third possible implementation if you abstract out the concept of an Airplane Type collection:
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();
}
}
Then your factory receives the class as an argument of the constructor:
public AirplaneFactory(AirplaneTypeCollection types)
{
}
For best compile time safety, the factory class itself can be generic. By constraining the types inside the generic definition, the program can always assume only the correct types are added (or created).
Although specifying types inside the constructor may speed up creating new subtypes, the checks can only be done in runtime, with the help of exceptions
By subclassing the planes themselves, the factory can be tuned to a specific subtype. e.g. with the following setup:
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
}
The following can be used, with the last line giving a compiler error:
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!
Because you may want more subclasses then inheritance allows, you can use interfaces instead.
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);
}
And use the factories with the interfaces:
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
Of course, since it's a factory, Create functions would be expected, rather than 'Add' functions. But with a generic factory, there would always be the need of an extra specification. But that can be done with a method which reuses the factory constraint:
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;
}
}
example
verticalFact.Create<F14>(); //valid
verticalFact.Create<F15>(); //Invalid!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With