Design Patterns in C#
I design patterns sono soluzioni collaudate a problemi comuni che si incontrano durante lo sviluppo del software. Applicare questi modelli in C# ti aiuta a scrivere codice più strutturato, riutilizzabile e manutenibile. In questo articolo, esploreremo alcuni dei design patterns più comuni e come implementarli in C#.
Cosa sono i Design Patterns?
I design patterns sono schemi ricorrenti che risolvono problemi di progettazione del software. Non sono specifici di un linguaggio, ma sono ampiamente utilizzati in linguaggi orientati agli oggetti come C#. Questi modelli forniscono una guida su come strutturare le tue classi e oggetti per risolvere problemi comuni in modo efficace.
Vantaggi dei Design Patterns
- Riutilizzabilità: I design patterns promuovono la scrittura di codice riutilizzabile.
- Manutenibilità: Forniscono un’architettura chiara e strutturata, rendendo il codice più facile da mantenere.
- Comunicazione: I design patterns offrono un linguaggio comune tra sviluppatori, facilitando la comunicazione e la collaborazione nei team.
Tipi di Design Patterns
I design patterns sono solitamente classificati in tre categorie principali: creazionali, strutturali e comportamentali.
1. Creational Patterns
Questi pattern si concentrano sul processo di creazione degli oggetti, rendendo il sistema indipendente da come gli oggetti vengono creati, compositi e rappresentati.
Singleton
Il Singleton garantisce che una classe abbia una sola istanza e fornisce un punto di accesso globale a quella istanza.
Esempio
public class Singleton
{
private static Singleton _instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
public void MostraMessaggio()
{
Console.WriteLine("Singleton: Unica istanza creata!");
}
}
public static void Main()
{
Singleton.Instance.MostraMessaggio();
}
In questo esempio, la classe Singleton
garantisce che venga creata solo una singola istanza di se stessa.
Factory Method
Il Factory Method fornisce un’interfaccia per creare oggetti in una superclasse, ma permette alle sottoclassi di alterare il tipo di oggetti che saranno creati.
Esempio
public abstract class Prodotto
{
public abstract void Operazione();
}
public class ProdottoConcreto : Prodotto
{
public override void Operazione()
{
Console.WriteLine("Prodotto concreto operativo.");
}
}
public abstract class Creatore
{
public abstract Prodotto CreaProdotto();
}
public class CreatoreConcreto : Creatore
{
public override Prodotto CreaProdotto()
{
return new ProdottoConcreto();
}
}
public static void Main()
{
Creatore creatore = new CreatoreConcreto();
Prodotto prodotto = creatore.CreaProdotto();
prodotto.Operazione();
}
Il pattern Factory Method permette alla classe Creatore
di delegare la creazione di oggetti alle sue sottoclassi.
2. Structural Patterns
I pattern strutturali si occupano di comporre classi e oggetti per formare strutture più grandi.
Adapter
L’Adapter permette a due interfacce incompatibili di lavorare insieme. Funziona come un traduttore tra le classi.
Esempio
public interface ITarget
{
void Richiesta();
}
public class Adattato
{
public void RichiestaSpecificata()
{
Console.WriteLine("Richiesta specificata dal'Adattato.");
}
}
public class Adapter : ITarget
{
private readonly Adattato _adattato;
public Adapter(Adattato adattato)
{
_adattato = adattato;
}
public void Richiesta()
{
_adattato.RichiestaSpecificata();
}
}
public static void Main()
{
Adattato adattato = new Adattato();
ITarget target = new Adapter(adattato);
target.Richiesta();
}
In questo esempio, Adapter
consente all’oggetto Adattato
di essere utilizzato tramite l’interfaccia ITarget
.
Decorator
Il Decorator permette di aggiungere comportamenti a un oggetto dinamicamente senza dover modificare il codice delle classi esistenti.
Esempio
public interface IComponente
{
void Operazione();
}
public class ComponenteConcreto : IComponente
{
public void Operazione()
{
Console.WriteLine("Operazione del componente concreto.");
}
}
public class Decorator : IComponente
{
protected IComponente _componente;
public Decorator(IComponente componente)
{
_componente = componente;
}
public virtual void Operazione()
{
_componente.Operazione();
}
}
public class DecoratorConcreto : Decorator
{
public DecoratorConcreto(IComponente componente) : base(componente)
{
}
public override void Operazione()
{
base.Operazione();
Console.WriteLine("Aggiunta di funzionalità dal decorator concreto.");
}
}
public static void Main()
{
IComponente componente = new ComponenteConcreto();
IComponente decorator = new DecoratorConcreto(componente);
decorator.Operazione();
}
In questo esempio, DecoratorConcreto
aggiunge funzionalità extra a ComponenteConcreto
senza modificarne il codice.
3. Behavioral Patterns
I pattern comportamentali si concentrano su algoritmi e l’assegnazione delle responsabilità tra gli oggetti.
Strategy
Il Strategy permette di definire una famiglia di algoritmi, incapsularli e renderli intercambiabili. Consente di selezionare l’algoritmo da utilizzare in fase di esecuzione.
Esempio
public interface IStrategia
{
void Esegui();
}
public class StrategiaConcretaA : IStrategia
{
public void Esegui()
{
Console.WriteLine("Esecuzione della strategia A.");
}
}
public class StrategiaConcretaB : IStrategia
{
public void Esegui()
{
Console.WriteLine("Esecuzione della strategia B.");
}
}
public class Contesto
{
private IStrategia _strategia;
public Contesto(IStrategia strategia)
{
_strategia = strategia;
}
public void EseguiStrategia()
{
_strategia.Esegui();
}
}
public static void Main()
{
Contesto contesto;
contesto = new Contesto(new StrategiaConcretaA());
contesto.EseguiStrategia();
contesto = new Contesto(new StrategiaConcretaB());
contesto.EseguiStrategia();
}
Qui, StrategiaConcretaA
e StrategiaConcretaB
sono implementazioni della stessa interfaccia IStrategia
, che può essere scelta dinamicamente.
Observer
L’Observer definisce una dipendenza uno-a-molti tra oggetti in modo tale che quando un oggetto cambia stato, tutti i suoi dipendenti vengono notificati e aggiornati automaticamente.
Esempio
public interface IObserver
{
void Aggiorna(string stato);
}
public class OsservatoreConcreto : IObserver
{
private readonly string _nome;
public OsservatoreConcreto(string nome)
{
_nome = nome;
}
public void Aggiorna(string stato)
{
Console.WriteLine($"{_nome} ha ricevuto l'aggiornamento: {stato}");
}
}
public class Soggetto
{
private readonly List<IObserver> _osservatori = new List<IObserver>();
private string _stato;
public void AggiungiOsservatore(IObserver osservatore)
{
_osservatori.Add(osservatore);
}
public void RimuoviOsservatore(IObserver osservatore)
{
_osservatori.Remove(osservatore);
}
public void NotificaOsservatori()
{
foreach (var osservatore in _osservatori)
{
osservatore.Aggiorna(_stato);
}
}
public string Stato
{
get => _stato;
set
{
_stato = value;
NotificaOsservatori();
}
}
}
public static void Main()
{
Soggetto soggetto = new Soggetto();
IObserver osservatore1 = new OsservatoreConcreto("Osservatore 1");
IObserver
osservatore2 = new OsservatoreConcreto("Osservatore 2");
soggetto.AggiungiOsservatore(osservatore1);
soggetto.AggiungiOsservatore(osservatore2);
soggetto.Stato = "Stato cambiato!";
}
In questo esempio, ogni volta che lo stato del Soggetto
cambia, tutti gli osservatori registrati vengono notificati e aggiornati.
Conclusione
I design patterns sono strumenti potenti che possono aiutarti a scrivere codice più efficace, manutenibile e flessibile in C#. Comprendere e applicare questi pattern ti permetterà di affrontare problemi di progettazione comuni in modo strutturato e di migliorare la qualità del tuo software. Con la pratica, sarai in grado di riconoscere quando e come utilizzare ciascuno di questi pattern per creare soluzioni robuste e scalabili.