Monday, October 5, 2009

Conditional Complexity: Move Embellishment to Decorator

Continuiamo con lo smell: Conditional Complexity
orig_400px-Control_flow_graph_of_function_with_two_if_else_statements.svg

Problema:

Collegandoci al post precedente: Conditional Complexity: Replace Conditional Logic with Strategy
continuiamo con un’altra possibile soluzione
Un esempio di logica errata:
// Prima
string result = stringBuilder.ToString();

if (shouldDecode)
    result = MyDecoder.decode(result);

return result;

// Dopo 1 settimana
string result = stringBuilder.ToString();

if (shouldDecode)
    result = MyDecoder.decode(result);

if (removeTabs)
    result = MyDecoder.withOutTabs(result);

return result;

// Dopo 2 settimane
string result = stringBuilder.ToString();

if (shouldDecode)
    result = MyDecoder.decode(result);

if (removeTabs)
    result = MyDecoder.withOutTabs(result);

if (capitalizeFirstLetter)
    result = MyDecoder.capitalizeFirstLetter(result);

return result;



Motivazione:

Il codice sopra scritto, di per se (nella fase Prima), non è nulla di sbagliato. Semplice, leggibile, testabile. La motivazione al cambiamento nasce quando, aggiungendo le implementazioni, otteniamo qualcosa come nella fase Dopo 2 settimane.
Il pattern Decorator offre una buona soluzione rilasciando il compito alle classi Decorators di lavorare sull’oggetto (embellishment).
In più questo pattern offre la possibilità di aggiungere più Decorators insieme allo stesso oggetto.
Detta così sembrerebbe che il Decorator è la soluzione più elegante sopratutto rispetto allo Strategy; ma in realtà non è così, perchè potremmo non poter usare il Decorator a causa di alcuni suoi limiti:


  • Non si può condividere la stessa istanza di Decorator
  • Il Decorator deve essere conforme all’interfaccia della classe che decorerà
  • Il pattern Decorator richiede più risorse del pattern Strategy sopratutto per classi con molti dati e metodi pubblici
  • I Decorators possono aggiungere comportamenti diversi ad altre classi Decorators, basta che implementano la stessa interfaccia
Quindi non c’è la Soluzione; il Decorator è una possibile soluzione.
Soluzione:
Ecco come si mostrerà il codice di sopra applicando il Decorator:

class Program
{
    static void Main(string[] args)
    {
        IComponent stringaDaDecodificare = new StringToDecode("stringa da decodificare\r\n");
        IComponent stringaLavorata = new Decode(new RemoveTabs(new CapitalizeFirstLetter(stringaDaDecodificare)));

        Console.WriteLine(stringaLavorata.DoIt());
        Console.ReadLine();
    }
}

interface IComponent
{
    string DoIt();
}

class StringToDecode : IComponent
{
    private string _stringToDecode;
    public StringToDecode(string stringToDecode)
    {
        _stringToDecode = stringToDecode;
    }

    public string DoIt()
    {
        return _stringToDecode;
    }
}

class Decode : IComponent
{
    IComponent _component;
    private string _stringDecoded;

    public Decode(IComponent c)
    {
        _component = c;
    }

    public string DoIt()
    {
        string s = _component.DoIt();
        s += "decodificata\r\n";
        _stringDecoded = s;
        return s;
    }
}

class RemoveTabs : IComponent
{
    IComponent _component;

    public RemoveTabs(IComponent c)
    {
        _component = c;
    }

    public string DoIt()
    {
        string s = _component.DoIt();
        s += "tabs rimossi\r\n";
        return s;
    }
}

class CapitalizeFirstLetter : IComponent
{
    IComponent _component;

    public CapitalizeFirstLetter(IComponent c)
    {
        _component = c;
    }

    public string DoIt()
    {
        string s = _component.DoIt();
        s += "prime lettere in maiuscolo\r\n";
        return s;
    }
}


Quì ho usato un’interfaccia, ma potreste usare un’abstract class nel caso in cui avete bisogno di eseguire un’operazione nella classe padre, o se per stile preferite le abstract:






Benefici e non
+ Semplifica le classi rimuovendo le condizioni.
+ Rende chiara l’idea tra classe core (nel nostro caso StringToDecode) e le classi che si occupano degli embellishments.
+ Aiuta a rimuove il codice duplicato sparso per il progetto.

- Cambia l’identità dell’oggetto in quella dell’oggetto che decora.
- Potrebbe rendere il codice complicato da leggere e debuggare.
- Complica il design quando combiniamo i Decorators di un tipo con un’altro.

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.

No comments:

Post a Comment