33 votes

Classe de refactoring pour se débarrasser du boîtier de l'interrupteur

Dire que j'ai une classe comme ça, pour le calcul des frais de déplacement des distances différentes avec différents modes de transport:

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

C'est très bien et tout, mais le commutateur peut être un cauchemar à l'entretien sage, et que si je veux utiliser de l'avion ou de train plus tard? Puis-je changer la classe ci-dessus. Quelle alternative à un commutateur cas pourrais-je l'utiliser ici et toutes les allusions à combien?

Je suis d'imaginer de l'utiliser dans une application console comme cela qui devait être exécuté depuis la ligne de commande avec des arguments de ce type de véhicule de transport que vous souhaitez utiliser, et la distance que vous voulez de voyage:

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

Tous les indicateurs grandement apprécié!

37voto

Kevin Points 57797

Vous pourriez faire quelque chose comme ceci:

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

Vous pouvez ensuite charger le type de transport et du modificateur dans un fichier de configuration au lieu d'utiliser une instruction switch. Je l'ai mis dans le constructeur de montrer l'exemple, mais il peut être chargé à partir de n'importe où. Je voudrais aussi probablement le Dictionnaire statique et ne charger qu'une seule fois. Il n'y a pas besoin de continuer à l'alimenter chaque fois que vous créez un nouveau TransportationCostCalculator surtout si il ne va pas changer au cours de l'exécution.

Comme l'a noté ci-dessus, voici comment vous pouvez charger un fichier de configuration:

void Main()
{
  // By Hard coding. 
  /*
    TransportationCostCalculator.AddTravelModifier("bicycle", 1);
    TransportationCostCalculator.AddTravelModifier("bus", 2);
    TransportationCostCalculator.AddTravelModifier("car", 3);
  */
    //By File 
    //assuming file is: name,value
    System.IO.File.ReadAllLines("C:\\temp\\modifiers.txt")
    .ToList().ForEach(line =>
        {
           var parts = line.Split(',');
        TransportationCostCalculator.AddTravelModifier
            (parts[0], Double.Parse(parts[1]));
        }
    );

}

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

    public static void AddTravelModifier(string name, double modifier)
    {
        if (_travelModifier.ContainsKey(name))
        {
            throw new Exception($"{name} already exists in dictionary.");
        }

        _travelModifier.Add(name, modifier);
    }

    public double DistanceToDestination { get; set; }

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


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

Edit: Il a été mentionné dans les commentaires que ce ne serait pas permettre à l'équation de l'être modifié si jamais il devait changer sans mettre à jour le code, j'ai donc écrit un post sur la façon de le faire ici: http://structuredsight.com/2016/03/07/configuring-logic.

31voto

Darrel Hoffman Points 585

Il me semble que toute solution fondée sur votre méthode actuelle est entaché d'erreur dans l'une de façon critique: peu importe comment vous le trancher, on met les données dans votre code. Cela signifie que chaque fois que vous voulez modifier l'un de ces numéros, ajouter un nouveau type de véhicule, etc., vous devez éditer le code, puis recompiler, de distribuer un patch, etc.

Ce que vous devez faire est de mettre les données là où il appartient - dans un autre, non-fichier compilé. Vous pouvez utiliser XML, JSON, une certaine forme de base de données, ou même juste un simple fichier de configuration. Chiffrer si vous le souhaitez, pas forcément nécessaire.

Ensuite, vous pouvez simplement écrire un analyseur qui lit le fichier et crée une carte de véhicule type de multiplicateur de coût ou d'autres propriétés que vous souhaitez enregistrer. L'ajout d'un nouveau véhicule devrait être aussi simple que la mise à jour de votre fichier de données. Pas besoin de modifier le code ou recompiler, etc. Beaucoup plus robuste et plus facile à maintenir si vous prévoyez d'ajouter des trucs dans le futur.

11voto

HimBromBeere Points 8328

Sonne comme un bon candidat pour l'injection de dépendance:

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

Maintenant, vous pouvez facilement créer une nouvelle classe Plane:

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

Maintenant, créez un constrcutor pour votre calculatrice qui s'attend à une instance d' ITransportation. Au sein de votre CostOfTravel-méthode, vous pouvez maintenant appeler ITransportation.CalcCosts(DistanceToDestination).

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

Ceci a l'avantage que vous pouvez échanger vos réels de transport instance sans aucun code de changement à votre TransportationCostCalculator-classe.

Pour compléter cette conception, vous pouvez également créer une TransportationFactory comme suit:

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

Ce que vous appelez comme

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

7voto

Tgsmith61591 Points 1507

Vous pouvez définir une classe abstraite comme celle-ci et avoir chacun TransportationMethod étendre la classe abstraite:

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

Donc, dans votre appel de méthode actuel, il pourrait être récrit comme ceci:

 public decimal CostOfTravel(TransportationMethod t) {
    return t.travelCost(DistanceToDestination);
}
 

4voto

Jack Hughes Points 1501

Vous pouvez utiliser une classe de stratégie pour chaque type de voyage. Mais alors, vous aurez probablement besoin d’une usine pour créer la stratégie basée sur la méthode de transport qui aurait probablement une instruction switch pour renvoyer la calculatrice appropriée.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X