实现一个通用的工厂方法

10

我已经实现了一个车辆服务,负责为汽车和卡车提供服务:

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,而从实现 IVehicleService<Car>CarService 转换为有效返回类型 IVehicleService<Vehicle> 是不可能的(如果我错了,请纠正我)。
希望能得到一些解决此问题的指导。

为什么要将其转换为IVehicleService<T>?如果从返回语句中删除“as IVehcileService<T>”会发生什么?@juharr所以我后来注意到了,因此我删除了我的评论。问题仍然存在,GetVehicle()返回什么? - oerkelens
现在的问题是,您没有一个通用类型,既可以将CarService转换为它,也可以将TruckService转换为它,这是有充分理由的。简单地说,IVehicleService<Car>不是IVehicleService<Vehicle> - juharr
@oerkelens 不重要,因为它要么是 Vehicle,要么被隐式转换为 Vehicle - juharr
1
啊,我这里出现了SRP头痛的严重情况!你试图把太多责任塞进一个可怜的工厂类中。事实上,你应该有一个门面工厂类,它作为交通管制器,指挥各个工厂为你服务,提供你需要的特定类。(深深地叹了口气,然后吃了一颗阿斯匹林)。 - code4life
2
在我进行代码审查时,经常会遇到一个普遍的抱怨:你不应该在没有检查结果的情况下使用 as。这样做会导致神秘的空引用异常,就像这样。如果你想表达“这个东西确实是这种类型”,那么就直接将其转换 - 至少当它失败时,你会在发生错误的地方得到一个无效的转换异常,而不是在不同位置难以调试的空引用异常。 - Philip Kendall
显示剩余4条评论
5个回答

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

请将车辆通过工厂检验:

涉及it技术的内容,暂无法提供更多信息。
    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的回答,只是因为它更接近我当前的实现方式。 - user8851548

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

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

或者,您可以坚持运行时检查,这在您的第一个示例中实现。


2

问题

您面临的问题与C#推断的通用类型有关。

Vehicle vehicle = GetVehicle();

这行代码会给你带来麻烦,因为你传递的vehicle变量类型不正确。

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

类型为Vehicle,并且不是Car(或者Truck)。因此你的工厂方法GetVehicleService<T>推断出的类型(T)是Vehicle。然而,在你的GetVehicleService方法中,你执行了一个安全转换(as),如果给定的类型不能按照你的意愿进行转换,则返回null。 如果你改成直接转换,

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

你会发现,在这一行代码中,调试器会捕获一个 InvalidCastException 异常。这是因为你的 CarService 实现了 IVehicleService<Car> 接口,但程序实际上尝试将其转换为 IVehicleService<Vehicle> 接口,而你的 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语句相比应该不会很慢。


谢谢,非常好的解决方案。标记为已接受的答案。 - user8851548
你说最后一个选项没有使用反射,但事实上你确实在使用反射! - DavidG
@DavidG,实际上不需要。自从.Net 4.7以来,您不需要使用反射来运行我的示例。您可以尝试在不使用System.Reflection命名空间的情况下编译它,它仍然可以编译。 - Timo
@DavidG 好的,你可能是对的。但是微软是否已经将 System.Type 中的重型内容移除,并将其转移到位于 System.Reflection 中的扩展方法中呢?这样低性能成本的函数就可以在 Reflection 命名空间之外访问了吧? - Timo
@DavidG 我不知道。我知道在 .Net Core 中曾经有一种不同的反射处理方式,但是在反射方面,.Net 4.7 之前和之后存在差异。我认为他们是从 .Net Core 中采用了这种方式。 - Timo
显示剩余3条评论

1
我会在Factory类中使用类似以下的内容:
```

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

谢谢您的建议,但如果可能的话,我宁愿不使用反射。 - user8851548
担心速度问题吗?https://dev59.com/NXA75IYBdhLWcg3wSm-4 - codebender

0

对于工厂实现,您可以使用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 会显著减缓实例化速度。 - FCin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接