2010-05-07 5 views
9

J'ai déjà essayé de poser une variante de cette question. J'ai eu quelques réponses utiles, mais toujours rien qui me semblait tout à fait juste. Il me semble que cela ne devrait pas être si difficile à cerner, mais je ne suis pas capable de trouver une solution simple et élégante. (Voici mon précédent article, mais s'il vous plaît, essayez d'abord de considérer le problème comme étant le code de procédure afin de ne pas être influencé par l'explication précédente qui semblait conduire à des solutions très compliquées: Design pattern for cost calculator app?)Remplacer conditionnel avec refactorisation de polymorphisme ou similaire?

Fondamentalement, le problème est pour créer une calculatrice pour les heures nécessaires pour les projets qui peuvent contenir un certain nombre de services. Dans ce cas "écriture" et "analyse". Les heures sont calculées différemment pour les différents services: l'écriture est calculée en multipliant un taux horaire «par produit» par le nombre de produits, et plus les produits sont inclus dans le projet, plus le taux horaire est bas, mais le nombre total de les heures sont accumulées progressivement (c.-à-d. pour un projet de taille moyenne, vous prenez à la fois la petite échelle de prix, puis vous ajoutez le prix moyen de gamme au nombre de produits réels). Alors que pour l'analyse, c'est beaucoup plus simple, c'est juste un taux global pour chaque gamme de taille. Comment pourriez-vous refactoriser cela en une version élégante et de préférence simple orientée objet (s'il vous plaît noter que je ne l'écrirais jamais comme cela d'une manière purement procédurale, c'est juste pour montrer le problème d'une autre manière succinctement). J'ai pensé en termes de modèles d'usine, de stratégie et de décorateur, mais je n'arrive pas à bien les faire fonctionner. (J'ai lu Head First Design Patterns il y a quelque temps, et les motifs de décorateur et d'usine décrits ont quelques similitudes avec ce problème, mais j'ai du mal à les voir comme de bonnes solutions. , mais peut-être que ça pourrait mieux fonctionner ici, je ne sais pas ... Au moins le fait que le calcul des heures s'accumule progressivement m'a fait penser au motif décorateur ... Et l'exemple de modèle d'usine du livre à la pizzeria. Il semble juste que cela crée une explosion de classes aussi ridicule, du moins dans leur exemple: j'ai déjà trouvé bon usage des modèles d'usine, mais je ne vois pas comment je pourrais l'utiliser ici sans avoir un ensemble de cours vraiment compliqués

Le but principal serait de n'avoir à changer qu'à un seul endroit (couplage lâche, etc.) si je devais ajouter un nouveau paramètre r (disons une autre taille, comme XSMALL, et/ou un autre service, comme "Administration"). Voici l'exemple de code procédural:

public class Conditional 
{ 
    private int _numberOfManuals; 
    private string _serviceType; 
    private const int SMALL = 2; 
    private const int MEDIUM = 8; 

    public int GetHours() 
    { 
     if (_numberOfManuals <= SMALL) 
     { 
      if (_serviceType == "writing") 
       return 30 * _numberOfManuals; 
      if (_serviceType == "analysis") 
       return 10; 
     } 
     else if (_numberOfManuals <= MEDIUM) 
     { 
      if (_serviceType == "writing") 
       return (SMALL * 30) + (20 * _numberOfManuals - SMALL); 
      if (_serviceType == "analysis") 
       return 20; 
     } 
     else //i.e. LARGE 
     { 
      if (_serviceType == "writing") 
       return (SMALL * 30) + (20 * (MEDIUM - SMALL)) + (10 * _numberOfManuals - MEDIUM); 
      if (_serviceType == "analysis") 
       return 30; 
     } 
     return 0; //Just a default fallback for this contrived example 
    } 
} 

Toutes les réponses sont appréciées! (Mais comme je l'ai dit dans mes précédents articles, j'apprécierais les exemples de code plutôt que "Essayez ce modèle", car comme je l'ai mentionné, c'est ce qui me dérange ...) J'espère que quelqu'un a une solution vraiment élégante à ce problème que je pensais en fait depuis le début serait très simple ...

============================= ===========================

NOUVEAU AJOUT:

Je vous remercie toutes les réponses à ce jour, mais je suis Je ne vois toujours pas de solution vraiment simple et flexible au problème (celui que je pensais ne pas être très complexe au début, mais qui l'est apparemment). Il se peut aussi que je n'ai pas encore bien compris chaque réponse correctement. Mais je pensais que je posterais ma tentative actuelle de l'élaborer (avec de l'aide en lisant tous les différents angles de réponses ici). S'il vous plaît dites-moi si je suis sur la bonne voie ou non. Mais au moins maintenant, il semble que ça commence à devenir plus flexible ... Je peux facilement ajouter de nouveaux paramètres sans avoir à changer dans beaucoup d'endroits (je pense!), Et la logique conditionnelle est au même endroit. J'en ai une partie en XML pour obtenir les données de base, ce qui simplifie une partie du problème, et une partie est une tentative de solution de type stratégie.

Voici le code:

public class Service 
{ 
    protected HourCalculatingStrategy _calculatingStrategy; 
    public int NumberOfProducts { get; set; } 
    public const int SMALL = 3; 
    public const int MEDIUM = 9; 
    public const int LARGE = 20; 
    protected string _serviceType; 
    protected Dictionary<string, decimal> _reuseLevels; 

    protected Service(int numberOfProducts) 
    { 
     NumberOfProducts = numberOfProducts; 
    } 

    public virtual decimal GetHours() 
    { 
     decimal hours = _calculatingStrategy.GetHours(NumberOfProducts, _serviceType); 
     return hours; 
    } 
} 

public class WritingService : Service 
{ 
    public WritingService(int numberOfProducts) 
     : base(numberOfProducts) 
    { 
     _calculatingStrategy = new VariableCalculatingStrategy(); 
     _serviceType = "writing"; 
    } 
} 

class AnalysisService : Service 
{ 
    public AnalysisService(int numberOfProducts) 
     : base(numberOfProducts) 
    { 
     _calculatingStrategy = new FixedCalculatingStrategy(); 
     _serviceType = "analysis"; 
    } 
} 

public abstract class HourCalculatingStrategy 
{ 
    public abstract int GetHours(int numberOfProducts, string serviceType); 

    protected int GetHourRate(string serviceType, Size size) 
    { 
     XmlDocument doc = new XmlDocument(); 
     doc.Load("calculatorData.xml"); 
     string result = doc.SelectSingleNode(string.Format("//*[@type='{0}']/{1}", serviceType, size)).InnerText; 
     return int.Parse(result); 
    } 
    protected Size GetSize(int index) 
    { 
     if (index < Service.SMALL) 
      return Size.small; 
     if (index < Service.MEDIUM) 
      return Size.medium; 
     if (index < Service.LARGE) 
      return Size.large; 
     return Size.xlarge; 
    } 
} 

public class VariableCalculatingStrategy : HourCalculatingStrategy 
{ 
    public override int GetHours(int numberOfProducts, string serviceType) 
    { 
     int hours = 0; 
     for (int i = 0; i < numberOfProducts; i++) 
     { 
      hours += GetHourRate(serviceType, GetSize(i + 1)); 
     } 
     return hours; 
    } 
} 

public class FixedCalculatingStrategy : HourCalculatingStrategy 
{ 
    public override int GetHours(int numberOfProducts, string serviceType) 
    { 
     return GetHourRate(serviceType, GetSize(numberOfProducts)); 
    } 
} 

Et une forme simple exemple qu'il appelle (je suppose que je pourrais aussi avoir une classe enveloppe de projet avec un dictionnaire contenant les objets de service, mais je n'ai pas eu à ce):

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 CreateProject() 
    { 
     int numberOfProducts = (int)comboBoxNumberOfProducts.SelectedItem; 
     Service writing = new WritingService(numberOfProducts); 
     Service analysis = new AnalysisService(numberOfProducts); 

     labelWriterHours.Text = writing.GetHours().ToString(); 
     labelAnalysisHours.Text = analysis.GetHours().ToString(); 
    } 
    private void comboBoxNumberOfProducts_SelectedIndexChanged(object sender, EventArgs e) 
    { 
     CreateProject(); 
    } 

} 

(je n'ai pas pu inclure le xml, car il se est automatiquement mis en forme sur cette page, mais il est fondamentalement juste un tas d'éléments avec chaque type de service, et chaque type de service contenant les tailles avec le taux horaires en tant que valeurs.)Je ne suis pas sûr si je pousse juste le problème sur le fichier xml (je devrais encore ajouter de nouveaux éléments pour chaque nouveau type de servicet, et ajouter des éléments pour toute nouvelle taille dans chaque type de service si cela a changé .) Mais peut-être qu'il est impossible de réaliser ce que j'essaie de faire et ne pas avoir à faire au moins ce type de changement. En utilisant une base de données plutôt que xml le changement serait aussi simple que l'ajout d'un champ et une rangée:

ServiceType Small Medium Large

Rédaction 125 100 60

analyse 56 104 200

(simplement formaté comme une "table" ici, bien que les colonnes ne soient pas alignées ... je ne suis pas le meilleur en conception de base de données, et peut-être que cela devrait être fait différemment, mais vous avez l'idée ...)

S'il vous plaît dites-moi ce que vous pensez!

+1

Il est un petit changement, et ne pas vraiment répondre à toutes vos dans le cas « écriture » question de modèle plus large, mais vous pouvez,, invoquer récursive la fonction: 'getHours de retour (petit) + 20 * _numberOfManuals - SMALL); 'Cela vous aide lorsque vous voulez ajouter XSMALL, et peut-être lire plus proprement. –

+1

+1 Je l'aime quand les gens sont passionnés par l'écriture de code propre –

Répondre

5

J'ai tendance à commencer par une énumération ProjectSize {Small, Medium, Large} et une fonction simple pour renvoyer l'énumération appropriée en donnant un numéro de manuel. De là, j'écrirais différents ServiceHourCalculators, le WritingServiceHourCalculator et le AnalysisServiceHourCalculator (parce que leur logique est suffisamment différente). Chacun prend un nombre de manuels, une taille de projet et renvoie le nombre d'heures. Je serais probablement créer une carte de chaîne à ServiceHourCalculator, pour que je puisse dire:

ProjectSize projectSize = getProjectSize(_numberOfManuals); 
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals); 

De cette façon, quand j'ai ajouté une nouvelle taille du projet, le compilateur rechignent à certains cas pour chaque service non gérées. Tout n'est pas géré au même endroit, mais tout est géré avant qu'il ne se compile à nouveau, et c'est tout ce dont j'ai besoin.

Mise à jour Je sais que Java, pas C# (très bien), donc ce ne peut pas être 100% à droite, mais la création de la carte serait quelque chose comme ceci:

Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>(); 
serviceMap.put("writing", new WritingServiceHourCalculator()); 
serviceMap.put("analysis", new AnalysisServiceHourCalculator()); 
+1

+1 J'aime ça - étapes progressives vers une solution refactorisée au lieu de sabotage dans un modèle –

+0

Merci, ouais, l'enum est utilisé dans l'application réelle (comme J'ai mentionné qu'il s'agit d'un exemple procédural artificiel, et rien de tel que le code que j'ai essayé jusqu'ici, parce que l'affichage qui m'a juste obtenu des réponses trop compliquées).Mais en gros, vous dites que vous ne vous efforceriez pas d'obtenir le conditionnel à un seul endroit, et vous seriez heureux aussi longtemps que le compilateur l'obtiendrait? J'ai peut-être mal compris, mais j'espérais aller plus loin dans le but d'avoir cette logique au même endroit ... Je n'ai pas compris le point de la carte de service, pourriez-vous élaborer? – Anders

+0

Je suis * confortable * avec l'énumération. Ce n'est peut-être pas le design final, le meilleur, mais c'est simple et s'il ne semble pas y avoir trop d'endroits, je suis prêt à le faire. En tout cas, c'est une bonne situation intermédiaire, et une fois que les choses sont dans cette forme, il peut être plus facile de voir les refactorings suivants. La carte de service est juste une façon de dire, donné une chaîne ("écriture" ou "analyse"), me rendre la calculatrice appropriée. J'aurais probablement dû l'appeler "calculatormap". :-) –

2

Un bon point de départ serait pour extraire l'instruction conditionnelle dans une méthode (bien que seulement une petite méthode) et lui donner un nom vraiment explicite. Puis extrayez la logique dans l'instruction if dans leurs propres méthodes - encore avec des noms vraiment explicites. (Ne vous inquiétez pas si les noms de méthode sont longues - aussi longtemps qu'ils font ce qu'ils sont appelés)

Je voudrais écrire ceci dans le code, mais ce serait mieux pour vous de choisir les noms.

Je voudrais ensuite passer à des méthodes et des modèles de refactoring plus complexes. Ce n'est que lorsque vous regardez une série d'appels de méthode qu'il vous semble approprié de commencer à appliquer des modèles, etc.Faites votre premier objectif d'écrire du code propre, facile à lire et à comprendre. Il est facile de s'enthousiasmer à propos des modèles (en parlant d'expérience) mais ils sont très difficiles à appliquer si vous ne pouvez pas décrire votre code existant dans des abstractions.

EDIT: Donc, pour clarifier - vous devriez viser à obtenir votre instruction if qui ressemble à ceci

if(isBox()) 
{ 
    doBoxAction(); 
} 
else if(isSquirrel()) 
{ 
    doSquirrelAction(); 
} 

Une fois que vous faites cela, à mon avis, il est plus facile d'appliquer certains des modèles mentionnés ici . Mais une fois que vous avez encore des calculs, etc ... dans votre instruction if, alors il est plus difficile de voir le bois des arbres car vous êtes trop bas d'une abstraction.

+0

Ok, Je pense que je sais en quelque sorte ce que vous voulez dire, mais pourriez-vous donner un petit exemple dans le code juste pour que je n'ai pas mal compris? – Anders

+0

Ok, merci pour la clarification. Encore une fois cependant, comme je l'ai mentionné, l'exemple est conçu pour des raisons de clarté - l'expliquer d'une manière procédurale dans laquelle je ne l'écrirais jamais, juste pour obtenir une vision objective (sans jeu de mots) des solutions orientées objet. – Anders

1

C'est un problème courant, il y a quelques options auxquelles je peux penser. Il y a deux modèles qui me viennent à l'esprit, d'abord le Strategy Pattern et ensuite le Factory Pattern. Avec le modèle de stratégie, il est possible d'encapsuler le calcul dans un objet, par exemple vous pouvez encapsuler votre méthode GetHours dans des classes individuelles, chacune représenterait un calcul basé sur la taille. Une fois que nous avons défini les différentes stratégies de calcul, nous les emballons ensuite dans une usine. L'usine serait responsable de la sélection de la stratégie pour effectuer le calcul, tout comme votre instruction if dans la méthode GetHours. De toute façon jeter un oeil au code ci-dessous et voir ce que vous en pensez

À tout moment, vous pouvez créer une nouvelle stratégie pour effectuer un calcul différent. La stratégie peut être partagée entre différents objets permettant d'utiliser le même calcul à plusieurs endroits. En outre l'usine pourrait dynamiquement élaborer la stratégie à utiliser en fonction de la configuration, par exemple

class Program 
{ 
    static void Main(string[] args) 
    { 
     var factory = new HourCalculationStrategyFactory(); 
     var strategy = factory.CreateStrategy(1, "writing"); 

     Console.WriteLine(strategy.Calculate()); 
    } 
} 

public class HourCalculationStrategy 
{ 
    public const int Small = 2; 
    public const int Medium = 8; 

    private readonly string _serviceType; 
    private readonly int _numberOfManuals; 

    public HourCalculationStrategy(int numberOfManuals, string serviceType) 
    { 
     _serviceType = serviceType; 
     _numberOfManuals = numberOfManuals; 
    } 

    public int Calculate() 
    { 
     return this.CalculateImplementation(_numberOfManuals, _serviceType); 
    } 

    protected virtual int CalculateImplementation(int numberOfManuals, string serviceType) 
    { 
     if (serviceType == "writing") 
      return (Small * 30) + (20 * (Medium - Small)) + (10 * numberOfManuals - Medium); 
     if (serviceType == "analysis") 
      return 30; 

     return 0; 
    } 
} 

public class SmallHourCalculationStrategy : HourCalculationStrategy 
{ 
    public SmallHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType) 
    { 
    } 

    protected override int CalculateImplementation(int numberOfManuals, string serviceType) 
    { 
     if (serviceType == "writing") 
      return 30 * numberOfManuals; 
     if (serviceType == "analysis") 
      return 10; 

     return 0; 
    } 
} 

public class MediumHourCalculationStrategy : HourCalculationStrategy 
{ 
    public MediumHourCalculationStrategy(int numberOfManuals, string serviceType) : base(numberOfManuals, serviceType) 
    { 
    } 

    protected override int CalculateImplementation(int numberOfManuals, string serviceType) 
    { 
     if (serviceType == "writing") 
      return (Small * 30) + (20 * numberOfManuals - Small); 
     if (serviceType == "analysis") 
      return 20; 

     return 0; 
    } 
} 

public class HourCalculationStrategyFactory 
{ 
    public HourCalculationStrategy CreateStrategy(int numberOfManuals, string serviceType) 
    { 
     if (numberOfManuals <= HourCalculationStrategy.Small) 
     { 
      return new SmallHourCalculationStrategy(numberOfManuals, serviceType); 
     } 

     if (numberOfManuals <= HourCalculationStrategy.Medium) 
     { 
      return new MediumHourCalculationStrategy(numberOfManuals, serviceType); 
     } 

     return new HourCalculationStrategy(numberOfManuals, serviceType); 
    } 
} 
+0

Merci, encore une fois, cependant, je pense que l'ajout de plus de services pourrait encore être un problème ici, car vous auriez à ajouter de la logique pour les nouveaux services dans chacune des stratégies de taille. Et comme je le vois il y a encore beaucoup de conditions ici ... toutes articulées sur les mêmes paramètres, n'obtenant ainsi pas ce que je voulais lire à l'origine en lisant le conditionnel de Fowler avec le refactorisation du polymorphisme ... Aussi, les stratégies ne devraient-elles pas être partie d'une autre classe qui les utilise (mais peut-être que vous venez de quitter cette partie)? – Anders

1

Je voudrais aller avec un dérivé de modèle de stratégie. Cela ajoute des classes supplémentaires, mais est plus maintenable sur le long terme. De plus, gardez à l'esprit qu'il ya encore opporunities pour refactorisation ici:

public class Conditional 
{ 
    private int _numberOfManuals; 
    private string _serviceType; 
    public const int SMALL = 2; 
    public const int MEDIUM = 8; 
    public int NumberOfManuals { get { return _numberOfManuals; } } 
    public string ServiceType { get { return _serviceType; } } 
    private Dictionary<int, IResult> resultStrategy; 

    public Conditional(int numberOfManuals, string serviceType) 
    { 
     _numberOfManuals = numberOfManuals; 
     _serviceType = serviceType; 
     resultStrategy = new Dictionary<int, IResult> 
     { 
       { SMALL, new SmallResult() }, 
       { MEDIUM, new MediumResult() }, 
       { MEDIUM + 1, new LargeResult() } 
     }; 
    } 

    public int GetHours() 
    { 
     return resultStrategy.Where(k => _numberOfManuals <= k.Key).First().Value.GetResult(this); 
    } 
} 

public interface IResult 
{ 
    int GetResult(Conditional conditional); 
} 

public class SmallResult : IResult 
{ 
    public int GetResult(Conditional conditional) 
    { 
     return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ; 
    } 

    private int WritingResult(Conditional conditional) 
    { 
     return 30 * conditional.NumberOfManuals; 
    } 

    private int AnalysisResult(Conditional conditional) 
    { 
     return 10; 
    } 
} 

public class MediumResult : IResult 
{ 
    public int GetResult(Conditional conditional) 
    { 
     return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ; 
    } 

    private int WritingResult(Conditional conditional) 
    { 
     return (Conditional.SMALL * 30) + (20 * conditional.NumberOfManuals - Conditional.SMALL); 

    } 

    private int AnalysisResult(Conditional conditional) 
    { 
     return 20; 
    } 
} 

public class LargeResult : IResult 
{ 
    public int GetResult(Conditional conditional) 
    { 
     return conditional.ServiceType.IsWriting() ? WritingResult(conditional) : AnalysisResult(conditional); ; 
    } 

    private int WritingResult(Conditional conditional) 
    { 
     return (Conditional.SMALL * 30) + (20 * (Conditional.MEDIUM - Conditional.SMALL)) + (10 * conditional.NumberOfManuals - Conditional.MEDIUM); 

    } 

    private int AnalysisResult(Conditional conditional) 
    { 
     return 30; 
    } 
} 

public static class ExtensionMethods 
{ 
    public static bool IsWriting(this string value) 
    { 
     return value == "writing"; 
    } 
} 
+0

ok, merci, c'est plus le long des lignes que je pensais moi-même, mais je crois qu'il a toujours le problème avec l'ajout de nouveaux paramètres ... J'ai peut-être mal expliqué mes objectifs, mais ce que je veux dire c'est que facile d'ajouter plus de classes de taille, mais si vous avez ajouté un autre service, cela ne serait-il pas pire? Devoir ajouter du code pour le nouveau service dans chacune des classes de taille ...? – Anders

2

Vous n'avez pas besoin l'usine si vos sous-classes se filtrent sur ce qu'ils veulent faire payer. Cela exige une classe de projet pour contenir les données, si rien d'autre:

class Project { 
    TaskType Type { get; set; } 
    int? NumberOfHours { get; set; } 
} 

Puisque vous voulez ajouter de nouveaux calculs facilement, vous avez besoin d'une interface:

IProjectHours { 
    public void SetHours(IEnumerable<Project> projects); 
} 

Et, quelques classes pour implémenter l'interface :

class AnalysisProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     projects.Where(p => p.Type == TaskType.Analysis) 
       .Each(p => p.NumberOfHours += 30); 
    } 
} 

// Non-LINQ equivalent 
class AnalysisProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     foreach (Project p in projects) { 
      if (p.Type == TaskType.Analysis) { 
      p.NumberOfHours += 30; 
      } 
     } 
    } 
} 

class WritingProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     projects.Where(p => p.Type == TaskType.Writing) 
       .Skip(0).Take(2).Each(p => p.NumberOfHours += 30); 
     projects.Where(p => p.Type == TaskType.Writing) 
       .Skip(2).Take(6).Each(p => p.NumberOfHours += 20); 
     projects.Where(p => p.Type == TaskType.Writing) 
       .Skip(8).Each(p => p.NumberOfHours += 10); 
    } 
} 

// Non-LINQ equivalent 
class WritingProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     int writingProjectsCount = 0; 
     foreach (Project p in projects) { 
      if (p.Type != TaskType.Writing) { 
      continue; 
      } 
      writingProjectsCount++; 
      switch (writingProjectsCount) { 
       case 1: case 2: 
       p.NumberOfHours += 30; 
       break; 
       case 3: case 4: case 5: case 6: case 7: case 8: 
       p.NumberOfHours += 20; 
       break; 
       default: 
       p.NumberOfHours += 10; 
       break; 
      } 
     } 
    } 
} 

class NewProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     projects.Where(p => p.Id == null).Each(p => p.NumberOfHours += 5); 
    } 
} 

// Non-LINQ equivalent 
class NewProjectHours : IProjectHours { 
    public void SetHours(IEnumerable<Project> projects) { 
     foreach (Project p in projects) { 
      if (p.Id == null) { 
      // Add 5 additional hours to each new project 
      p.NumberOfHours += 5; 
      } 
     } 
    } 
}  

le code d'appel peut soit charger dynamiquement IProjectHours implementors (ou les statiques) et puis juste marcher la liste des Project s par la m:

foreach (var h in AssemblyHelper.GetImplementors<IProjectHours>()) { 
    h.SetHours(projects); 
} 
Console.WriteLine(projects.Sum(p => p.NumberOfHours)); 
// Non-LINQ equivalent 
int totalNumberHours = 0; 
foreach (Project p in projects) { 
    totalNumberOfHours += p.NumberOfHours; 
} 
Console.WriteLine(totalNumberOfHours); 
+0

Merci, même si je ne peux pas dire que je comprends très bien votre exemple, j'ai peur. Je ne connais pas suffisamment Lambda et Linq pour interpréter exactement ce qui se passe. Cela me donne envie d'aller plus loin, et peut-être que je peux dire si cela marchera bien ou pas ... :-) (Votre bienvenue pour l'expliquer un peu plus si vous avez le temps) Je vais aussi regarder dans le modèle de visiteur, qui n'a pas été couvert dans le livre HF. – Anders

+0

@Anders - Maintenant que j'y pense, ce n'est pas du tout un modèle de Visiteur. Tant pis. Les instructions LINQ sont juste ma tentative de rendre votre logique un peu plus facile à lire. Le bit important est que les conditions sont remplacées par 'IProjectHours' qui sait quel type de' Project' ils sont appropriés. Cela maintient le filtre (conditionnel) et l'action (le nombre d'heures) au même endroit. –

Questions connexes