Refactoring class to get rid of switch case

2020-06-06 19:20发布

Say I have a class like this for calculating the cost of travelling different distances with different modes of transportation:

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

This is fine and all, but switch cases can be a nightmare to maintenance wise, and what if I want to use airplane or train later on? Then I have to change the above class. What alternative to a switch case could I use here and any hints to how?

I'm imagining using it in a console application like this which would be run from the command-line with arguments for what kind of transportation vehicle you want to use, and the distance you want to travel:

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

Any hints greatly appreciated!

标签: c#
10条回答
相关推荐>>
2楼-- · 2020-06-06 19:42

I prefer to use Enum for that like this:

public enum TransportMethod
{
    Bicycle = 1,
    Bus = 2,
    Car = 3
}

And use it like this method:

public decimal CostOfTravel(string transportMethod)
{
    var tmValue = (int)Enum.Parse(typeof(TransportMethod), transportMethod);
    return DistanceToDestination  * tmValue;
}

Note that above method is case-sensitive, So you can capitalize first char;

Related Answer

查看更多
够拽才男人
3楼-- · 2020-06-06 19:43

This is a case for the strategy design pattern. Create a base class, say TravelCostCalculator, then develop classes for each mode of travel you will consider, each overriding a common method, Calculate(double). You can then instantiate the specific TravelCostCalculator as needed using the factory pattern.

The trick is in how to construct the factory (without a switch statement). The way I do this is by having a static class constructor (public static Classname() - not an instance constructor) that registers each strategy class with the factory in a Dictionary<string, Type>.

Since C# does not run class constructors deterministically (like C++ does in most cases) you have to explicitly run them to ensure they will run. This could be done in the main program or in the factory constructor. The downside is that if you add a strategy class, you must also add it to the list of constructors to be run. You can either create a static method that must be run (Touch or Register) or you can also use 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]);
    }
}
查看更多
三岁会撩人
4楼-- · 2020-06-06 19:46

You could define an abstract class like this, and have each TransportationMethod extend the abstract class:

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

So in your actual method call, it could be rewritten like this:

public decimal CostOfTravel(TransportationMethod t) {
    return t.travelCost(DistanceToDestination);
}
查看更多
三岁会撩人
5楼-- · 2020-06-06 19:47

Sounds like a good candidate for dependency-injection:

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

Now you can easily create a new class Plane:

class Plane : ITransportation {
    decimal CalcCosts(double distance) { return (decimal)(distance * 4); }
}

Now create a constrcutor for your calculator that expects an instance of ITransportation. Within your CostOfTravel-method you can now call ITransportation.CalcCosts(DistanceToDestination).

var calculator = new TransportationCostCalculator(new Plane());

This has the advantage that you can exchange your actual transportation-instance without any code-change to your TransportationCostCalculator-class.

To complete this design you might also create a TransportationFactory as follows:

class TransportationFactory {
    ITransportation Create(string type) {
        switch case "Bus": return new Bus(); break
        // ...
}

Which you call like

ITransportation t = myFactory.Create("Bus");
TransportationCostCalculator calculator = new TransportationCostCalculator(t);
var result = myCalculator.CostOfTravel(50);
查看更多
登录 后发表回答