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!