4 votes

Modèle de conception pour une application de calcul des coûts ?

J'ai un problème pour lequel j'ai déjà essayé d'obtenir de l'aide, mais je n'ai pas réussi à le résoudre à l'époque. J'essaie donc de simplifier le problème maintenant pour voir si je peux obtenir une aide plus concrète, car cela me rend fou

En fait, j'ai une version fonctionnelle (plus complexe) de cette application, qui est un calculateur de coût de projet. Mais comme j'essaie en même temps d'apprendre à mieux concevoir mes applications, j'aimerais avoir des commentaires sur la façon dont je pourrais améliorer cette conception. En fait, j'aimerais surtout avoir des commentaires sur les conditionnelles qui (ici) se répètent à deux endroits. Les suggestions que j'ai reçues auparavant étaient d'utiliser le modèle de stratégie ou le modèle d'usine. Je connais aussi le livre de Martin Fowler qui suggère de refaire du conditionnel avec du polymorphisme. Je comprends ce principe dans son exemple le plus simple. Mais comment puis-je faire l'une ou l'autre de ces choses ici (si elles conviennent) ? De la façon dont je vois les choses, le calcul dépend de deux conditions : 1. De quel type de service s'agit-il, rédaction ou analyse ? 2. Le projet est-il petit, moyen ou grand ? (Veuillez noter qu'il peut y avoir d'autres paramètres, tout aussi différents, tels que "les produits sont-ils nouveaux ou existent-ils déjà ?") Il devrait donc être possible d'ajouter de tels paramètres, mais j'ai essayé de garder l'exemple simple avec seulement deux paramètres afin d'obtenir une aide concrète).

Ainsi, le remaniement avec polymorphisme impliquerait la création d'un certain nombre de sous-classes, que j'ai déjà pour la première condition (type de service), et devrais-je vraiment créer d'autres sous-classes pour la deuxième condition également (taille) ? Qu'est-ce que cela donnerait, AnalysisSmall, AnalysisMedium, AnalysisLarge, WritingSmall, etc ??? Non, je sais que ce n'est pas bon, mais je ne vois pas comment travailler avec ce modèle.

Je vois le même problème pour les suggestions d'utilisation du modèle de stratégie (et le modèle de fabrique tel que je le vois ne serait qu'une aide pour réaliser le polymorphisme ci-dessus). Donc s'il vous plaît, si quelqu'un a des suggestions concrètes sur la meilleure façon de concevoir ces classes, je vous en serais vraiment reconnaissant ! Veuillez également considérer si j'ai choisi les objets correctement ou s'ils ont besoin d'être redessinés. (Les réponses du type "vous devriez considérer le modèle d'usine" ne seront évidemment pas utiles...) J'ai déjà emprunté cette voie et je ne sais pas exactement comment faire dans ce cas).

Voir aussi,

Anders

Le code (très simplifié, ne vous préoccupez pas du fait que j'utilise des chaînes au lieu d'enums, que je n'utilise pas de fichier de configuration pour les données etc, cela sera fait comme nécessaire dans l'application réelle une fois que j'aurai maîtrisé ces problèmes de conception) :

public abstract class Service
{
    protected Dictionary<string, int> _hours;
    protected const int SMALL = 2;
    protected const int MEDIUM = 8;

    public int NumberOfProducts { get; set; }
    public abstract int GetHours();
}

public class Writing : Service
{
    public Writing(int numberOfProducts)
    {
        NumberOfProducts = numberOfProducts;
        _hours = new Dictionary<string, int> { { "small", 125 }, { "medium", 100 }, { "large", 60 } };
    }

    public override int GetHours()
    {
        if (NumberOfProducts <= SMALL)
            return _hours["small"] * NumberOfProducts;
        if (NumberOfProducts <= MEDIUM)
            return (_hours["small"] * SMALL) + (_hours["medium"] * (NumberOfProducts - SMALL));
        return (_hours["small"] * SMALL) + (_hours["medium"] * (MEDIUM - SMALL))
            + (_hours["large"] * (NumberOfProducts - MEDIUM));
    }
}

public class Analysis : Service
{
    public Analysis(int numberOfProducts)
    {
        NumberOfProducts = numberOfProducts;
        _hours = new Dictionary<string, int> { { "small", 56 }, { "medium", 104 }, { "large", 200 } };
    }

    public override int GetHours()
    {
        if (NumberOfProducts <= SMALL)
            return _hours["small"];
        if (NumberOfProducts <= MEDIUM)
            return _hours["medium"];
        return _hours["large"];
    }
}

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        List<int> quantities = new List<int>();

        for (int i = 0; i < 100; i++)
        {
            quantities.Add(i);
        }
        comboBoxNumberOfProducts.DataSource = quantities;
    }

    private void comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e)
    {
        Service writing = new Writing((int) comboBoxNumberOfProducts.SelectedItem);
        Service analysis = new Analysis((int) comboBoxNumberOfProducts.SelectedItem);

        labelWriterHours.Text = writing.GetHours().ToString();
        labelAnalysisHours.Text = analysis.GetHours().ToString();
    }
}

2voto

CyberClaw Points 433

Dans votre calcul, il existe un lien étroit entre le type de service, la taille du service et le nombre de produits, et il est très difficile, en l'état, de les séparer en morceaux modulaires pour appliquer le modèle de stratégie.

Si le système de calcul est fixe, il semble que le modèle de stratégie ne soit pas approprié. Si ce n'est pas le cas... Pourquoi ne pas simplifier le système ?

Par exemple, tirez le nombre d'heures de base de la taille du service et appliquez diverses réductions ou augmentations en fonction de vos autres paramètres.

public class Service
{
    public IServiceSize serviceSize { internal get; set; }
    public IServiceBulkRate serviceBulkRate { internal get; set; }
    public IServiceType serviceType { internal get; set; }
    public int numberOfProducts { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="Service"/> class with default values
    /// </summary>
    public Service()
    {
        serviceSize = new SmallSize();
        serviceBulkRate = new FlatBulkRate();
        serviceType = new WritingService();
        numberOfProducts = 1;
    }

    public decimal CalculateHours()
    {
        decimal hours = serviceSize.GetBaseHours();
        hours = hours * serviceBulkRate.GetMultiplier(numberOfProducts);
        hours = hours * serviceType.GetMultiplier();

        return hours;
    }
}

public interface IServiceSize
{
    int GetBaseHours();
}

public class SmallSize : IServiceSize
{
    public int GetBaseHours()
    {
        return 125;
    }
}

public interface IServiceBulkRate
{
    decimal GetMultiplier(int numberOfProducts);
}

public class FlatBulkRate : IServiceBulkRate
{
    public decimal GetMultiplier(int numberOfProducts)
    {
        return numberOfProducts;
    }
}

public class StaggeredBulkRate : IServiceBulkRate
{
    public decimal GetMultiplier(int numberOfProducts)
    {
        if (numberOfProducts < 2)
            return numberOfProducts;
        else if (numberOfProducts >= 2 & numberOfProducts < 8)
            return numberOfProducts * 0.85m;
        else
            return numberOfProducts * 0.8m;
    }
}

public interface IServiceType
{
    decimal GetMultiplier();
}

public class WritingService : IServiceType
{
    public decimal GetMultiplier()
    {
        return 1.15m;
    }
}

1voto

Lee Points 63849

Je déplacerais la logique de choix de la valeur à calculer dans la classe de base Service et déléguerais les calculs effectifs à chaque sous-classe :

public abstract class Service
{
    private readonly int numberOfProducts;
    private readonly IDictionary<string, int> hours;
    protected const int SMALL = 2; 
    protected const int MEDIUM = 8;

    public Service(int numberOfProducts, IDictionary<string, int> hours)
    {
        this.numberOfProducts = numberOfProducts;
        this.hours = hours;
    }

    public int GetHours()
    {
        if(this.numberOfProducts <= SMALL)
            return this.CalculateSmallHours(this.hours["small"], this.numberOfProducts);
        else if(this.numberOfProducts <= MEDIUM)
            return this.CalculateMediumHours(this.hours["medium"], this.numberOfProducts);
        else
            return this.CalculateLargeHours(this.hours["large"], this.numberOfProducts);
    }

    protected abstract int CalculateSmallHours(int hours, int numberOfProducts);
    protected abstract int CalculateMediumHours(int hours, int numberOfProducts);
    protected abstract int CalculateLargeHours(int hours, int numberOfProducts);
}

Ensuite, si un calcul est particulièrement compliqué, vous pouvez l'extraire dans un objet de stratégie et l'utiliser uniquement pour cette sous-classe spécifique.

EDIT : Si vous souhaitez prendre en charge un nombre arbitraire de calculs, vous pouvez créer une classe pour gérer les correspondances entre les heures, les "catégories" et un calcul pour chacune d'entre elles. Ensuite, chaque sous-classe (ou une usine) peut fournir les calculs pertinents pour chaque catégorie :

public class HoursCalculationStrategyCollection
{
    private readonly Dictionary<string, int> hours;

    private readonly Dictionary<string, Func<int, int, int>> strategies;

    public HoursCalculationStrategyCollection(IDictionary<string, int> hours)
    {
        this.hours = hours;
        this.strategies = new Dictionary<string, Func<int, int, int>();
    }

    public void AddCalculationStrategy(string hours, Func<int, int, int> strategy)
    {
        this.strategies[hours] = strategy;
    }

    public int CalculateHours(int numberOfProducts)
    {
        string hoursKey = null;

        if(numberOfProducts <= SMALL)
            hoursKey = small;
        else if(...)
            ...

        Func<int, int, int> strategy = this.strategies[hoursKey];
        return strategy(this.hours[hoursKey], numberOfProducts);
    }
}

0voto

Jørgen Fogh Points 3579

Il est possible de combiner le modèle de la fabrique et celui de la stratégie. Votre fabrique créerait alors un service concret et lui transmettrait une stratégie pour gérer les différentes tailles (petite, moyenne ou grande).

Vous obtiendrez ainsi 8 classes : Service, Analysis, Writing, MediumStrategy, SmallStrategy, LargeStrategy et ServiceFactory + l'interface pour les stratégies.

Le ServiceFactory contiendrait alors le code permettant de décider de la stratégie à utiliser. Quelque chose comme :

Analysis createAnalysis(int numberOfProducts) {
    SizeStrategy strategy;
    if (numberOfProducts <= SMALL) {
        strategy = new SmallStrategy();
    } else if (numberOfProducts <= MEDIUM) {
        strategy = new MediumStrategy();
    } else {
        strategy = new LargeStrategy();
    }
    return new Analysis(numberOfProducts, strategy);
}

Dans ce cas, vous n'économisez que très peu de code. En tant qu'exercice, cela n'a pas d'importance bien sûr, mais je ne pense pas que je perdrais mon temps à refaire ce code dans la pratique.

EDIT : En y réfléchissant bien, et en supposant que les règles sont susceptibles de changer, il me semble qu'un table de contrôle est probablement plus approprié que les modèles OOP.

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