Indecent Conditional Logic: Replace Conditional Logic with Stategy and Nullable Object

Continuiamo con lo smell: Indecent Conditional Logic

Questo smell l’ho creato io questa mattina.
Nel senso che ho trovato del codice che non mi piaceva e ho voluto trovare una soluzione.

Problema:

Il client crea dei tipi con valori di default se le condizioni sono vere o altrimenti con i valori passati.

Motivazione:

L’esponenziale presenza di if mi irrita l’epidermide e, nel codice che andremo a vedere, di if non ce ne sono poche.
Tentendo questa logica, avremo un proliferare di condizioni inutili lungo tutto il nostro progetto.

Un esempio di logica errata:

Ecco un esempio, tratto da una Web Application, di controlli di valori nel quale ci potremmo imbattere:

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string value1;
try
{
value1 = Page.Request.QueryString["value1"].ToString();

}
catch (System.Exception em)
{
value1 = "";
}

string value2;

if (Page.Request.QueryString["value2"] != null)
value2 = Page.Request.QueryString["value1"].ToString();
else
value2 = "";
}
}

 
Soluzione:

Tenendo presente la regola: far lavorare il client tramite interfacce e non tramite oggetti.
(Si, lo scriverò in ogni post, magari lo capiremo :D).

Per la soluzione ho tenuto presente 3 bisogni:

  1. Far tornare un valore di default se il valore nella QueryString è null o vuoto (Null Object).
  2. Far tornare un valore diverso da quello di default se specificato (Creation Methods).
  3. Mantenere la costruzione degli oggetti in un unico punto (Abstract Factory).

Questa è una possibile soluzione, la mia soluzione:

 

class Programm
{
static void Main(string[] args)
{
List<FactoryNullableObject> lst = new List<FactoryNullableObject>();
lst.Add(FactoryNullableObject.CreateMyObjectNoDefault(null));
lst.Add(FactoryNullableObject.CreateMyObjectWidthDefault("Giovanni", "Giuseppe"));
lst.Add(FactoryNullableObject.CreateMyObjectWidthDefault(null, "Salvo"));

foreach (FactoryNullableObject item in lst)
Console.WriteLine(item.Value);

Console.ReadLine();
}
}
 
 
abstract class FactoryNullableObject
{
private static FactoryNullableObject FactoryMethod(string key, string defaultValue)
{
if (key == null)
return new NullableQueryStringObject(defaultValue);

return new QueryStringNullable(key);
}

public static FactoryNullableObject CreateMyObjectNoDefault(string key)
{
return FactoryMethod(key, "");
}

public static FactoryNullableObject CreateMyObjectWidthDefault(string key, string defaultValue)
{
return FactoryMethod(key, defaultValue);
}

public abstract string Value { get; }
}

class QueryStringNullable : FactoryNullableObject
{
public QueryStringNullable(string key)
{
_value = key;
}

public string _value = "";
public override string Value
{
get
{
return _value;
}
}
}

class NullableQueryStringObject : FactoryNullableObject
{
public NullableQueryStringObject(string defaultValue)
{
_value = defaultValue;
}

public string _value = "";
public override string Value
{
get { return _value; }
}
}

 

 

Benefici e non
+ Semplifica e chiarifica la creazione degli oggetti.
+ Allegerisce la conoscenza da parte del client delle classi e della loro implementazione.
+ Mantiene viva la regola sull’utilizzo delle interfacce.

- Potrebbe richiedere il passaggio di parametri ulteriori ai Factory Method.
- E’ un metodo non standard per istanziare gli oggetti. Così potreste trovarvi alcune classi con costruttori classici e altre no.
- Potrebbe esserci un proliferare di metodi CreateMyObject

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 :)