Home > Enterprise >  How to construct an interface over types which contain different signatures, but with params all cor
How to construct an interface over types which contain different signatures, but with params all cor

Time:07-05

I have several car factories:

HondaFactory, MercedesFactory, ToyotaFactory

All of these factories have a Create() method:

Honda Create(HondaParts parts);
Mercedes Create(MercedesParts parts);
Toyota Create(ToyotaParts parts);

All return types implement ICar, and all parts implement ICarParts.

I have a class above all this which, when given a factory type and an ICarParts, wants to simply call Create() on the factory without an if/else/if/else statement on the type.

I went to create an ICarFactory and give it an interface of:

ICar Create(ICarParts parts)

But then I got stuck, as of course each factory has a different signature, even though all of the parameters comply with the ICarParts type. Is there any way to achieve what I'm after?

CodePudding user response:

Use generics

abstract class CarFactory<TParts> where TParts : ICarParts 
{
    public abstract ICar Create(TParts parts);
}

class HondaFactory : CarFactory<HondaParts>
{
    public override ICar Create(HondaParts parts)
    {
        ...
    }
}

... other factories

But as always with generics there are limits. E.g., you cannot create a List<Factory<?>> containing different types of factories. The different types of factories are not assignment compatible.

At some point you will still need the if/else/if/else statement to get the right factory. The non-generic, weakly typed approach ICar Create(ICarParts parts) allows more "dynamic" scenarios.

Since C# 9.0 (and .NET 5.0 runtime, I think) you can use covariant return types :

    public override Honda Create(HondaParts parts)
    {
        ...
    }

assuming public class Honda : ICar.


A more sophisticated approach combines a non-generic and a generic interface. Program against the non-generic interface in more dynamic but weakly typed scenarios and against the generic one in other cases:

public interface ICarFactory
{
    ICar Create(ICarParts parts);
}

public interface ICarFactory<in TParts> : ICarFactory
    where TParts : ICarParts
{
    ICar Create(TParts parts);
}

abstract class CarFactory<TParts> : ICarFactory<TParts>
    where TParts : ICarParts
{
    ICar ICarFactory.Create(ICarParts parts) => Create((TParts)parts);

    public abstract ICar Create(TParts parts);
}

Note that the non-generic implementation is done explicitly and is therefore only visible when programming directly against the ICarFactory interface. Now, you can, e.g., create a factory dictionary like this:

var factories = new Dictionary<string, ICarFactory> {
    ["Honda"] = new HondaFactory(),
    ["Mercedes"] = new MercedesFactory(),
    ["Toyota"] = new ToyotaFactory(),
};

It is now your responsibility to provide the right type of parts:

string carType = "Toyota";
ICarFactory factory = factories[carType];
ICar car = factory.Create(parts); // Not type safe!
  •  Tags:  
  • c#
  • Related