Внедрение общего метода 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
{
}