【问题标题】:Implementing a generic factory method实现通用工厂方法
【发布时间】:2017-10-30 17:12:11
【问题描述】:

我已经实现了一个 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
    }
}

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

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");
    }
}

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

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

GetVehicleService 返回null,我猜是因为我将基本类型Vehicle 传递到此方法中,所以T 将评估为Vehicle,并且无法从@987654333 转换@(实现IVehicleService&lt;Car&gt;)到有效的返回类型,即IVehicleService&lt;Vehicle&gt;(如果我错了,请纠正我)。

不胜感激有关如何解决此问题的指导。

【问题讨论】:

  • @oerkelens 它为空,因为TVechicle,所以转换为IVehicleService&lt;Vechicle&gt; 失败并返回null
  • 为什么要转换为 IVehicleService?如果您从退货声明中删除 as IVehcileService&lt;T&gt; 会发生什么? @juharr所以我后来注意到了,因此我删除了我的评论。问题仍然存在,GetVehicle() 返回什么?
  • 现在的问题是你没有一个公共类型可以同时转换CarServiceTruckService,这是有充分理由的。简单地说IVehicleService&lt;Car&gt;不是IVehicleService&lt;Vehicle&gt;
  • 呃,我在这里遇到了严重的 SRP 头痛!你试图把太多的责任塞进一个单一的、糟糕的工厂班级。实际上,您应该有一个外观工厂类,它充当各个工厂的流量控制器,为您提供所需的特定类。 (……深深叹了口气,然后喝了一杯)。
  • 插入一个我在进行代码审查时的常见抱怨:你不应该在没有检查结果的情况下使用as。这样做会导致神秘的空引用异常,就像这样。如果您的意思是说“这东西确实属于这种类型”,那么只需转换它 - 至少当它失败时,您会在它发生的确切位置得到一个无效的转换异常,而不是在不同的位置难以调试空引用异常。

标签: c# generics interface factory-method


【解决方案1】:

问题

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

Vehicle vehicle = GetVehicle();

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

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

属于Vehicle 类型,属于Car(或Truck)类型。因此您的工厂方法GetVehicleService&lt;T&gt; 推导出的类型(T)是Vehicle。但是,在您的 GetVehicleService 方法中,如果不能按照您的意愿转换给定的类型,您将执行返回 null 的安全转换 (as)。 如果您将其更改为直接投射

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

您会看到,调试器将在这一行捕获 InvalidCastException。这是因为您的CarService 实现了IVehicleService&lt;Car&gt;,但程序实际上试图将其转换为IVehicleService&lt;Vehicle&gt;,而您的CarService 没有实现它,因此引发了异常。

如果你完全移除演员表

return new CarService();

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

解决方案

不幸的是,我不知道可以由 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");
    }
}

如您所见,工厂现在是非泛型的,接口也是非泛型的(就像您之前拥有的一样)。但是,服务的抽象基类现在可以处理类型并在类型不匹配时抛出异常(不幸的是仅在运行时)。

一个(也许)有用的补充

如果你的工厂有很多不同的类型,并且你想保存几十个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
// ...

然后像这样改变你的工厂:

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 语句相比应该不会那么慢。

【讨论】:

  • 谢谢,非常好的解决方案。标记为已接受的答案。
  • 你说最后一个选项没有使用反射,但你是!
  • @DavidG 实际上不是。从 .Net 4.7 开始,我的示例不需要反射。您可以尝试在不使用 System.Reflection 命名空间的情况下编译它,它仍然可以编译。
  • 任何时候你做GetType()你都在使用反射。你不需要命名空间。
  • @DavidG 好的,你可能是对的。但是 MS 没有从 System.Type 中删除繁重的东西到位于 System.Reflection 中的扩展方法吗?这样可以在 Reflection 命名空间之外访问低性能成本函数吗?
【解决方案2】:

有一种方法可以避免在 IVehicleService 上使用泛型,并避免将卡车传递到 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}");
        }
    }

并让工厂通过车辆:

    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
    {

    }

【讨论】:

  • 谢谢,非常简单的解决方案。我接受 Timo 的答案只是因为它更接近我当前的实现。
【解决方案3】:

您要求编译时类型安全。然而,您正在使用在编译时类型未知的代码。在这个例子中......

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);

工厂将返回相应的服务类。

要么这样,要么坚持运行时检查,这在您的第一个示例中实现。

【讨论】:

    【解决方案4】:

    我会在 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); }
    }
    

    【讨论】:

    【解决方案5】:

    对于工厂实现,您可以使用MEF

    这将允许您使用具有唯一名称的 Export 和 Import 属性来实现,并且您不需要 if else/witch 语句来创建工厂。

    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
    {
    
    }
    

    【讨论】:

    • MEF 大大减慢了实例化速度。
    猜你喜欢
    • 2020-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多