Внедрение общего метода factory

Я внедрил сервис Vehicle, который отвечает за обслуживание автомобилей, таких как автомобили и грузовики:

public interface IVehicleService
{
    void ServiceVehicle(Vehicle vehicle);   
}

public class CarService : IVehicleService
{
    void ServiceVehicle(Vehicle vehicle)
    {
        if (!(vehicle is Car))
            throw new Exception("This service only services cars")

       //logic to service the car goes here
    }
}

У меня также есть транспортное средство factory, которое отвечает за создание службы транспортного средства в соответствии с типом транспортного средства, переданного в метод factory:

public class VehicleServiceFactory 
{
    public IVehicleService GetVehicleService(Vehicle vehicle)
    {
        if (vehicle is Car)
        {
            return new CarService();
        }

        if (vehicle is Truck)
        {
            return new TruckService();
        }

        throw new NotSupportedException("Vehicle not supported");
    }
}

Проблема была у меня с методом CarService.ServiceVehicle. Он принимает Vehicle, когда в идеале должен принимать Car, поскольку он знает, что он будет обслуживать только автомобили. Поэтому я решил обновить эту реализацию, чтобы вместо этого использовать дженерики:

public interface IVehicleService<T> where T : Vehicle
{
    void ServiceVehicle(T vehicle); 
}

public class CarService : IVehicleService<Car>
{
    void ServiceVehicle(Car vehicle)
    {
        //this is better as we no longer need to check if vehicle is a car

        //logic to service the car goes here 
    }
}

public class VehicleServiceFactory 
{
    public IVehicleService<T> GetVehicleService<T>(T vehicle) where T : Vehicle
    {
        if (vehicle is Car)
        {
            return new CarService() as IVehicleService<T>;
        }

        if (vehicle is Truck)
        {
            return new TruckService() as IVehicleService<T>;
        }

        throw new NotSupportedException("Vehicle not supported");
    }
}

В настоящее время проблема заключается в вызове этого factory следующим образом:

var factory = new VehicleServiceFactory();
Vehicle vehicle = GetVehicle();
var vehicleService = factory.GetVehicleService(vehicle);  // this returns null!
vehicleService.ServiceVehicle(vehicle);

GetVehicleService возвращает null, я думаю, потому что я передаю базовый тип Vehicle в этот метод, поэтому T будет оценивать до Vehicle, и невозможно отбросить из CarService (который реализует IVehicleService<Car>) для эффективного типа возврата, который был бы IVehicleService<Vehicle> (пожалуйста, исправьте меня, если я ошибаюсь).

Порадовал бы некоторые рекомендации относительно того, как разрешить это.

Ответ 1

Проблема

Проблема, с которой вы сталкиваетесь, связана с общим типом С#.

Vehicle vehicle = GetVehicle();

Эта строка вызывает проблемы, поскольку тип переменной vehicle, который вы передаете в

var vehicleService = factory.GetVehicleService(vehicle);  // this returns null!

имеет тип vehicle и не типа Car (или Truck). Поэтому тип, который ваш метод factory GetVehicleService<T> выводит (T), равен vehicle. Однако в вашем методе GetVehicleService вы выполняете безопасный листинг (as), который возвращает null, если данный тип не может быть отличен по вашему желанию. Если вы измените его на прямой трансляции

return (IVehicleService<T>) new CarService();

вы увидите, что отладчик поймает InvalidCastException в этой строке. Это связано с тем, что ваш CarService реализует IVehicleService<Car>, но программа на самом деле пытается передать его в IVehicleService<Vehicle>, который не реализован вашим CarService и, следовательно, генерирует исключение.

Если вы вообще удалите бросок на

return new CarService();

вы даже получите ошибку во время компиляции, сообщив вам, что эти типы не могут быть переданы друг другу.

Решение

К сожалению, я не знаю, какое решение можно решить с помощью С#. Однако вы можете создать абстрактный базовый класс для своих сервисов, реализуя не общий интерфейс:

public interface IVehicleService
{
    void ServiceVehicle(Vehicle vehicle);
}

public abstract class VehicleService<T> : IVehicleService where T : Vehicle
{
    public void ServiceVehicle(Vehicle vehicle)
    {
        if (vehicle is T actual)
            ServiceVehicle(actual);
        else
            throw new InvalidEnumArgumentException("Wrong type");
    }

    public abstract void ServiceVehicle(T vehicle);
}

public class CarService : VehicleService<Car>
{
    public override void ServiceVehicle(Car vehicle)
    {
        Console.WriteLine("Service Car");
    }
}

public class TruckService : VehicleService<Truck>
{
    public override void ServiceVehicle(Truck vehicle)
    {
        Console.WriteLine("Service Truck");
    }
}

public class VehicleServiceFactory
{
    public IVehicleService GetVehicleService(Vehicle vehicle)
    {
        if (vehicle is Car)
        {
            return new CarService();
        }

        if (vehicle is Truck)
        {
            return new TruckService();
        }

        throw new NotSupportedException("Vehicle not supported");
    }
}

Как вы можете видеть, factory теперь не является общим, а также интерфейс (как и раньше). Однако базовый класс abstratc для сервисов теперь может обрабатывать типы и исключать исключение (к сожалению, только во время выполнения), если типы не совпадают.

A (возможно) полезное дополнение

Если ваш factory имеет много разных типов, и вы хотите сохранить десятки операторов if, вы можете сделать небольшое обходное решение с атрибутами.

Сначала создайте класс ServiceAttribute:

[AttributeUsage(AttributeTargets.Class)]
public class ServiceAttribute : Attribute
{
    public Type Service { get; }

    public ServiceAttribute(Type service)
    {
        Service = service;
    }
}

Затем добавьте этот атрибут в классы вашего автомобиля:

[Service(typeof(TruckService))]
public class Truck : Vehicle
// ...

И измените ваш factory следующим образом:

public class VehicleServiceFactory
{
    public IVehicleService GetVehicleService(Vehicle vehicle)
    {
        var attributes = vehicle.GetType().GetCustomAttributes(typeof(ServiceAttribute), false);

        if (attributes.Length == 0)
            throw new NotSupportedException("Vehicle not supported");

        return (IVehicleService) Activator.CreateInstance(((ServiceAttribute)attributes[0]).Service);
    }
}

Эта методология не использует отражение и поэтому не должна быть такой медленной по сравнению с операторами if.

Ответ 2

Вы запрашиваете безопасность типа компиляции. Тем не менее, вы используете код, где тип неизвестен во время компиляции. В этом примере....

var factory = new VehicleServiceFactory();
Vehicle vehicle = GetVehicle();  //Could return any kind of vehicle
var vehicleService = factory.GetVehicleService(vehicle);  
vehicleService.ServiceVehicle(vehicle);

... тип vehicle просто не известен при компиляции кода.

Даже если вы могли бы это сделать, вы не могли бы ничего сделать с возвращенным классом, потому что опять же, вы не знаете тип во время компиляции:

CarService s = new CarSevice();
Vehicle v = new Car();
s.ServiceVehicle(v); //Compilation error

Если вам нужна проверка времени компиляции, вам нужно объявить тип во время компиляции. Поэтому просто измените это на следующее:

var factory = new VehicleServiceFactory();
Car vehicle = GetCar();  //<-- specific type
var vehicleService = factory.GetVehicleService(vehicle);  
vehicleService.ServiceVehicle(vehicle);

Или, если вы настаиваете на том, чтобы удерживать автомобиль с переменной типа vehicle, вы можете использовать

var factory = new VehicleServiceFactory();
Vehicle vehicle = GetCar();  
var vehicleService = factory.GetVehicleService<Car>(vehicle);   //Explicit type
vehicleService.ServiceVehicle(vehicle);

И factory вернет соответствующий класс службы.

Либо это, либо придерживаться проверки выполнения, которая реализована в вашем первом примере.

Ответ 3

В классе Factory я использовал бы что-то вроде следующего:

public T GetVehicle<T>(T it) {
    try {
        Type type = it.GetType();                                   // read  incoming Vehicle type
        ConstructorInfo ctor = type.GetConstructor(new[] { type }); // get its constructor
        object instance = ctor.Invoke(new object[] { it });         // invoke its constructor
        return (T)instance;                                         // return proper vehicle type
    } catch { return default(T); }
}

Ответ 4

Там вы можете избежать использования Generics на IVehicleService и избегать проблемы передачи Truck в CarService и наоборот. Вы можете сначала изменить IVehicleService, чтобы он не был общим, или передать вежику в:

public interface IVehicleService
{
    void ServiceVehicle();
}

вместо этого мы передаем транспортное средство в конструктор CarService/TruckService:

    public class CarService : IVehicleService
    {
        private readonly Car _car;

        public CarService(Car car)
        {
            _car = car;
        }

        public void ServiceVehicle()
        {
            Console.WriteLine($"Service Car {_car.Id}");
        }
    }

И передайте factory автомобиль в:

    public class VehicleServiceFactory
    {
        public IVehicleService GetVehicleService(Vehicle vehicle)
        {
            if (vehicle is Car)
            {
                return new CarService((Car)vehicle);
            }

            if (vehicle is Truck)
            {
                return new TruckService((Truck)vehicle);
            }

            throw new NotSupportedException("Vehicle not supported");
        }
    }

Так я бы это реализовал

    public static void Main(string[] args)
    {
        var factory = new VehicleServiceFactory();
        Vehicle vehicle = GetVehicle();
        var vehicleService = factory.GetVehicleService(vehicle);
        vehicleService.ServiceVehicle();

        Console.ReadLine();

    }

    public static Vehicle GetVehicle()
    {
        return new Truck() {Id=1};

        //return new Car() { Id = 2 }; ;
    }


    public interface IVehicleService
    {
        void ServiceVehicle();
    }

    public class CarService : IVehicleService
    {
        private readonly Car _car;

        public CarService(Car car)
        {
            _car = car;
        }

        public void ServiceVehicle()
        {
            Console.WriteLine($"Service Car {_car.Id}");
        }
    }

    public class TruckService : IVehicleService
    {
        private readonly Truck _truck;

        public TruckService(Truck truck)
        {
            _truck = truck;
        }

        public void ServiceVehicle()
        {
            Console.WriteLine($"Service Truck {_truck.Id}");
        }
    }

    public class VehicleServiceFactory
    {
        public IVehicleService GetVehicleService(Vehicle vehicle)
        {
            if (vehicle is Car)
            {
                return new CarService((Car)vehicle);
            }

            if (vehicle is Truck)
            {
                return new TruckService((Truck)vehicle);
            }

            throw new NotSupportedException("Vehicle not supported");
        }
    }


    public abstract class Vehicle
    {
        public int Id;

    }

    public class Car : Vehicle
    {

    }

    public class Truck : Vehicle
    {

    }

Ответ 5

Для реализации factory вы можете использовать MEF

Это позволит вам реализовать с атрибутами Export и Import с уникальными именами, и вам не понадобятся инструкции if else/witch для создания factory.

class Program
{
    private static CompositionContainer _container;

    public Program()
    {

        var aggList = AppDomain.CurrentDomain
                               .GetAssemblies()
                               .Select(asm => new AssemblyCatalog(asm))
                               .Cast<ComposablePartCatalog>()
                               .ToArray();

        var catalog = new AggregateCatalog(aggList);

        _container = new CompositionContainer(catalog);
        _container.ComposeParts(this);
    }

    static void Main(string[] args)
    {
        var prg = new Program();

        var car = _container.GetExportedValue<IVehicle>("CAR") as Car; 
        var carService = _container.GetExportedValue<IVehicleService<Car>>("CARSERVICE") as CarService;
        carService.ServiceVehicle(car);

        var truck = _container.GetExportedValue<IVehicle>("TRUCK") as Truck;
        var truckService = _container.GetExportedValue<IVehicleService<Truck>>("TRUCKSERVICE") as TruckService;
        truckService.ServiceVehicle(truck);

        Console.ReadLine();
    }
}

public interface IVehicleService<in T> 
{
    void ServiceVehicle(T vehicle);
}

public interface IVehicle
{
}

[Export("CARSERVICE", typeof(IVehicleService<Car>)), PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public class CarService : IVehicleService<Car>
{
    public void ServiceVehicle(Car vehicle)
    {
    }
}

[Export("TRUCKSERVICE", typeof(IVehicleService<Truck>)), PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public class TruckService : IVehicleService<Truck>
{
    public void ServiceVehicle(Truck vehicle)
    {

    }
}

public abstract class Vehicle : IVehicle
{

}

[Export("CAR", typeof(IVehicle)), PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public class Car : Vehicle
{

}

[Export("TRUCK", typeof(IVehicle)), PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public class Truck : Vehicle
{

}