🏗️ Design Patterns: Architetture per il Codice
“I pattern sono soluzioni standard a problemi comuni nella progettazione del software.”
::: info I Design Patterns, formalizzati dalla Gang of Four (GoF), si dividono in tre categorie principali. Non sono snippet di codice da copiare, ma schemi mentali per strutturare la logica del software in modo che sia robusto e manutenibile. :::
1. Patterns Creazionali
Si occupano della creazione degli oggetti, cercando di separare il sistema dai dettagli di come i suoi oggetti vengono creati e composti.
Singleton (Python)
Garantisce che una classe abbia una sola istanza e fornisce un punto di accesso globale ad essa. Utilissimo per gestire pool di connessioni ai database o configurazioni.
from typing import Any
class SingletonMeta(type):
"""Metaclass that creates a Singleton instance."""
_instances: dict[Any, Any] = {}
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def connect(self) -> None:
print("Connecting to Database...")
2. Patterns Strutturali
Riguardano la composizione di classi e oggetti per formare strutture più grandi e flessibili.
Adapter (C++)
Permette a interfacce incompatibili di lavorare insieme. Funge da “traduttore”.
#include <iostream>
#include <memory>
#include <string>
// Target interface
class ModernSensor {
public:
virtual ~ModernSensor() = default;
virtual float getTemperatureCelsius() const = 0;
};
// Legacy class with incompatible interface
class LegacyFahrenheitSensor {
public:
float getTempF() const { return 98.6f; }
};
// Adapter: adapts Legacy to Modern
class SensorAdapter : public ModernSensor {
private:
std::unique_ptr<LegacyFahrenheitSensor> legacy_sensor;
public:
SensorAdapter() : legacy_sensor(std::make_unique<LegacyFahrenheitSensor>()) {}
float getTemperatureCelsius() const override {
return (legacy_sensor->getTempF() - 32.0f) * 5.0f / 9.0f;
}
};
3. Patterns Comportamentali
Si concentrano sulla comunicazione tra oggetti, ovvero come gli oggetti interagiscono e si distribuiscono le responsabilità.
Strategy (Python)
Permette di definire una famiglia di algoritmi, incapsularli e renderli intercambiabili a runtime.
from abc import ABC, abstractmethod
from typing import List
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, files: List[str]) -> None:
pass
class ZIPCompression(CompressionStrategy):
def compress(self, files: List[str]) -> None:
print(f"Compressing {len(files)} files using ZIP.")
class RARCompression(CompressionStrategy):
def compress(self, files: List[str]) -> None:
print(f"Compressing {len(files)} files using RAR.")
class ArchiveManager:
def __init__(self, strategy: CompressionStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: CompressionStrategy) -> None:
self._strategy = strategy
def create_archive(self, files: List[str]) -> None:
self._strategy.compress(files)
⚖️ Critical Spirit: Quando NON usarli
L’errore più comune di un ingegnere è l’Over-Engineering.
- Non forzare i pattern: Se una funzione semplice risolve il problema, non creare una gerarchia di classi con Factory e Strategy.
- YAGNI (You Ain’t Gonna Need It): Applica un pattern solo quando la complessità del problema lo richiede realmente, non “in previsione di” ipotetici cambiamenti futuri che potrebbero non avvenire mai.
Ultimo aggiornamento: {{UPDATE_DATE}} | Tags: #SoftwareArchitecture #DesignPatterns #CleanCode #Python #CPP