Design Patterns: La Cassetta degli Attrezzi dello Sviluppatore 🛠️
Ah, i design pattern! Quei piccoli gioielli di saggezza che ci fanno sembrare più intelligenti di quanto siamo realmente. Ma cosa sono esattamente? E perché dovresti preoccupartene? Beh, immagina di costruire una casa senza un progetto. Certo, potresti farcela, ma probabilmente finirai con un tetto che perde e una porta che non si chiude. I design pattern sono i progetti per il tuo codice: soluzioni collaudate a problemi comuni.
Un Po’ di Storia 📜
Il concetto di design pattern nasce nel libro Design Patterns: Elements of Reusable Object-Oriented Software, scritto da quattro luminari del software (la famosa “Gang of Four”): Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Questo libro ha introdotto un linguaggio comune per descrivere soluzioni a problemi ricorrenti nello sviluppo software.
Ogni design pattern identifica un problema generalizzato e associa ad esso una soluzione. Spesso, questi pattern sono rappresentati con diagrammi UML per facilitarne la comprensione.
Perché Usare i Design Pattern? 🤔
- Riutilizzabilità: Perché reinventare la ruota quando puoi prendere in prestito una soluzione già testata?
- Manutenibilità: Codice più leggibile e organizzato significa meno mal di testa per te (e per chi erediterà il tuo progetto).
- Comunicazione: Dire “Usiamo il Singleton” è molto più veloce che spiegare tutto il concetto da zero.
- Soluzioni Provate: I design pattern sono frutto di anni di esperienza e casi d’uso reali, quindi puoi fidarti che funzionano.
Le Tre Categorie Principali
Il libro della Gang of Four suddivide i design pattern in tre categorie principali:
1. Pattern Creazionali 🏗️
Questi pattern si concentrano sulla creazione degli oggetti, isolando la logica di costruzione dal resto del codice.
- Singleton: Garantisce che una classe abbia una sola istanza.
- Factory Method: Definisce un’interfaccia per creare oggetti, lasciando alle sottoclassi la decisione su quale classe istanziare.
- Abstract Factory: Permette di creare famiglie di oggetti correlati senza specificare le loro classi concrete.
- Builder: Consente di costruire oggetti complessi passo dopo passo.
- Prototype: Crea nuovi oggetti clonando un’istanza esistente.
Esempio di Factory Method in GoLang
1// filepath: /Users/sandrolain/work/fullstackdeveloper/content/docs/development/design-patterns.md
2package main
3
4import "fmt"
5
6type Product interface {
7 Use() string
8}
9
10type ConcreteProductA struct{}
11
12func (p ConcreteProductA) Use() string {
13 return "Using Product A"
14}
15
16type ConcreteProductB struct{}
17
18func (p ConcreteProductB) Use() string {
19 return "Using Product B"
20}
21
22func FactoryMethod(productType string) Product {
23 switch productType {
24 case "A":
25 return ConcreteProductA{}
26 case "B":
27 return ConcreteProductB{}
28 default:
29 return nil
30 }
31}
32
33func main() {
34 product := FactoryMethod("A")
35 fmt.Println(product.Use()) // Using Product A
36}
2. Pattern Strutturali 🧱
Questi pattern si occupano della composizione delle classi e degli oggetti, rendendo il sistema più flessibile ed efficiente.
- Adapter: Permette a due interfacce incompatibili di lavorare insieme.
- Bridge: Divide un’astrazione dalla sua implementazione, permettendo loro di evolvere indipendentemente.
- Decorator: Aggiunge dinamicamente funzionalità a un oggetto.
- Façade: Fornisce un’interfaccia semplificata a un sistema complesso.
- Flyweight: Riduce il consumo di memoria condividendo dati tra oggetti simili.
- Proxy: Fornisce un surrogato o un placeholder per controllare l’accesso a un oggetto.
Esempio di Decorator in GoLang
1// filepath: /Users/sandrolain/work/fullstackdeveloper/content/docs/development/design-patterns.md
2package main
3
4import "fmt"
5
6// Component interface
7type Coffee interface {
8 Cost() int
9 Description() string
10}
11
12// Concrete Component
13type SimpleCoffee struct{}
14
15func (c SimpleCoffee) Cost() int {
16 return 5
17}
18
19func (c SimpleCoffee) Description() string {
20 return "Simple Coffee"
21}
22
23// Decorator
24type MilkDecorator struct {
25 coffee Coffee
26}
27
28func (m MilkDecorator) Cost() int {
29 return m.coffee.Cost() + 2
30}
31
32func (m MilkDecorator) Description() string {
33 return m.coffee.Description() + ", Milk"
34}
35
36type SugarDecorator struct {
37 coffee Coffee
38}
39
40func (s SugarDecorator) Cost() int {
41 return s.coffee.Cost() + 1
42}
43
44func (s SugarDecorator) Description() string {
45 return s.coffee.Description() + ", Sugar"
46}
47
48func main() {
49 coffee := SimpleCoffee{}
50 fmt.Println(coffee.Description(), "Cost:", coffee.Cost()) // Simple Coffee Cost: 5
51
52 coffeeWithMilk := MilkDecorator{coffee: coffee}
53 fmt.Println(coffeeWithMilk.Description(), "Cost:", coffeeWithMilk.Cost()) // Simple Coffee, Milk Cost: 7
54
55 coffeeWithMilkAndSugar := SugarDecorator{coffee: coffeeWithMilk}
56 fmt.Println(coffeeWithMilkAndSugar.Description(), "Cost:", coffeeWithMilkAndSugar.Cost()) // Simple Coffee, Milk, Sugar Cost: 8
57}
3. Pattern Comportamentali 🎭
Questi pattern si concentrano sulle interazioni tra gli oggetti e sulla distribuzione delle responsabilità.
- Observer: Notifica automaticamente gli osservatori quando lo stato di un oggetto cambia.
- Strategy: Permette di selezionare dinamicamente un algoritmo tra diversi disponibili.
- Command: Incapsula una richiesta come oggetto, permettendo di parametrizzare i client con richieste diverse.
- State: Permette a un oggetto di cambiare comportamento in base al suo stato interno.
- Template Method: Definisce il “guscio” di un algoritmo, delegando i dettagli alle sottoclassi.
- Visitor: Separa un algoritmo dalla struttura dati su cui opera.
Esempio di Strategy in GoLang
1// filepath: /Users/sandrolain/work/fullstackdeveloper/content/docs/development/design-patterns.md
2package main
3
4import "fmt"
5
6type Strategy interface {
7 Execute(a, b int) int
8}
9
10type Add struct{}
11
12func (Add) Execute(a, b int) int {
13 return a + b
14}
15
16type Multiply struct{}
17
18func (Multiply) Execute(a, b int) int {
19 return a * b
20}
21
22func main() {
23 var strategy Strategy
24
25 strategy = Add{}
26 fmt.Println("Add:", strategy.Execute(3, 4)) // Add: 7
27
28 strategy = Multiply{}
29 fmt.Println("Multiply:", strategy.Execute(3, 4)) // Multiply: 12
30}
Risorse Utili 📚
- Refactoring Guru: Una guida completa ai design pattern con esempi pratici.
- Canale YouTube di Christopher Okhravi: Video esplicativi sui design pattern.
- Wikipedia - Design Pattern (IT): Una panoramica generale.
Conclusione
I design pattern sono strumenti potenti, ma come ogni strumento, vanno usati con giudizio. Non complicare inutilmente il tuo codice solo per “usare un pattern”. Ricorda: il buon senso è il miglior design pattern di tutti. 😉