Pregunta

I currently have an inheritance structure where the return type of a method is being left as an open generic so that each class can return an object from the corresponding level of another structure. For example, lets say we had a VehicleFactory where TVehicle:Vehicle that could ProduceVehicle TVehicle. I also have CarFactory:VehicleFactory where TVehicle:Car.

Car inherits from Vehicle, so all this is valid and allows me to know that my CarFactory must produce Cars and my Vehicle factory may produce any kind of vehicle. The problem I have run in to is that I need a way to instantiate a VehicleFactory as a CarFactory when it is being run by Ford, but as a BoatFactory when being run by Wave Runner.

I thought I could do this by creating an interface that matched the functionality of VehicleFactory and writing a MakeFactory method that returns an IVehicleFactory (which would return vehicles non-genericly). Since CarFactory returns cars, which are vehicles, it fulfills the interface and all should be right in the world. The unexpected problem is that VehicleFactory fails to meet the interface that is closed as TVehicle being Vehicle, despite the fact that TVehicle must be a Vehicle.

Does anyone know why this is or if there is another way to work around this limitation? If there isn't a way around this limitation directly, are there any alternative methods for having a shared set of functionality ensure that it is always instantiated as one of two or more sets of more specific classes. (Where Vehicle is the shared layer and Car and Boat are the context specific layers.)

class Vehicle
{
}

class Car : Vehicle
{
}

interface IVehicleFactory
{
     Vehicle ProduceVehicle();
}

class VehicleFactory<TVehicle> : IVehicleFactory
    where TVehicle:Vehicle
{
    public virtual TVehicle ProduceVehicle()
    {
    }
}

class CarFactory<TVehicle> : VehicleFactory<TVehicle>
    where TVehicle : Car
{
    public override TVehicle ProduceVehicle()
    {
    }
}

And the code using it

static IVehicleFactory CreateVehicleFactory()
{
    if(somecondition)
    {
         Return new CarFactory<Car>();
    }
    else
    {
         Return new BoatFactory<Boat>();
    }
}

Adding some more detail to clarify the problem.

The use of the term factory is not intended to imply a factory pattern. It is actually a repository that is retrieving a "vehicle." The type of vehicle is an application specific version of a library with a common shared base code. The repositories for each application Car and Boat may have different logic for the same retrieval and may be dependent on fields in the Car or Boat variant of their object. I need a way to build a factory which can look at the context of the application I am in and return the appropriate repository (BoatFactory or CarFactory), such that the shared code at the Vehicle level will properly use any application specific overridden functionality.

¿Fue útil?

Solución 4

Well, while I don't have a solution to how to make it work yet, (at least not in full), I think I now understand the problem. One of my initial assumptions appears to have been incorrect. I was under the impression that a less specific return type on an interface could be fulfilled by a more specific type. ie, an interface defining SomeBaseClass as a return type for a method can not be implemented by a class that implements the method returning SomeChildClass where SomeChildClass inherits from SomeBaseClass.

The use of covariant generics (via the out keyword) does provide some manner of ability to circumvent this by allowing the interface to be generically defined, however for my purposes, this falls apart when I hit results that need to be IList based (which requires an invariant type parameter).

Ultimately, the best it appears that can be done is that I can make it so that CarFactory only understands that it is working with a vehicle (by removing the additional where condition on the TVehicle generic parameter), requiring a cast within CarFactory, however when ever CarCompany instantiates CarFactory, it can still make it as CarFactory and the client code itself will not have to worry about a cast.

It's not ideal, but I think it may be the best possible due to C#'s lack of covariant return types.

interface IVehicleFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod();
}

VehicleFactory<TVehicle> : IVehilceFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod()
    {
    }
}

CarFactory<TVehicle> : IVehicleFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod()
    {
       ~~Always returns car and uses cast from Vehicle when necessary.
    }
}

class VehicleFactory<TVehicle>
{
   static IVehicleFactory<TVehicle> CreateContextSpecificFactory()
   {
       if(someCondition)
       {
            return new CarFactory<TVehicle>;
       }
   }
}

class CarCompany
{
    CarFactory<Car> carFactory = new CarFactory<Car>;
    Car car = carFactory.SomeMethod();
}

class VehicleCompany
{
    VehicleFactory<Vehicle> vehicleFactory = VehicleFactory<Vehicle>.CreateContextSpecificFactory();
}

Otros consejos

Hope this help:

interface IVehicleFactory<out T> where T : Vehicle
{
    T Create();
}

class CarFactory : IVehicleFactory<Car>
{
    public Car Create()
    {
        return new Car();
    }
}

class VehicleFactoryProvider
{
    public IVehicleFactory<T> GetFactoryFor<T>() where T : Vehicle
    {
        throw new NotImplementedException();
    }
}

class Program
{
    public static void Main(string[] args)
    {
        VehicleFactoryProvider provider = new VehicleFactoryProvider();
        var factory = provider.GetFactoryFor<Car>();
        var car = factory.Create();
    }
}

Your VehicleFactory<TVehicle> does not satisfy the IVehicleFactory interface. To satisfy it, it must have a method with the signature Vehicle ProduceVehicle(). You obviously don't have a method with that signature (but returns a derived type) so it will not work.

The easiest way to deal with this would be to explicitly implement the interface and return the result of calling your other method. The interface will be satisfied and you're still producing a vehicle of the correct type. This is similar to what you would usually do when implementing the IEnumerable<T> interface (which requires implementing the IEnumerable interface).

class VehicleFactory<TVehicle> : IVehicleFactory
    where TVehicle:Vehicle
{
    public virtual TVehicle ProduceVehicle()
    {
        throw new NotImplementedException();
    }

    // explicitly implement the interface by
    // returning the value of our actual method
    Vehicle IVehicleFactory.ProduceVehicle()
    {
        return ProduceVehicle();
    }
}

I'm thinking that perhaps a small refactor of architecture might be in order here; semantically, it seems like all you need is:

public class VehicleFactory
{
    protected Dictionary<Type, ISpecificTransportFactory> _mapping = // initialize

    public Vehicle CreateVehicle<TVehicle>() // any params
    {
        if(_mapping.ContainsKey(typeof(TVehicle))
          return _mapping[typeof(TVehicle)].CreateVehicle(); // any params that need to be passed

        throw new ConfigurationInvalidException("No transportFactory defined for : " + typeof(TVehicle).Name);
    }
}

Then with a dash of this:

public interface ISpecificTransportFactory
{
     Vehicle CreateVehicle();
}

public abstract class FactoryBase<TVehicleType> : ISpecificTransportFactory
    where TVehicleType : Vehicle
{
    public Vehicle CreateVehicle()
    {
        return CreateInstance();
    }

    public abstract TVehicleType CreateInstance();
}

public class CarFactory : FactoryBase<Car>
{
     public override Car CreateInstance()
     {
          return new Car();
     }
}

Theoretically, then you can use it in the following fashion:

public static void Main(string[] args)
{
     var vehicleFactory = new VehicleFactory();

     var vehicle = vehicleFactory.CreateVehicle<Car>();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top