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ó