Continuiamo con lo smell: Duplicated Code
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:
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.
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:
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.