实现通用工厂方法

use*_*548 10 c# generics interface factory-method

我已经实施了一项车辆服务,负责维修汽车和卡车等车辆:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我还有一个车辆服务工厂,负责根据传入工厂方法的车辆类型创建车辆服务:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是这个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");
    }
}
Run Code Online (Sandbox Code Playgroud)

我目前遇到的问题是在调用此工厂时如下:

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

GetVehicleService返回null,我想是因为我将基类型传递Vehicle给此方法,因此T将进行求值,Vehicle并且不可能从CarService(实现IVehicleService<Car>)转换为有效的返回类型IVehicleService<Vehicle>(如果我是,请纠正我)错误).

希望得到一些关于如何解决这个问题的指导.

Tim*_*imo 2

问题

您面临的问题与 C# 推论的泛型类型有关。

Vehicle vehicle = GetVehicle();
Run Code Online (Sandbox Code Playgroud)

这行会给你带来麻烦,因为vehicle你传入的变量类型

var vehicleService = factory.GetVehicleService(vehicle);  // this returns null!
Run Code Online (Sandbox Code Playgroud)

是 类型Vehicle不是Car( 或)类型Truck。因此,您的工厂方法推导出的类型GetVehicleService<T>(T) 是Vehicle。但是,在 GetVehicleService 方法中,如果给定类型无法按您希望的方式进行强制转换,则执行安全强制转换 ( as) 并返回。null如果改成直接演员

return (IVehicleService<T>) new CarService();
Run Code Online (Sandbox Code Playgroud)

您将看到,调试器将在此行捕获 InvalidCastException。这是因为您CarService实现了IVehicleService<Car>,但程序实际上尝试将其转换为IVehicleService<Vehicle>您未实现的对象CarService,因此抛出异常。

如果你完全移除演员阵容

return new CarService();
Run Code Online (Sandbox Code Playgroud)

您甚至会在编译时收到错误,告诉您这些类型不能相互转换。

一个办法

不幸的是,我不知道可以由 C# 处理的简洁解决方案。但是,您可以为您的服务创建一个抽象基类,实现一个非通用接口:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,工厂现在是非通用的,接口也是如此(就像您之前的接口一样)。然而,服务的抽象基类现在可以处理类型并在类型不匹配时抛出异常(不幸的是仅在运行时)。

一个(也许)有用的补充

如果您的工厂有很多不同的类型并且您想要保存数十条if语句,您可以使用属性做一些解决方法。

首先,创建一个ServiceAttribute类:

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

    public ServiceAttribute(Type service)
    {
        Service = service;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将此属性附加到您的车辆类别:

[Service(typeof(TruckService))]
public class Truck : Vehicle
// ...
Run Code Online (Sandbox Code Playgroud)

并像这样改变你的工厂:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法不使用反射,因此与 if 语句相比应该不会那么慢。