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

 

Duplicated Code: Introduce Polymorphic Creation with Factory Method

Continuiamo con lo smell: Duplicated Code

ccfinder5_thumb[1]

Problema:

Due o più classi, gerarchicamente implementate (ovvero avendo la stessa classe base), contengono lo stesso metodo.

Solitamente questo smell nasce dal copia&incolla o perchè non si conoscono le classi sviluppate precedentemente (e questo può essere uno smell causato dal nome
usato per le vecchie classi o perchè non abbiamo fatto un pò di Pair Programming).

Motivazione:

Innanzitutto nella programmazione Object-Oriented, la strada più frequente e più pulita per creare un oggetto è il pattern Factory Method.
Solitamente il pattern viene implementato durante la stesura della classe base, creando il pattern nella classe stessa o lasciando chi eredita il compito di implementarlo.
Stessa cosa dicasi quando, invece di una classe base, abbiamo un’interfaccia.

L’implementazione del pattern semplifica l’estensione della classe.

Solitamente una spruzzata di Template Methods migliora, non di poco, il codice.

Un esempio di logica errata:

smell_duplicated_code

 

class Program
{
    static void Main(string[] args)
    {
        DOMBuilder builder = new DOMBuilder("ordini");
        XMLBuilder builder = new XMLBuilder("ordini");
    }
}

public abstract class Builders
{
    public virtual void InsertRoot()
    {
    }
}

public class DOMBuilder : Builders
{
    public DOMBuilder(string rootName)
    { }

    public override void InsertRoot()
    {
    }
}

public class XMLBuilder : Builders
{
    public XMLBuilder(string rootName)
    { }

    public override void InsertRoot()
    {
    }
}

 


Soluzioni:

Ecco, applicando il pattern Factory Method cosa otteremo:

class Programm
{
    static void Main(string[] args)
    {
        AbstractorBuilder[] builders = new AbstractorBuilder[2];
        builders[0] = new DOMBuilder();
        builders[1] = new XMLBuilder();

        foreach (AbstractorBuilder item in builders)
        {
            DataWriter dw = item.FactoryMethod();
            dw.InsertRoot("rootName");
            Console.WriteLine("Created {0}", dw.GetType().Name);
        }

        Console.Read();
    }
}

abstract class DataWriter
{
    public abstract void InsertRoot(string rootName);
}

class DOMDataWriter : DataWriter
{
    public override void InsertRoot(string rootName)
    {
    }
}

class XMLDataWriter : DataWriter
{
    public override void InsertRoot(string rootName)
    {
    }
}

abstract class AbstractorBuilder
{
    public abstract DataWriter FactoryMethod();
}

class DOMBuilder : AbstractorBuilder
{
    public override DataWriter FactoryMethod()
    {
        return new DOMDataWriter();
    }
}

class XMLBuilder : AbstractorBuilder
{
    public override DataWriter FactoryMethod()
    {
        return new XMLDataWriter();
    }
}

 

Questa è una possibile soluzione

Come nel caso del refactoring per lo smell Indecent Exposure, anche quì spostiamo le conoscenze del client dagli oggetti alle classi Abstract che utilizzeremo per creare gli oggetti che ci servono.
Così facendo evitermo di dare al client la responsabiltià di conoscere come istanziare una classe e raggrupperemo il nostro codice in un punto e non più codice sparso in varie parti della nostra applicazione.

refactored_duplicated_code

La stessa soluzione applicando le interfaccie (che preferisco notevolmente di più, sopratutto quando nelle classi abstract abbiamo solamente metodi public anch’essi abstract) è la seguente:

refactored_interface_duplicated_code

class Programm
{
    static void Main(string[] args)
    {
        IBuilder[] builders = new IBuilder[2];
        builders[0] = new DOMBuilder();
        builders[1] = new XMLBuilder();

        foreach (IBuilder item in builders)
        {
            IDataWriter dw = item.FactoryMethod();
            dw.InsertRoot("rootName");
            Console.WriteLine("Created {0}", dw.GetType().Name);
        }

        Console.Read();
    }
}

public interface IBuilder
{
    IDataWriter FactoryMethod();
}

public interface IDataWriter
{
    void InsertRoot(string rootName);
}

class DOMDataWriter : IDataWriter
{
    public void InsertRoot(string rootName)
    {
    }
}

class XMLDataWriter : IDataWriter
{
    public void InsertRoot(string rootName)
    {
    }
}

class DOMBuilder : IBuilder
{
    public IDataWriter FactoryMethod()
    {
        return new DOMDataWriter();
    }
}

class XMLBuilder : IBuilder
{
    public IDataWriter FactoryMethod()
    {
        return new XMLDataWriter();
    }
}

 

Benefici e non
+ Semplifica la creazione delle istanze degli oggetti e riduce il codice duplicato.
+ Divide la logica della creazione dagli oggetti da utilizzare per il nostro scopo finale.
+ Rafforza l’utilizzo del Factory Method.
- Potrebbe richiedere il passaggio di parametri ulteriori ai FactoryMethod.

 

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 questi post sto prendendo, molto, spunto da libro Refactoring To Patterns di Joshua Kerievsky.