c# - 给定类型 T,解析并使用通用类型 Calc<T> 的其他对象来处理 T

标签 c# design-patterns architecture

抱歉,如果标题不能很好地解释问题,但我想不出更好的名字。这可能会成为一个很长的问题,但请耐心等待。

假设我有两种类型的车辆 CarYacht它扩展了一个名为 IVehicle 的接口(interface).

接口(interface)本身对于这个问题并不重要,但类包含描述它们的属性。

然后我有一个 ITaxCalculator<T> where T : IVehicle其中包含方法 double Calculate(T vehicle); .

然后有几个类实现了不同版本的ITaxCalculator<T>。 (并采用不同的构造函数参数),例如:

  • LegitYachtTaxCalculator : ITaxCalculator<Yacht>
  • TaxHavenYachtTaxCalculator : ITaxCalculator<Yacht>
  • CarTaxCalculator : ITaxCalculator<Car>

然后我有一个 List<IVehicle>包含我的多辆汽车和游艇,我想计算我必须为它们支付的税款总额,同时能够切换用于计算每种类型税款的方法。


这是一些代码:

接口(interface):

ITaxCalculator<T>

public interface ITaxCalculator<T> where T : IVehicle
{
    double Calculate(T vehicle);
}

IVehicle

public interface IVehicle
{
    string RegistrationPlate { get; }
}

实现

Car

public class Car : IVehicle
{
    public Car(string registrationPlate)
    {
        RegistrationPlate = registrationPlate;
    }
    public string RegistrationPlate { get; }
}

Yacht

public class Yacht : IVehicle
{
    public int WeightInTons { get; }
    public Yacht(string registrationPlate, int weightInTons)
    {
        RegistrationPlate = registrationPlate;
        WeightInTons = weightInTons;
    }
    public string RegistrationPlate { get; }
}

CarTaxCalculator : ITaxCalculator<Car>

public class CarTaxCalculator : ITaxCalculator<Car>
{
    public double Calculate(Car vehicle)
    {
        Console.WriteLine($"Calculating tax for {vehicle.GetType().FullName} with plate {vehicle.RegistrationPlate} using {this.GetType().FullName}");

        return 4999.95;
    }
}

LegitYachtTaxCalculator : ITaxCalculator<Yacht>

public class LegitYachtTaxCalculator : ITaxCalculator<Yacht>
{
    public double WeightMultiplier { get; }
    public LegitYachtTaxCalculator(double weightMultiplier)
    {
        WeightMultiplier = weightMultiplier;
    }
    public double Calculate(Yacht vehicle)
    {
        Console.WriteLine($"Calculating tax for {vehicle.GetType().FullName} with plate {vehicle.RegistrationPlate} using {this.GetType().FullName}");

        return vehicle.WeightInTons * WeightMultiplier;
    }
}

TaxHavenYachtTaxCalculator : ITaxCalculator<Yacht>

public class TaxHavenYachtTaxCalculator : ITaxCalculator<Yacht>
{
    public double Calculate(Yacht vehicle)
    {
        Console.WriteLine($"Calculating tax for {vehicle.GetType().FullName} with plate {vehicle.RegistrationPlate} using {this.GetType().FullName}");

        return 0.0; // No taxes, woho!
    }
}

主要内容

static void Main(string[] args)
{
    List<IVehicle> vehicles = new List<IVehicle>
    {
        new Car("PLT111"),
        new Yacht("PLT333", 2500)
    };

    double totalTaxes = 0;

    foreach (var vehicle in vehicles)
    {
        //Here I want to use engines which are configurable earlier to calculate the taxes for the vehicles
    }
}

我已经尝试通过多种方式解决这个问题,例如

  • IoC 容器
  • 一个更轻量级的“解析器”变体,您可以在一个包含 Dictionary<Type, object> 的类中注册引擎。其中 object ITaxCalculators 是否通过以下方法注册和解析
    1. public ITaxCalculator<T> GetTaxCalculator<T>() where T : IVehicle
    2. public void RegisterTaxCalculator<T>(ITaxCalculator<T> calculator) where T : IPosition
  • 在创建过程中将 ITaxCalculator 传递给 T 的每个实例,并让 T 使用它计算自己的税收。

他们最终都感觉过于复杂,使用了不良做法或需要太多样板。所以我想知道构建它的最佳方法是什么?我是不是从一开始就走错了路,还是有什么模式我还没有想到?

谢谢!

最佳答案

基本上,您需要一种方法来定义税收计算器和车辆之间的映射。

首先,使税收计算器成为非通用的:

public interface ITaxCalculator
{
    double Calculate(IVehicle vehicle);
}

发现所有非泛型实现、将它们加载到集合中并调用计算要容易得多。

要处理特定的 IVehicle 实现细节,声明税计算器的基类,并使其通用:

public abstract class TaxCalculator<T> : ITaxCalculator
    where T : IVehicle
{
    public double Calculate(IVehicle vehicle)
    {
        // this is a single place, where cast is required
        return Calculate((T)vehicle);
    }

    protected abstract double Calculate(T vehicle);
}

映射任务将我们引向元数据。
大多数 DI 容器都具有此功能。例如,这里是 Autofac文档。

定义元数据类:

// This is metadata class;
// It defines vehicle type to calculate the tax value
public class TaxCalculatorMetadata
{
    public Type VehicleType { get; set; }
}

注册ITaxCalculator实现:

// Every implementation of ITaxCalculator must be registered like this
builder.RegisterType<CarTaxCalculator>()
    .As<ITaxCalculator>()
    .WithMetadata<TaxCalculatorMetadata>(m => m.For(_ => _.VehicleType, typeof(Car)));

现在您可以加载所有 ITaxCalculator 实现,以某种方式过滤它们(“将它们插入”插槽“”),并使用来自“插槽”的车辆类型获取特定计算器:

var vehicles = new List<Vehicle>
{
    // ...
};

// assuming, that tax calculators were imported  
// using IEnumerable<Lazy<ITaxCalculator, TaxCalculatorMetadata>>
foreach (var vehicle in vehicles)
{
    var taxCalculator = taxCalculators
        .First(_ => _.Metadata.VehicleType == vehicle.GetType());

    Console.WriteLine(taxCalculator.Value.Calculate(vehicle));
}

关于c# - 给定类型 T,解析并使用通用类型 Calc<T> 的其他对象来处理 T,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50253062/

相关文章:

c# - C# mono 出现奇怪的编译器错误

design-patterns - DAO 是否负责向 'join' 表中插入记录

jakarta-ee - 如何处理高流量 Java Web 应用程序中的负载?

mysql - 使用 Cloud Formation 将 MySQL RDS 存储类型从磁性更改为 SSD/预配置 IOPS

c# - 什么 SQlite 数据类型映射到 clr 数据类型 double?

c# - 在 WinRT 的默认 Web 浏览器中打开 URL

c# - 如何使用 LINQ 在组内进行排序

c# - 从 UI 中选择类型的最佳实践

ruby 的 Regexp.last_match 全局值内部方法

android - 单个应用程序中的多个同步适配器或仅 1 个