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!
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 Me encanta cuando a las personas les apasiona escribir código limpio –