Els patrons de disseny són solucions provades i documentades per a problemes comuns en el desenvolupament de programari. Aquests patrons ajuden a crear codi més reutilitzable, mantenible i escalable. En aquest tema, explorarem alguns dels patrons de disseny més comuns i com implementar-los en C#.
Contingut
Introducció als Patrons de Disseny
Els patrons de disseny es classifiquen en tres categories principals:
- Patrons Creacionals: Ajuden a crear objectes de manera que s'adeqüin a la situació donada.
- Patrons Estructurals: Ajuden a compondre objectes i classes en estructures més grans.
- Patrons de Comportament: Ajuden a definir com els objectes interactuen i es comuniquen entre ells.
Patrons Creacionals
Singleton
El patró Singleton assegura que una classe només tingui una instància i proporciona un punt d'accés global a aquesta instància.
Exemple de Codi
public class Singleton
{
private static Singleton instance = null;
private static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
}Explicació
- private static Singleton instance: Emmagatzema la instància única de la classe.
- private static readonly object padlock: Utilitzat per assegurar que l'accés a la instància sigui segur en entorns multithread.
- Singleton(): Constructor privat per evitar la creació d'instàncies externes.
- public static Singleton Instance: Proporciona l'accés global a la instància única.
Factory Method
El patró Factory Method defineix una interfície per crear un objecte, però permet a les subclasses decidir quina classe instanciar.
Exemple de Codi
public abstract class Product
{
public abstract void Operation();
}
public class ConcreteProductA : Product
{
public override void Operation()
{
Console.WriteLine("Operation of ConcreteProductA");
}
}
public class ConcreteProductB : Product
{
public override void Operation()
{
Console.WriteLine("Operation of ConcreteProductB");
}
}
public abstract class Creator
{
public abstract Product FactoryMethod();
}
public class ConcreteCreatorA : Creator
{
public override Product FactoryMethod()
{
return new ConcreteProductA();
}
}
public class ConcreteCreatorB : Creator
{
public override Product FactoryMethod()
{
return new ConcreteProductB();
}
}Explicació
- Product: Defineix una interfície per als objectes que la fàbrica crearà.
- ConcreteProductA i ConcreteProductB: Implementen la interfície Product.
- Creator: Declara el mètode FactoryMethod que retorna un objecte de tipus Product.
- ConcreteCreatorA i ConcreteCreatorB: Implementen el mètode FactoryMethod per crear instàncies de ConcreteProductA i ConcreteProductB respectivament.
Abstract Factory
El patró Abstract Factory proporciona una interfície per crear famílies d'objectes relacionats o dependents sense especificar les seves classes concretes.
Exemple de Codi
public interface IAbstractFactory
{
IAbstractProductA CreateProductA();
IAbstractProductB CreateProductB();
}
public class ConcreteFactory1 : IAbstractFactory
{
public IAbstractProductA CreateProductA()
{
return new ProductA1();
}
public IAbstractProductB CreateProductB()
{
return new ProductB1();
}
}
public class ConcreteFactory2 : IAbstractFactory
{
public IAbstractProductA CreateProductA()
{
return new ProductA2();
}
public IAbstractProductB CreateProductB()
{
return new ProductB2();
}
}
public interface IAbstractProductA
{
void OperationA();
}
public interface IAbstractProductB
{
void OperationB();
}
public class ProductA1 : IAbstractProductA
{
public void OperationA()
{
Console.WriteLine("OperationA of ProductA1");
}
}
public class ProductB1 : IAbstractProductB
{
public void OperationB()
{
Console.WriteLine("OperationB of ProductB1");
}
}
public class ProductA2 : IAbstractProductA
{
public void OperationA()
{
Console.WriteLine("OperationA of ProductA2");
}
}
public class ProductB2 : IAbstractProductB
{
public void OperationB()
{
Console.WriteLine("OperationB of ProductB2");
}
}Explicació
- IAbstractFactory: Defineix mètodes per crear productes abstractes.
- ConcreteFactory1 i ConcreteFactory2: Implementen la interfície IAbstractFactory per crear productes concrets.
- IAbstractProductA i IAbstractProductB: Defineixen interfícies per als productes.
- ProductA1, ProductA2, ProductB1, ProductB2: Implementen les interfícies de producte.
Patrons Estructurals
Adapter
El patró Adapter permet que classes amb interfícies incompatibles treballin juntes.
Exemple de Codi
public interface ITarget
{
void Request();
}
public class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("SpecificRequest of Adaptee");
}
}
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public void Request()
{
_adaptee.SpecificRequest();
}
}Explicació
- ITarget: Defineix la interfície que el client utilitza.
- Adaptee: Conté una interfície existent que necessita adaptar-se.
- Adapter: Implementa la interfície ITarget i tradueix les crides al mètode SpecificRequest de l'Adaptee.
Decorator
El patró Decorator permet afegir funcionalitat a un objecte de manera dinàmica.
Exemple de Codi
public abstract class Component
{
public abstract void Operation();
}
public class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("Operation of ConcreteComponent");
}
}
public abstract class Decorator : Component
{
protected Component _component;
public void SetComponent(Component component)
{
_component = component;
}
public override void Operation()
{
if (_component != null)
{
_component.Operation();
}
}
}
public class ConcreteDecoratorA : Decorator
{
public override void Operation()
{
base.Operation();
Console.WriteLine("Operation of ConcreteDecoratorA");
}
}
public class ConcreteDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();
Console.WriteLine("Operation of ConcreteDecoratorB");
}
}Explicació
- Component: Defineix la interfície per als objectes que poden tenir responsabilitats afegides dinàmicament.
- ConcreteComponent: Implementa la interfície Component.
- Decorator: Manté una referència a un objecte Component i defineix una interfície que segueix la interfície de Component.
- ConcreteDecoratorA i ConcreteDecoratorB: Afegixen funcionalitat a l'objecte Component.
Facade
El patró Facade proporciona una interfície simplificada a un subsistema complex.
Exemple de Codi
public class SubsystemA
{
public void OperationA()
{
Console.WriteLine("OperationA of SubsystemA");
}
}
public class SubsystemB
{
public void OperationB()
{
Console.WriteLine("OperationB of SubsystemB");
}
}
public class Facade
{
private SubsystemA _subsystemA;
private SubsystemB _subsystemB;
public Facade()
{
_subsystemA = new SubsystemA();
_subsystemB = new SubsystemB();
}
public void Operation()
{
_subsystemA.OperationA();
_subsystemB.OperationB();
}
}Explicació
- SubsystemA i SubsystemB: Representen parts del subsistema complex.
- Facade: Proporciona una interfície simplificada per interactuar amb el subsistema.
Patrons de Comportament
Observer
El patró Observer defineix una dependència un-a-molts entre objectes, de manera que quan un objecte canvia d'estat, tots els seus dependents són notificats i actualitzats automàticament.
Exemple de Codi
public interface IObserver
{
void Update();
}
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
public class ConcreteSubject : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
foreach (var observer in _observers)
{
observer.Update();
}
}
}
public class ConcreteObserver : IObserver
{
public void Update()
{
Console.WriteLine("Observer has been updated");
}
}Explicació
- IObserver: Defineix la interfície per als observadors.
- ISubject: Defineix la interfície per al subjecte que manté una llista d'observadors.
- ConcreteSubject: Implementa la interfície ISubject i notifica els observadors quan hi ha un canvi d'estat.
- ConcreteObserver: Implementa la interfície IObserver i actualitza el seu estat en resposta a les notificacions del subjecte.
Strategy
El patró Strategy defineix una família d'algoritmes, encapsula cada un d'ells i els fa intercanviables. Permet que l'algoritme variï independentment dels clients que l'utilitzen.
Exemple de Codi
public interface IStrategy
{
void Execute();
}
public class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine("Strategy A executed");
}
}
public class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine("Strategy B executed");
}
}
public class Context
{
private IStrategy _strategy;
public void SetStrategy(IStrategy strategy)
{
_strategy = strategy;
}
public void ExecuteStrategy()
{
_strategy.Execute();
}
}Explicació
- IStrategy: Defineix la interfície per a tots els algoritmes.
- ConcreteStrategyA i ConcreteStrategyB: Implementen la interfície IStrategy amb diferents algoritmes.
- Context: Manté una referència a un objecte Strategy i delega l'execució de l'algoritme a aquest objecte.
Command
El patró Command encapsula una petició com un objecte, permetent que els paràmetres de clients amb diferents sol·licituds, cues o registres de sol·licituds, i suporten operacions desfer.
Exemple de Codi
public interface ICommand
{
void Execute();
}
public class Receiver
{
public void Action()
{
Console.WriteLine("Action executed by Receiver");
}
}
public class ConcreteCommand : ICommand
{
private Receiver _receiver;
public ConcreteCommand(Receiver receiver)
{
_receiver = receiver;
}
public void Execute()
{
_receiver.Action();
}
}
public class Invoker
{
private ICommand _command;
public void SetCommand(ICommand command)
{
_command = command;
}
public void ExecuteCommand()
{
_command.Execute();
}
}Explicació
- ICommand: Defineix la interfície per a les comandes.
- Receiver: Conté la lògica per executar l'acció.
- ConcreteCommand: Implementa la interfície ICommand i invoca el mètode del Receiver.
- Invoker: Manté una referència a una comanda i la pot executar.
Exercicis Pràctics
- Implementa un patró Singleton: Crea una classe Logger que només permeti una instància i proporcioni un mètode per registrar missatges.
- Utilitza el patró Factory Method: Crea una fàbrica que generi diferents tipus de documents (PDF, Word) basant-se en una entrada de l'usuari.
- Aplica el patró Observer: Implementa un sistema de notificacions on diversos observadors reben actualitzacions quan un subjecte canvia d'estat.
Conclusió
Els patrons de disseny són eines poderoses que ajuden a resoldre problemes comuns en el desenvolupament de programari. En aquest tema, hem explorat alguns dels patrons més utilitzats en C# i hem vist com implementar-los. Practicar aquests patrons t'ajudarà a escriure codi més net, mantenible i escalable.
Curs de Programació en C#
Mòdul 1: Introducció al C#
- Introducció al C#
- Configuració de l'Entorn de Desenvolupament
- Programa Hello World
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
Mòdul 2: Estructures de Control
Mòdul 3: Programació Orientada a Objectes
Mòdul 4: Conceptes Avançats de C#
- Interfícies
- Delegats i Esdeveniments
- Genèrics
- Col·leccions
- LINQ (Consulta Integrada al Llenguatge)
- Programació Asíncrona
Mòdul 5: Treballant amb Dades
Mòdul 6: Temes Avançats
- Reflexió
- Atributs
- Programació Dinàmica
- Gestió de Memòria i Recollida d'Escombraries
- Multifil i Programació Paral·lela
Mòdul 7: Construcció d'Aplicacions
Mòdul 8: Millors Pràctiques i Patrons de Disseny
- Estàndards de Codificació i Millors Pràctiques
- Patrons de Disseny
- Proves Unitàries
- Revisió de Codi i Refactorització
