C# 重构类以消除开关情况

C# 重构类以消除开关情况,c#,C#,假设我有一个这样的类,用于计算使用不同交通方式进行不同距离旅行的成本: public class TransportationCostCalculator { public double DistanceToDestination { get; set; } public decimal CostOfTravel(string transportMethod) { switch (transportMethod) {

假设我有一个这样的类,用于计算使用不同交通方式进行不同距离旅行的成本:

public class TransportationCostCalculator
{
    public double DistanceToDestination { get; set; }

    public decimal CostOfTravel(string transportMethod)
    {
        switch (transportMethod)
        {
            case "Bicycle":
                return (decimal)(DistanceToDestination * 1);
            case "Bus":
                return (decimal)(DistanceToDestination * 2);
            case "Car":
                return (decimal)(DistanceToDestination * 3);
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
这很好,但开关箱对维护人员来说可能是一场噩梦,如果我以后想使用飞机或火车怎么办?那我就要换上面的课了。我可以在这里使用什么样的开关箱替代方案,以及如何使用的提示

我设想在这样的控制台应用程序中使用它,该应用程序将从命令行运行,并带有您想要使用哪种交通工具以及您想要行驶的距离的参数:

class Program
{
    static void Main(string[] args)
    {
        if(args.Length < 2)
        {
            Console.WriteLine("Not enough arguments to run this program");
            Console.ReadLine();
        }
        else
        {
            var transportMethod = args[0];
            var distance = args[1];
            var calculator = new TransportCostCalculator { DistanceToDestination = double.Parse(distance) };
            var result = calculator.CostOfTravel(transportMethod);
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }
}
类程序
{
静态void Main(字符串[]参数)
{
如果(参数长度<2)
{
WriteLine(“没有足够的参数来运行此程序”);
Console.ReadLine();
}
其他的
{
var transportMethod=args[0];
var距离=args[1];
var calculator=new TransportCostCalculator{DistanceToDestination=double.Parse(distance)};
var结果=计算器。旅行成本(运输法);
控制台写入线(结果);
Console.ReadLine();
}
}
}

非常感谢任何提示

对于每种类型的旅行,您都可以使用策略类。但是,您可能需要一个工厂根据传输方法创建策略,该方法可能有一个switch语句来返回相应的计算器

    public class CalculatorFactory {
        public static ICalculator CreateCalculator(string transportType) {
            switch (transportType) {
                case "car":
                    return new CarCalculator();
                ...
public class CarCalculator : ICalculator {
    public decimal Calc(double distance) {
        return distance * 1;
    }
}
....

听起来像是依赖注入的一个很好的候选者:

interface ITransportation {
    decimal CalcCosts(double distance);
}

class Bus : ITransportation { 
    decimal CalcCosts(double distance) { return (decimal)(distance * 2); }
}
class Bicycle : ITransportation { 
    decimal CalcCosts(double distance) { return (decimal)(distance * 1); }
}
class Car: ITransportation {
    decimal CalcCosts(double distance) { return (decimal)(distance * 3); }
}
现在,您可以轻松创建一个新类
平面

class Plane : ITransportation {
    decimal CalcCosts(double distance) { return (decimal)(distance * 4); }
}
现在为您的计算器创建一个构造函数,它需要一个
ITransportation
实例。在您的
CostOfTravel
-方法中,您现在可以调用
ITransportation.CalcCosts(DistanceToDestination)

这样做的好处是,您可以在不更改
TransportationCostCalculator
-类代码的情况下交换实际的transportation实例

要完成此设计,您还可以创建一个
TransportationFactory
,如下所示:

class TransportationFactory {
    ITransportation Create(string type) {
        switch case "Bus": return new Bus(); break
        // ...
}
你管它叫什么

ITransportation t = myFactory.Create("Bus");
TransportationCostCalculator calculator = new TransportationCostCalculator(t);
var result = myCalculator.CostOfTravel(50);

你可以这样做:

public class TransportationCostCalculator {
    Dictionary<string,double> _travelModifier;

    TransportationCostCalculator()
    {
        _travelModifier = new Dictionary<string,double> ();

        _travelModifier.Add("bicycle", 1);
        _travelModifier.Add("bus", 2);
        _travelModifier.Add("car", 3);
    }


    public decimal CostOfTravel(string transportationMethod) =>
       (decimal) _travelModifier[transportationMethod] * DistanceToDestination;
}

编辑:评论中提到,如果方程式需要更改而不更新代码,则不允许对其进行修改,因此我在这里写了一篇关于如何执行的帖子:。

您可以这样定义一个抽象类,并让每个
TransportationMethod
扩展抽象类:

abstract class TransportationMethod {
    public TransportationMethod() {
        // constructor logic
    }

    abstract public double travelCost(double distance);
}

class Bicycle : TransportationMethod {
    public Bicycle() : base() { }

    override public double travelCost(double distance) {
        return distance * 1;
    }
}

class Bus : TransportationMethod {
    public Bus() : base() { }

    override public double travelCost(double distance) {
        return distance * 2;
    }
}

class Car : TransportationMethod {
    public Car() : base() { }

    override public double travelCost(double distance) {
        return distance * 3;
    }
}
因此,在实际的方法调用中,可以这样重写:

public decimal CostOfTravel(TransportationMethod t) {
    return t.travelCost(DistanceToDestination);
}
public enum TransportMethod
{
    Bicycle = 1,
    Bus = 2,
    Car = 3
}
您可以创建一个基于传输返回乘数的

public class TransportationCostCalculator
{
    Dictionary<string, int> multiplierDictionary;

    TransportationCostCalculator () 
    {
         var multiplierDictionary= new Dictionary<string, int> (); 
         dictionary.Add ("Bicycle", 1);
         dictionary.Add ("Bus", 2);
         ....
    }

    public decimal CostOfTravel(string transportMethod)
    {
         return  (decimal) (multiplierDictionary[transportMethod] * DistanceToDestination);       
    }
公共类运输成本计算器
{
字典乘数字典;
运输成本计算器()
{
var multiperdictionary=newdictionary();
加上(“自行车”,1);
添加(“总线”,2);
....
}
公共十进制CostOfTravel(字符串传输方法)
{
返回(十进制)(乘法器字典[transportMethod]*距离目标);
}

我认为答案是某种数据库

如果您使用了一些,TransportCostCalculator会向数据库询问多人游戏的特定transportmethod

数据库可以是文本文件、xml或SQL server。只需一个键值对

如果你只想使用代码,就没有办法避免从transportmethod到multiplayer(或cost)的转换


使用数据库将字典从代码中删除,并且不能更改代码以应用新的传输方法或改变值。

< P>这是策略设计模式的一个例子。创建基类,例如“代码> ToalCuxCalpalCalpUT/CODE >,然后开发您将考虑的每种旅行模式的类,每个都覆盖一个共同点。方法,
Calculate(double)
。然后可以根据需要使用factory模式实例化特定的
TravelCostCalculator

诀窍在于如何构造工厂(没有switch语句)。我这样做的方法是使用静态类构造函数(
publicstaticclassname()
-不是实例构造函数),在
字典中向工厂注册每个策略类

因为C不确定地运行类构造函数(像大多数情况下C++一样)您必须显式运行它们以确保它们运行。这可以在主程序或工厂构造函数中完成。缺点是,如果添加策略类,还必须将其添加到要运行的构造函数列表中。您可以创建必须运行的静态方法(

Touch
Register
)或者您也可以使用
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor

class Derived : Base
{
    public static Derived()
    {
        Factory.Register(typeof(Derived));
    }
}

// this could also be done with generics rather than Type class
class Factory
{
    public static Register(Type t)
    {
        RegisteredTypes[t.Name] = t;
    }
    protected Dictionary<string, Type t> RegisteredTypes;

    public static Base Instantiate(string typeName)
    {
        if (!RegisteredTypes.ContainsKey(typeName))
            return null;
        return (Base) Activator.CreateInstance(RegisteredTypes[typeName]);
    }
}
派生类:基
{
公共静态派生()
{
工厂登记簿(类型(派生));
}
}
//这也可以通过泛型而不是类型类来实现
阶级工厂
{
公共静态寄存器(t型)
{
RegisteredTypes[t.Name]=t;
}
受保护的字典注册类型;
公共静态基实例化(字符串类型名)
{
如果(!RegisteredTypes.ContainsKey(typeName))
返回null;
return(Base)Activator.CreateInstance(RegisteredTypes[typeName]);
}
}

在我看来,基于您当前方法的任何解决方案都有一个关键缺陷:无论您如何分割,都是在代码中添加数据。这意味着每次您想要更改这些数字、添加新车型等,都必须编辑代码,然后重新编译、分发补丁等

你真正应该做的是把数据放在它所属的地方——放在一个单独的、未编译的文件中。你可以使用XML、JSON、某种形式的数据库,甚至只是一个简单的配置文件。如果你想加密它,不必加密
class Derived : Base
{
    public static Derived()
    {
        Factory.Register(typeof(Derived));
    }
}

// this could also be done with generics rather than Type class
class Factory
{
    public static Register(Type t)
    {
        RegisteredTypes[t.Name] = t;
    }
    protected Dictionary<string, Type t> RegisteredTypes;

    public static Base Instantiate(string typeName)
    {
        if (!RegisteredTypes.ContainsKey(typeName))
            return null;
        return (Base) Activator.CreateInstance(RegisteredTypes[typeName]);
    }
}
public enum TransportMethod
{
    Bicycle = 1,
    Bus = 2,
    Car = 3
}
public decimal CostOfTravel(string transportMethod)
{
    var tmValue = (int)Enum.Parse(typeof(TransportMethod), transportMethod);
    return DistanceToDestination  * tmValue;
}