Conditional Complexity: Replace Conditional Logic with Strategy

 

Continuiamo con lo smell: Conditional Complexity

400px-Control_flow_graph_of_function_with_two_if_else_statements.svg

Problema:

Esiste un metodo di controllo con una logica condizionale composta da diverse varianti i quali valori si conosceranno solamente a runtime.
La stessa logica di controllo viene riprodotta in varie classi affinchè si ottenga il valore desiderato.

Un esempio di logica errata:

Di seguito un esempio di logica errata, sul quale lavoreremo:

public double Capital()
{
if (expiry == null && maturity != null)
return commitment * Duration() * RiskFactor();
if (expiry != null && maturity == null)
{
if (GetUnusedPercentage() != 1.0)
return commitment * GetUnusedPercentage() * Duration() *
RiskFactor();
else
return
(OutStandingRiskAmount() * Duration() *
RiskFactor()) +
(UnUsedRiskAmount() * Duration() * UnUsedRiskFactor());
}

return 0.0;
}

 

Il codice di sopra è raffigurabile nella seguente maniera:

 conditional_complexity

Motivazione:

Nel momento in cui iniziamo a scrivere delle logiche di controllo, solitamente, si parte da un paio di controlli che, se li volessimo togliere, porterebbero solamente a complicare la leggibilità del codice stesso.
Il problema nasce quando i controlli da effettuare continuano a crescere, senza preoccuparsi di migliorare la leggibilità del codice tramite l’applicazione di un Pattern ne, tanto meno, tentando di rimpicciolire le condizioni.

Soluzione:

Tramite il pattern Strategy semplificheremo il mantenimento delle varianti che potranno nascere.
In pratica, utilizzandolo, avremo tutte le varianti in un’unico punto del codice.
Un’altra soluzione applicabile è tramite il pattern Decorator.

Le condizioni vengono solitamente scritte per capire quale algoritmo utilizzare; tramite:

  • Decompose Conditional
  • Compose Method

possiamo semplificarne la leggibilità ma avremo tanti piccoli metodi che verranno utilizzati dall’algoritmo.

Questo è il codice che otteniamo applicando il pattern Strategy:

class Program
{
static void Main(string[] args)
{
ILoan loan1 = new StandardLoan();
loan1.Capital();
}
}

public class CapitalStrategy
{
private readonly ILoan _loan;
private object RiskFactor;
private object UnusedRiskFactor;

public CapitalStrategy(ILoan loan, object riskFactor, object unusedRiskFactor)
{
_loan = loan;
RiskFactor = riskFactor;
UnusedRiskFactor = unusedRiskFactor;
}

public double Capital()
{
switch (_loan.LoanType)
{
case LoanType.Standard:
return _loan.Commitment * _loan.Duration() * RiskFactorFor();
case LoanType.NonStandardUnUsedPercentage:
return _loan.Commitment * _loan.UnusedPercentage() * _loan.Duration() * RiskFactorFor();
case LoanType.NonStandardUnUsedPercentageDotOne:
return (_loan.OutstandingRiskAmount() * _loan.Duration() * RiskFactorFor())
+ (_loan.UnusedRiskAmount() * _loan.Duration() * UnusedRiskFactorFor());
default:
return 0.0;
}
}

private double UnusedRiskFactorFor()
{
return UnusedRiskFactor.GetFactor(_load).GetUnusedRiskFactor();
}

private double RiskFactorFor()
{
return RiskFactor.GetFactor(_load).GetRiskFactor();
}
}

public enum LoanType
{
Standard,
NonStandardUnUsedPercentage,
NonStandardUnUsedPercentageDotOne
}

public interface ILoan
{
double Capital();
LoanType LoanType { get; }
double Commitment { get; }
double Duration();
double UnusedPercentage();
double OutstandingRiskAmount();
double UnusedRiskAmount();
}

public class StandardLoan : ILoan
{
readonly CapitalStrategy _capitalStrategy;
private object myRiskFactor = new object();
private object myUnusedRiskFactor = new object();

public StandardLoan()
{
_capitalStrategy = new CapitalStrategy(this, myRiskFactor, myUnusedRiskFactor);
}

#region ILoan Members

public double Capital()
{
return _capitalStrategy.Capital();
}

public LoanType LoanType
{
get { return LoanType.Standard; }
}

public double Commitment
{
get { throw new NotImplementedException(); }
}

public double Duration()
{
throw new NotImplementedException();
}

public double UnusedPercentage()
{
throw new NotImplementedException();
}

public double OutstandingRiskAmount()
{
throw new NotImplementedException();
}

public double UnusedRiskAmount()
{
throw new NotImplementedException();
}

#endregion
}

public class NonStandardUnUsedPercentage : ILoan
{
readonly CapitalStrategy _capitalStrategy;
private object myRiskFactor = new object();
private object myUnusedRiskFactor = new object();

public NonStandardUnUsedPercentage()
{
_capitalStrategy = new CapitalStrategy(this, myRiskFactor, myUnusedRiskFactor);
}

#region ILoan Members

public double Capital()
{
return _capitalStrategy.Capital();
}

public LoanType LoanType
{
get { return LoanType.NonStandardUnUsedPercentage; }
}

public double Commitment { get; private set; }
public double Duration()
{
throw new System.NotImplementedException();
}

public double UnusedPercentage()
{
throw new System.NotImplementedException();
}

public double OutstandingRiskAmount()
{
throw new System.NotImplementedException();
}

public double UnusedRiskAmount()
{
throw new System.NotImplementedException();
}

#endregion
}

 

 refactored_conditional_complexity

 

Benefici e non
+ Rende gli algoritmi più chiari diminuendo o eliminando le condizioni logiche.
+ Semplifica le classi muovendo le variazioni dell’algoritmo nella gerarchia.
+ Abilita la possibilità di swappare tra un algoritmo e un’altro a run-time.

- Complica il design.
- Complica la visione di come l’algoritmo ottiene/riceve i dati.

E non prendete come scusa: “il mio sistema ormai è troppo evoluto per poterne apportare queste migliorie. E’ troppo tardi.”

Se fosse realmente così non esisterebbe il Refactoring :)

Per questo post ho preso, molto spunto da libro Refactoring To Patterns di Joshua Kerievsky.

posted @ lunedì 18 maggio 2009 20:42

Print

Comments on this entry:

# re: Conditional Complexity: Replace Conditional Logic with Strategy

Left by Salvatore Di Fazio at 20/05/2009 16:25
Gravatar
Ciao neronotte,
non c'è alcun problema per le domande, anzi mi fanno piacere :)

Poi il problema non sono le if ma le motivazioni per le quali ci sono.
I macro motivi sono 2 gruppi:

- errato design e nessuna voglia di refactoring
- ci sono perchè sennò si complicherebbe troppo il design

il primo macrogruppo è quello sbagliato per eccellenza, mentre il secondo ci può stare.
Cioè è inutile fare quanto sopra se l'hai solamente in un punto del codice.
Se i punti del codice saranno molteplici allora userai lo Strategy, non per toglierti le if, quanto x avere "quel codice pieno di if" in un'unico punto.

Questo ti aiuterà con la manutenzione del codice.

Poi, post dopo post, vedrai come poter evitare più if possibili sparsi per il codice.

Già con i post precedenti, abbiamo risolto un paio di smell. E continueremo così... tempo permettendo :-\

# 

Left by BitVector at 15/10/2009 12:04
Gravatar
Conditional Complexity: Move Embellishment to Decorator
Comments have been closed on this topic.