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.

posted @ giovedì 21 maggio 2009 15:05

Print

Comments on this entry:

# re: Conditional Complexity: Move Embellishment to Decorator

Left by Cristian Bressan at 21/05/2009 15:57
Gravatar
Ottimo post!

In alternativa alla tua soluzione, è efficace l'uso degli extension methods. Il codice diventa ancora più semplice ed intuitivo.

class Program
{
static void Main(string[] args)
{
string s = "stringa da decodificare\r\n";

Console.WriteLine(s.Decode().RemoveTabs().CapitalizeFirstLetter());
Console.ReadLine();
}
}

public static class StringToDecode
{

public static string Decode(this String s)
{
s += "decodificata\r\n";
return s;
}

public static string RemoveTabs(this String s)
{
s += "tabs rimossi\r\n";
return s;
}

public static string CapitalizeFirstLetter(this String s)
{
s += "prime lettere in maiuscolo\r\n";
return s;
}
}
Comments have been closed on this topic.