AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 578, trackbacks - 27

Alcune alternative di design per ridurre la complessità

Ringrazio per le proposte di alternative di design che mi sono state segnalate nei commenti del mio precedente post.

Le riporto qui come futura memoria per valutare alternative di design. Per prima cosa è corretta l'osservazione di Riccardo, il play va chiamato dal consumer e non all'interno della classe Sounds. Questo perchè se oltre a Play avessi la necessità di chiamare un altro metodo dovrei duplicare l'if in giro per il codice.

  public class Sounds

  {

    private readonly Dictionary<Keys, Sound> m_sounds;

    private readonly Sound m_defaultSound;

 

    public Sounds(Sound defaultSound, Dictionary<Keys, Sound> sounds)

    {

      m_defaultSound = defaultSound;

      m_sounds = sounds;

    }

 

    public Sound GetSoundBy(Keys keyCode)

    {

      if (m_sounds.ContainsKey(keyCode))

      {

        return m_sounds[keyCode];

      }

      return m_defaultSound;

    }

  }

Vediamo adesso le due proposte di Alessandro:

  public class Sounds

  {

    private readonly Dictionary<Keys, Sound> m_sounds;

    private readonly KeyValuePair<Keys, Sound> m_defaultSound;

 

    public Sounds(Sound defaultSound, Dictionary<Keys, Sound> sounds)

    {

      m_defaultSound = new KeyValuePair<Keys, Sound>( Keys.Zoom, defaultSound);

      m_sounds = sounds;

    }

 

    public Sound GetSoundBy(Keys keyCode)

    {

      var qry = m_sounds.Where(s => s.Key == keyCode);

      return qry.DefaultIfEmpty(m_defaultSound).Single().Value;

    }

  }

Ho cambiato un pochino tutte le soluzioni per renderle uniformi, in modo da poterle testare nello stesso modo cambiando solo il namespace. Qui vedo uno smell: l'inizializzazione del default sound come KeyValuePair in cui il primo parametro non è significativo, inoltre, IMHO una maggiore difficoltà di leggibilità del metodo GetSoundBy. Comunque qui l'if è sparito o meglio lo fa il framework per noi.

Un'altra soluzione sempre di Alessandro:

    public Sound GetSoundBy(Keys keyCode)

    {

      var qry = m_sounds.SingleOrDefault(s => s.Key == keyCode);

      return qry.Value ?? m_defaultSound.Value;

    }

In questo caso l'operatore di null-coalescing lo considero equivalente all'if. Il metodo ne ha guadagnato in leggibilità.

Questa è la soluzione di Luca del Tongo:

  class Sounds

  {

    private readonly Dictionary<Keys, Sound> m_sounds;

    private readonly Sound m_defaultSound;

 

    public Sounds(Sound defaultSound, Dictionary<Keys, Sound> sounds)

    {

      m_defaultSound = defaultSound;

      m_sounds = sounds;

    }

 

    public Sound GetSoundBy(Keys keyCode)

    {

      Sound sound;

      m_sounds.TryGetValue(keyCode, out sound);

      return sound ?? m_defaultSound;

    }

  }

Simile a quella di Alessandro ma più semplice perchè non ha dovuto creare un KeyValuePair fittizio. Comunque anche qui c'è l'If.

Infine propongo la soluzione di Luka:

  class Sounds

  {

    private readonly Dictionary<Keys, Sound> m_sounds = new Dictionary<Keys, Sound>();

 

    public Sounds(Dictionary<Keys, Sound> sounds)

    {

      m_sounds = sounds;

    }

 

    public Sound GetSoundBy(Keys keyCode)

    {

      Sound soundToPlay;

      m_sounds.TryGetValue(keyCode, out soundToPlay);

      return soundToPlay;

    }

  }

 

struct Sound

{

  public static readonly Sound DefaultSound;

 

  private static int defaultFrequency;

  private static int defaultTime;

  public static void SetDefaultSound(int defaultFrequency, int defaultTime)

  {

    Sound.defaultFrequency = defaultFrequency;

    Sound.defaultTime = defaultTime;

  }

 

  private int deltaToDefaultFrequency;

  private int deltaToDefaultTime;

 

  public Sound(int frequency, int time)

  {

    this.deltaToDefaultFrequency = frequency - Sound.defaultFrequency;

    this.deltaToDefaultTime = time - Sound.defaultTime;

  }

 

  public void Play()

  {

    Console.Beep(defaultFrequency + deltaToDefaultFrequency, defaultTime + deltaToDefaultTime);

  }

}

Ho dovuto riportare anche la classe Sound perchè ha usato un approccio completamente diverso; cioè ha sfruttato il meccanismo del linguaggio delle struct che non possono essere null. Il problema in questo caso è che non si riesce in modo semplice a settare un valore di default, infatti quello nell'esempio è un delta di default.

Update:

Matteo Baglini propone questa soluzione di cui riporto il link. In pratica ha creato un decorator che ha la stessa interfaccia del Dictionary, ma con in più la gestione del default.

Se volete provare le varie versioni questo è il main che ho usato:

      Sound defaultSound = new Sound("default");

      var soundsMap = new Dictionary<Keys, Sound>

      {

      {Keys.A, new Sound("A")},

      {Keys.B, new Sound("B")}

      };

      var sounds = new Sounds(defaultSound, soundsMap);

      sounds.GetSoundBy(Keys.A).Play();

      sounds.GetSoundBy(Keys.Z).Play();

      Console.ReadKey();

Il metodo Play della classe Sound visualizza solamente il testo del suono in Console.

Se volete proporne altre o rettificare quelle che ho riportato scrivetemeli nei commenti che modifico il post.

Print | posted on domenica 25 aprile 2010 11.45 |

Feedback

Gravatar

# re: Alcune alternative di design per ridurre la complessità

Questo post dev'essere stato un lavoro titanico! :)
25/04/2010 11.54 | Riccardo
Gravatar

# re: Alcune alternative di design per ridurre la complessità


ecco la versione un po piu breve.

hai pubblicato il codice della struct per aiutare chi non sa come funzionano. mentre negli altri casi non hai pubblicato la implementazione del sound quindi il confronto della lunghezza non é equo.



class Sounds<Sound>
{
readonly Dictionary<Keys, Sound> m_sounds = new Dictionary<Keys, Sound>();

public Sounds(Dictionary<Keys, Sound> sounds)
{
m_sounds = sounds;
}

public Sound GetSoundBy(Keys keyCode)
{
Sound soundToPlay;
m_sounds.TryGetValue(keyCode, out soundToPlay);
return soundToPlay;
}

}


struct SoundWithDefault
{
static int defaultFrequency = 800;
static int defaultTime = 200;

int deltaToDefaultFrequency;
int deltaToDefaultTime;

public SoundWithDefault(int frequency, int time)
{
deltaToDefaultFrequency = frequency - defaultFrequency;
deltaToDefaultTime = time - defaultTime;
}

public void Play()
{
Console.Beep(defaultFrequency + deltaToDefaultFrequency, defaultTime + deltaToDefaultTime);
}
}
25/04/2010 15.13 | Luca Minudel
Gravatar

# re: Alcune alternative di design per ridurre la complessità

p.s. ecco le mie personali preferenze

sceglierei la prima di Alessandro quando nella code-base é giá in uso Linq e la mia quando nel codice ci sono giá delle struct.

quando non c'é ne Linq ne Struct nella code-base preferirei la strada di Riccardo: incapsulerei l'IF in un DictionaryWithDefault.

chi immaginava che da un "non si puo fare" si arrivasse a poter disporre di tutte queste alternative? complimenti alle varie soluzioni e grazie ad Antonio per aver provocato questa simpatica discussione
25/04/2010 16.40 | Luca Minudel
Gravatar

# re: Alcune alternative di design per ridurre la complessità

Luka:
Tu mi conosci e lo sai che sono un provocatore ;-).
Infatti mi fa piacere che sono uscite tutte queste idee.
25/04/2010 18.48 | ugog91@yahoo.it
Gravatar

# Ancora sulla complessit

Ancora sulla complessit
26/04/2010 12.37 | WetBlog
Gravatar

# re: Alcune alternative di design per ridurre la complessità

Un'altra alternativa, ma non troppo differente da quelle già proposte, è quella di spostare il concetto dei default instance direttamente nel dictionary applicando il Pattern Decorator.
Ho postato l'implementazione qui: http://pastie.org/935408
26/04/2010 15.17 | Matteo Baglini
Gravatar

# re: Alcune alternative di design per ridurre la complessità

X Matteo:
Come mai passi un Func nel costruttore? Non era più semplice passare direttamente il valore di default?
Propongo, se non mi sfugge qualcosa, questa implementazione:

public SmartDictionary(TValue defaultValue)
{
_defaultValue = defaultValue;
innerDictionary = new Dictionary<TKey, TValue>();
}

Cosa ne pensi?
Un ultima quel link è permanente? Posso linkarlo nel post?
26/04/2010 15.51 | Antonio Ganci
Gravatar

# re: Alcune alternative di design per ridurre la complessità

Ciao Antonio,
quel Func è un errore, quindi proposta accettata. L'errore è dovuto al fatto che ho estratto il codice da una collection che usa Func per fare Lazy Initialization del defaultValue e nel porting (aka copy-paste-edit) ho fatto un errore. La versione Lazy (ridotta all'osso) è questa: http://pastie.org/935673

Sì, i link sono permanenti. Certo che puoi linkarli nel post!!
26/04/2010 18.13 | Matteo Baglini

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 2 and 7 and type the answer here:

Powered by: