Ho provato a chiedere una variante di questa domanda prima. Ho avuto alcune risposte utili, ma ancora niente che mi sembrasse giusto. Mi sembra che questo non dovrebbe essere così difficile da decifrare, ma non sono in grado di trovare una soluzione semplice ed elegante. (Qui è il mio post precedente, ma per favore prova a guardare il problema qui indicato come codice procedurale per non essere influenzato dalla spiegazione precedente che sembrava portare a soluzioni molto complicate: Design pattern for cost calculator app?)Sostituire il condizionale con il refactoring del polimorfismo o simile?
Fondamentalmente, il problema è creare una calcolatrice per le ore necessarie per progetti che possono contenere un numero di servizi. In questo caso "scrittura" e "analisi". Le ore sono calcolate in modo diverso per i diversi servizi: la scrittura viene calcolata moltiplicando una tariffa oraria "per prodotto" con il numero di prodotti, e più prodotti sono inclusi nel progetto, più bassa è la tariffa oraria, ma il numero totale di le ore vengono accumulate progressivamente (ad esempio, per un progetto di medie dimensioni si prendono entrambi i prezzi della gamma piccola e quindi si aggiungono i prezzi della gamma media fino al numero di prodotti effettivi). Considerando che per l'analisi è molto più semplice, si tratta solo di una maggiorazione per ogni gamma di dimensioni.
Come si sarebbe in grado di refactoring in una versione elegante e preferibilmente semplice orientata agli oggetti (si noti che non lo scriverei mai in questo modo in modo puramente procedurale, questo è solo per mostrare il problema in un altro modo in modo succinto).
Ho pensato in termini di modelli di fabbrica, strategia e decoratore, ma non riesco a far funzionare bene nessuno. (Ho letto Head First Design Patterns qualche tempo fa, e sia i decoratori che i modelli di fabbrica descritti hanno alcune somiglianze con questo problema, ma ho difficoltà a vederli come buone soluzioni come indicato qui. L'esempio di decoratore sembrava molto complicato lì solo per aggiungere condimenti ma forse potrebbe funzionare meglio qui, non lo so, almeno il fatto che il calcolo delle ore si accumula progressivamente mi ha fatto pensare al motivo del decoratore ... E l'esempio del modello di fabbrica del libro con la fabbrica di pizze ... .bene sembra proprio creare un'esplosione ridicola di classi, almeno nel loro esempio: ho trovato un buon uso per i modelli di fabbrica, ma non riesco a vedere come potrei usarlo qui senza ottenere un insieme di classi davvero complicato)
L'obiettivo principale sarebbe quello di dover cambiare solo in un posto (accoppiamento libero ecc.) Se dovessi aggiungere un nuovo paramete r (dì un'altra dimensione, come XSMALL, e/o un altro servizio, come "Amministrazione"). Ecco l'esempio di codice procedurale:
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
}
}
Tutte le risposte sono apprezzate! (Ma come ho dichiarato nei miei post precedenti apprezzerei esempi di codice reali piuttosto che "Try this pattern", perché come ho detto, è quello che sto avendo problemi con ...) Spero che qualcuno abbia una soluzione davvero elegante a questo problema che ho pensato fin dall'inizio sarebbe stato molto semplice ...
======================== ===========================
nuova aggiunta:
apprezzo tutte le risposte finora, ma sono ancora non vedendo una soluzione veramente semplice e flessibile al problema (una cosa che pensavo non sarebbe stata molto complessa all'inizio, ma apparentemente lo è). Può anche darsi che non abbia ancora capito bene ogni risposta correttamente. Ma ho pensato di pubblicare il mio attuale tentativo di elaborarlo (con l'aiuto di leggere tutti i diversi punti di vista nelle risposte qui). Per favore dimmi se sono sulla strada giusta o no. Ma almeno ora sembra che stia iniziando a diventare più flessibile ... Posso facilmente aggiungere nuovi parametri senza dover cambiare in molti punti (credo!), E la logica condizionale è tutto in un unico posto. Ne ho alcuni in xml per ottenere i dati di base, il che semplifica parte del problema, e parte di esso è un tentativo di una soluzione di tipo strategico.
Ecco il codice:
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));
}
}
E una forma semplice esempio che chiama (Credo che avrei potuto anche avere una classe wrapper progetto con un dizionario contenente gli oggetti di servizio, ma non ho ottenuto a quella):
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();
}
}
(non ero in grado di includere il codice XML perché ha ottenuto automaticamente formattati in questa pagina, ma è fondamentalmente solo un mucchio di elementi con ogni tipo di servizio, e ogni tipo di servizio contenente le misure con il le ore come valori.)
io non sono sicuro se sto solo spingendo il problema verso il file xml (avrei dovuto ancora aggiungere nuovi elementi per ogni nuovo ServiceType, e aggiungere elementi per qualsiasi nuova dimensione in ogni ServiceType se questo è cambiato .) Ma forse è impossibile da raggiungere quello che sto cercando di fare e non dover fare almeno quel tipo di cambiamento. Utilizzando un database anziché XML cambiamento sarebbe sufficiente aggiungere un campo e una fila:
ServiceType Small Medium Large
scrittura 125 100 60
Analisi 56 104 200
(Semplicemente formattato come un "tavolo" qui, anche se le colonne non sono abbastanza allineati ... io non sono il migliore in progettazione di database, però, e forse dovrebbe essere fatto in modo diverso, ma si ottiene l'idea ...)
Per favore dimmi cosa ne pensi!
Si tratta di un piccolo cambiamento, e in realtà non rispondere alle vostre domande schema più ampio, ma si potrebbe, nel caso "scrittura", richiamare ricorsivamente la funzione: 'getHours di ritorno (small) + 20 * _numberOfManuals - SMALL): 'Questo ti aiuta quando vuoi aggiungere XSMALL e, discutibilmente, legge in modo più pulito. –
+1 Mi piace quando le persone sono appassionate di scrivere codice pulito –