Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use list of class types (or similar concept) to limit valid input

Tags:

c#

.net

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.

like image 957
Christian P. Avatar asked Aug 17 '17 19:08

Christian P.


People also ask

What are the different types of input validation?

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).

What is input validation allow list?

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.

What type of input validation should be performed on the client?

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.


2 Answers

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)
{

}
like image 87
JuanR Avatar answered Sep 27 '22 18:09

JuanR


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!
like image 27
Me.Name Avatar answered Sep 27 '22 18:09

Me.Name