2010-05-07 16 views
9

He tratado de hacer una variante de esta pregunta antes. Obtuve algunas respuestas útiles, pero todavía nada que me pareciera correcto. Me parece que esto no debería ser tan difícil de romper, pero no puedo encontrar una solución elegante y simple. (Aquí está mi publicación anterior, pero por favor trate de ver el problema indicado aquí como código de procedimiento primero para no ser influenciado por la explicación anterior que parecía conducir a soluciones muy complicadas: Design pattern for cost calculator app?)¿Reemplazar condicionalmente la refactorización de polimorfismo o similar?

Básicamente, el problema es para crear una calculadora de horas necesarias para proyectos que pueden contener una serie de servicios. En este caso, "escritura" y "análisis". Las horas se calculan de manera diferente para los diferentes servicios: la escritura se calcula multiplicando una tarifa por hora "por producto" con la cantidad de productos, y cuantos más productos se incluyen en el proyecto, menor es la tasa horaria, pero el número total de las horas se acumulan progresivamente (es decir, para un proyecto de tamaño mediano se toma el precio de rango pequeño y luego se agrega el precio de rango medio hasta el número de productos reales). Mientras que para el análisis es mucho más simple, es solo una tasa masiva para cada rango de tamaño.

¿Cómo podría refactorizar esto en una versión elegante y preferentemente simple orientada a objetos (tenga en cuenta que nunca lo escribiría así de una manera puramente de procedimiento, esto es solo para mostrar el problema de otra manera sucintamente)

He estado pensando en términos de patrones de fábrica, estrategia y decorador, pero ninguno puede funcionar bien. (Leí Patrones de Diseño de Head First hace un tiempo, y tanto el decorador como los patrones de fábrica descritos tienen algunas similitudes con este problema, pero tengo problemas para verlos como buenas soluciones, como se indica allí. El ejemplo del decorador parecía muy complicado para agregar condimentos , pero tal vez podría funcionar mejor aquí, no sé. Al menos el hecho de que el cálculo de las horas se acumula progresivamente me hizo pensar en el patrón decorador ... Y en el ejemplo del patrón de fábrica del libro de la fábrica de pizzas ... .well parece crear una explosión de clases tan ridícula, al menos en su ejemplo. He encontrado un buen uso para los patrones de fábrica antes, pero no puedo ver cómo podría usarlo aquí sin tener un conjunto realmente complicado de clases)

El objetivo principal sería tener que cambiar solo en un lugar (acoplamiento flojo, etc.) si tuviera que agregar un nuevo parámetro r (diga otro tamaño, como XSMALL y/u otro servicio, como "Administración"). Aquí está el ejemplo de código de procedimiento:

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

Todas las respuestas son apreciadas! (Pero como dije en mis publicaciones anteriores, apreciaría los ejemplos de código reales en lugar de simplemente "Pruebe este patrón", porque como mencioné, eso es lo que estoy teniendo problemas ...) Espero que alguien tenga una solución realmente elegante a este problema que de hecho pensé desde el principio sería muy simple ...

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

nueva adición:

agradezco todas las respuestas hasta ahora, pero estoy Todavía no veo una solución realmente simple y flexible al problema (una que pensé que no sería muy compleja al principio, pero aparentemente lo es). También es posible que todavía no haya entendido bien cada respuesta correctamente. Pero pensé en publicar mi intento actual de resolverlo (con la ayuda de leer todos los diferentes ángulos en las respuestas aquí). Por favor, dime si estoy en el camino correcto o no. Pero al menos ahora parece que se está volviendo más flexible ... Puedo fácilmente agregar nuevos parámetros sin tener que cambiar en muchos lugares (¡creo!), Y la lógica condicional está en un solo lugar. Tengo algo de esto en xml para obtener los datos básicos, lo que simplifica parte del problema, y ​​parte de esto es un intento de una solución de tipo estrategia.

Aquí está el código:

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

Y una forma simple ejemplo que llama (supongo que también podría tener una clase de proyectos envoltorio con un diccionario que contiene los objetos de servicio, pero no han llegado a ese):

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

} 

(yo no era capaz de incluir el código XML porque se hacía formatea automáticamente en esta página, pero es básicamente sólo un montón de elementos con cada tipo de servicio, y cada tipo de servicio que contiene los tamaños con el tarifas horarias como valores)

No estoy seguro de si solo estoy enviando el problema al archivo xml (aún tendría que agregar nuevos elementos para cada nuevo tipo de servicio, y agregar elementos para cualquier tamaño nuevo en cada tipo de servicio si eso cambiaba .) Pero tal vez es imposible lograr lo que estoy tratando de hacer y no tener que hacer al menos ese tipo de cambio. El uso de una base de datos en lugar de xml el cambio sería tan simple como añadir un campo y una fila:

ServiceType Pequeño Medio Grande

escritura 125 100 60

Análisis 56 104 200

(Simplemente formateado como una "tabla" aquí, aunque las columnas no están del todo alineadas ... No soy el mejor en diseño de bases de datos, y tal vez debería hacerse de otra manera, pero se entiende ...)

Por favor, dime lo que piensas!

+1

Es un cambio pequeño, y realmente no responde a ninguna de su pregunta patrón más amplio, pero se puede, en el caso de la "escritura", de forma recursiva invocar la función: 'getHours de retorno (pequeño) + 20 * _numberOfManuals - SMALL); 'Esto lo ayuda cuando desea agregar XSMALL, y podría decirse que es más limpio. –

+1

+1 Me encanta cuando a las personas les apasiona escribir código limpio –

Respuesta

5

Tendería a comenzar con una enumeración ProjectSize {Small, Medium, Large} y una función simple para devolver la enumeración apropiada dado un número de Manuals. Desde allí, escribiría ServiceHourCalculators, WritingServiceHourCalculator y AnalysisServiceHourCalculator (porque su lógica es lo suficientemente diferente). Cada uno tomaría un NumberOfManuals, un ProjectSize, y devolvería el número de horas. Me gustaría probablemente crear un mapa de cadena a ServiceHourCalculator, por lo que podría decir:

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

De esta manera, cuando he añadido un nuevo tamaño del proyecto, el compilador podría frustrar en algunos casos no controladas para cada servicio. No todo se maneja en un solo lugar, pero todo se maneja antes de que vuelva a compilarse, y eso es todo lo que necesito.

actualización Sé Java, no en C# (muy bien), por lo que este puede no ser 100% correcta, pero la creación del mapa sería algo como esto:

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

+1 Me gusta esto: pasos incrementales hacia una solución modificada en lugar de hornear zapatos en un patrón –

+0

Gracias, sí, la enumeración se usa en la aplicación real (como Mencioné que este es un ejemplo de procedimiento artificial, y nada como el código que he intentado hasta ahora, porque publicarlo me acaba de dar respuestas demasiado complicadas).Pero básicamente estás diciendo que no intentarías obtener el condicional en un solo lugar, y que serías feliz siempre que el compilador lo obtenga. Puede que te haya malinterpretado, pero esperaba llegar más lejos hacia el objetivo de tener esa lógica en un solo lugar ... No entiendo el punto del mapa de servicio, ¿podrías explicarlo? – Anders

+0

Estoy * cómodo * con la enumeración. Puede que no sea el mejor diseño final, pero es sencillo y si no aparece en muchos lugares, estoy bien dejándolo así. En cualquier caso, es una buena situación interina, y una vez que las cosas están en esa forma, puede ser más fácil ver refactorizaciones posteriores. El mapa de servicio es solo una manera de decir, dada una cadena ("escritura" o "análisis"), devuélvame la calculadora adecuada. Probablemente debería haberlo llamado "calculatorMap". :-) –

2

Un buen comienzo sería para extraer el enunciado condicional en un método (aunque solo sea un método pequeño) y darle un nombre realmente explícito. Luego extraiga la lógica dentro de la declaración if en sus propios métodos, de nuevo con nombres realmente explícitos. (No se preocupe si los nombres de los métodos son largos, siempre que hagan lo que se les llama)

Lo escribiría en el código, pero sería mejor que eligiera nombres.

Me gustaría pasar a métodos y patrones de refactorización más complicados. Solo cuando veas una serie de llamadas a métodos parecerá apropiado comenzar a aplicar patrones, etc.

Haga su primer objetivo para escribir código limpio, fácil de leer y comprender. Es fácil entusiasmarse con los patrones (hablando por experiencia) pero son muy difíciles de aplicar si no puede describir su código existente en abstracciones.

EDIT: Así que para aclarar - usted debe tratar de obtener su sentencia if con este aspecto

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

Una vez hecho esto, en mi opinión, entonces es más fácil de aplicar algunos de los patrones mencionados aquí . Pero una vez que todavía tiene cálculos, etc. en su declaración if, entonces es más difícil ver la madera de los árboles, ya que se encuentra en una abstracción demasiado baja.

+0

Ok, Creo que sé más o menos a qué te refieres, pero ¿podrías dar un pequeño ejemplo de código para no haber entendido mal? – Anders

+0

Ok, gracias por la aclaración. Sin embargo, una vez más, como mencioné, el ejemplo se ideó por razones de claridad, explicándolo de forma procedimental en el que nunca lo escribiría, solo para obtener una visión objetiva (sin intención) de soluciones orientadas a objetos. – Anders

1

este es un problema común, hay algunas opciones que se me ocurren. Hay dos patrones de diseño que vienen a la mente, primero el Strategy Pattern y el segundo el Factory Pattern. Con el patrón de estrategia, es posible encapsular el cálculo en un objeto, por ejemplo, podría encapsular su método GetHours en clases individuales, cada una representaría un cálculo basado en el tamaño. Una vez que hemos definido las diferentes estrategias de cálculo, las envolvemos en una fábrica. La fábrica sería responsable de seleccionar la estrategia para realizar el cálculo al igual que su declaración if en el método GetHours. De todos modos, eche un vistazo al siguiente código y vea lo que piensa

En cualquier punto podría crear una nueva estrategia para realizar un cálculo diferente. La estrategia se puede compartir entre diferentes objetos permitiendo que el mismo cálculo se use en múltiples lugares. Además, la fábrica podría determinar dinámicamente qué estrategia usar en función de la configuración, por ejemplo

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

Gracias, de nuevo, creo que añadir más servicios todavía podría ser un problema aquí, porque tendrías que agregar lógica para los nuevos servicios en cada una de las estrategias de tamaño. Y como yo lo veo, todavía hay muchos condicionales aquí ... todos siguiendo los mismos parámetros, sin lograr lo que quería al leer originalmente Fowler's Replace condicional con refactorización de polimorfismos ... Además, ¿no deberían ser las estrategias? parte de otra clase que los usa (¿pero quizás acabas de dejar esa parte)? – Anders

1

Iría con una derivada de estrategia. Esto agrega clases adicionales, pero es más fácil de mantener a largo plazo. Además, tenga en cuenta que todavía hay opporunities para refactorización aquí:

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, gracias, esto es más en la línea que he estado pensando, pero creo que todavía tiene el problema de agregar nuevos parámetros ... Pude haber explicado mis objetivos mal, pero lo que quiero decir es que seguro podría ser es fácil agregar más clases de tamaño, pero si agrega otro servicio, ¿no sería peor? ¿Tener que agregar código para el nuevo servicio en cada una de las clases de tamaño ...? – Anders

2

Usted no necesita la fábrica si sus subclases filtran a sí mismos en lo que quieren cobrar por. Eso requiere una clase de proyectos para contener los datos, si no otra cosa:

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

Desde desea agregar nuevos cálculos fácilmente, necesita una interfaz:

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

Y, algunas clases para implementar la interfaz :

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

el código de llamada o bien puede cargar dinámicamente IProjectHours ejecutores (o estática ellos) y luego simplemente caminar por la lista de Project s a través de 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

Gracias, aunque no puedo decir que entiendo muy bien tu ejemplo, me temo. No conozco a Lambda y Linq lo suficiente como para interpretar exactamente lo que está sucediendo. Sin embargo, me da ganas de analizarlo más detenidamente, y entonces quizás pueda decir si esto funcionaría bien o no ... :-) (Su bienvenida para explicarlo un poco más si tiene el tiempo) también lo haré mira en el patrón de visitante, que no estaba cubierto en el libro de HF. – Anders

+0

@Anders - Ahora que lo pienso, este no es realmente un patrón de visitante en absoluto. Oh bien. Las declaraciones LINQ son solo mi intento de hacer que tu lógica sea un poco más fácil de leer. Lo importante es que los condicionales se reemplazan con 'IProjectHours' que saben para qué tipo de' Proyecto' son apropiados. Eso mantiene el filtro (condicional) y la acción (el número de horas) en el mismo lugar. –

Cuestiones relacionadas