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 organitzat, reutilitzable i mantenible. En aquest tema, explorarem alguns dels patrons de disseny més comuns i com implementar-los en Dart.

Continguts

Introducció als Patrons de Disseny

Els patrons de disseny es classifiquen en tres categories principals:

  • Patrons Creacionals: Ajuden a crear objectes de manera que es controlin millor les seves creacions.
  • 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 Singleton en Dart

class Singleton {
  static final Singleton _instance = Singleton._internal();

  factory Singleton() {
    return _instance;
  }

  Singleton._internal();

  void someMethod() {
    print('Singleton method called');
  }
}

void main() {
  var singleton1 = Singleton();
  var singleton2 = Singleton();

  print(singleton1 == singleton2); // true
  singleton1.someMethod();
}

Explicació

  • _instance: Una instància estàtica de la classe.
  • factory Singleton(): Un constructor de fàbrica que retorna la mateixa instància cada vegada.
  • Singleton._internal(): Un constructor privat utilitzat per crear la instància única.

Factory Method

El patró Factory Method defineix una interfície per crear un objecte, però permet que les subclasses decideixin quina classe instanciar.

Exemple de Factory Method en Dart

abstract class Product {
  void operation();
}

class ConcreteProductA implements Product {
  @override
  void operation() {
    print('Operation of ConcreteProductA');
  }
}

class ConcreteProductB implements Product {
  @override
  void operation() {
    print('Operation of ConcreteProductB');
  }
}

abstract class Creator {
  Product factoryMethod();

  void someOperation() {
    var product = factoryMethod();
    product.operation();
  }
}

class ConcreteCreatorA extends Creator {
  @override
  Product factoryMethod() {
    return ConcreteProductA();
  }
}

class ConcreteCreatorB extends Creator {
  @override
  Product factoryMethod() {
    return ConcreteProductB();
  }
}

void main() {
  var creatorA = ConcreteCreatorA();
  creatorA.someOperation();

  var creatorB = ConcreteCreatorB();
  creatorB.someOperation();
}

Explicació

  • Product: Defineix la interfície dels objectes que el mètode de fàbrica crearà.
  • ConcreteProductA i ConcreteProductB: Implementen la interfície Product.
  • Creator: Declara el mètode de fàbrica que retorna un objecte de tipus Product.
  • ConcreteCreatorA i ConcreteCreatorB: Implementen el mètode de fàbrica per crear instàncies de ConcreteProductA i ConcreteProductB respectivament.

Patrons Estructurals

Adapter

El patró Adapter permet que classes amb interfícies incompatibles treballin juntes.

Exemple d'Adapter en Dart

class Target {
  void request() {
    print('Target request');
  }
}

class Adaptee {
  void specificRequest() {
    print('Adaptee specific request');
  }
}

class Adapter extends Target {
  final Adaptee _adaptee;

  Adapter(this._adaptee);

  @override
  void request() {
    _adaptee.specificRequest();
  }
}

void main() {
  var adaptee = Adaptee();
  var adapter = Adapter(adaptee);

  adapter.request(); // Adaptee specific request
}

Explicació

  • Target: Defineix l'interfície que el client utilitza.
  • Adaptee: Té una interfície incompatible amb Target.
  • Adapter: Converteix la interfície d'Adaptee en una interfície compatible amb Target.

Decorator

El patró Decorator permet afegir comportament a objectes de manera dinàmica.

Exemple de Decorator en Dart

abstract class Component {
  void operation();
}

class ConcreteComponent implements Component {
  @override
  void operation() {
    print('ConcreteComponent operation');
  }
}

class Decorator implements Component {
  final Component _component;

  Decorator(this._component);

  @override
  void operation() {
    _component.operation();
  }
}

class ConcreteDecoratorA extends Decorator {
  ConcreteDecoratorA(Component component) : super(component);

  @override
  void operation() {
    super.operation();
    print('ConcreteDecoratorA operation');
  }
}

class ConcreteDecoratorB extends Decorator {
  ConcreteDecoratorB(Component component) : super(component);

  @override
  void operation() {
    super.operation();
    print('ConcreteDecoratorB operation');
  }
}

void main() {
  var component = ConcreteComponent();
  var decoratorA = ConcreteDecoratorA(component);
  var decoratorB = ConcreteDecoratorB(decoratorA);

  decoratorB.operation();
}

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 implementa la interfície Component.
  • ConcreteDecoratorA i ConcreteDecoratorB: Afegixen comportament addicional abans o després de cridar l'operació del component.

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 d'Observer en Dart

abstract class Observer {
  void update(String state);
}

class ConcreteObserver implements Observer {
  final String _name;

  ConcreteObserver(this._name);

  @override
  void update(String state) {
    print('$_name received update: $state');
  }
}

class Subject {
  final List<Observer> _observers = [];
  String _state;

  void attach(Observer observer) {
    _observers.add(observer);
  }

  void detach(Observer observer) {
    _observers.remove(observer);
  }

  void notify() {
    for (var observer in _observers) {
      observer.update(_state);
    }
  }

  void setState(String state) {
    _state = state;
    notify();
  }
}

void main() {
  var subject = Subject();

  var observer1 = ConcreteObserver('Observer 1');
  var observer2 = ConcreteObserver('Observer 2');

  subject.attach(observer1);
  subject.attach(observer2);

  subject.setState('State 1');
  subject.setState('State 2');
}

Explicació

  • Observer: Defineix una interfície per a objectes que han de ser notificats de canvis en un Subject.
  • ConcreteObserver: Implementa la interfície Observer i actualitza el seu estat en resposta a notificacions.
  • Subject: Manté una llista d'observers i els notifica quan el seu estat canvia.

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 Strategy en Dart

abstract class Strategy {
  void execute();
}

class ConcreteStrategyA implements Strategy {
  @override
  void execute() {
    print('Executing strategy A');
  }
}

class ConcreteStrategyB implements Strategy {
  @override
  void execute() {
    print('Executing strategy B');
  }
}

class Context {
  Strategy _strategy;

  Context(this._strategy);

  void setStrategy(Strategy strategy) {
    _strategy = strategy;
  }

  void executeStrategy() {
    _strategy.execute();
  }
}

void main() {
  var context = Context(ConcreteStrategyA());
  context.executeStrategy();

  context.setStrategy(ConcreteStrategyB());
  context.executeStrategy();
}

Explicació

  • Strategy: Defineix una interfície comuna per a tots els algoritmes.
  • ConcreteStrategyA i ConcreteStrategyB: Implementen la interfície Strategy amb diferents algoritmes.
  • Context: Manté una referència a un objecte Strategy i delega l'execució de l'algoritme a aquest objecte.

Exercicis Pràctics

  1. Implementa un Singleton: Crea una classe Logger que només permeti una instància i tingui un mètode per registrar missatges.
  2. Crea un Adapter: Implementa un adaptador per a una classe que té una interfície incompatible amb una interfície esperada.
  3. Utilitza el patró Observer: Crea un sistema de notificacions on diversos observadors reben actualitzacions quan l'estat d'un subjecte canvia.

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 i com implementar-los en Dart. Practicar aquests patrons t'ajudarà a escriure codi més net, mantenible i escalable.

© Copyright 2024. Tots els drets reservats