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

  1. Implementa un patró Singleton: Crea una classe Logger que només permeti una instància i proporcioni un mètode per registrar missatges.
  2. 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.
  3. 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.

© Copyright 2024. Tots els drets reservats