Patrones de diseño
Los patrones de diseño son soluciones comunes y probadas a problemas comunes.
Se dividen en 3 categorias:
-
Creacionales: Se encargan de la creación de objetos, ayudan a crear objetos de manera eficiente y flexible, ocultando los detalles de instanciacion. Ejemplos: Singleton, Factory, Builder, Prototype, Abstract Factory.
-
Estructurales: Se encargan de la composición de clases y objetos, explican cómo ensamblar objetos y clases en estructuras más grandes, a la vez que se mantiene la flexibilidad y eficiencia de estas estructuras. Ejemplos: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.
-
Comportamiento: Se encargan de la interacción entre objetos, definen como los objetos interactuan y se comunican para cumplir tareas complejas. Ejemplos: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
Patrones Creacionales
Patron Singleton
El patrón Singleton es un patrón de diseño creacional que garantiza que solo haya una instancia de una clase y proporciona un punto de acceso global a ella.
public class Singleton {
private static Singleton instance;
private Singleton() {
// Constructor privado para evitar la creación de instancias desde fuera de la clase
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Patron Factory
El patrón Factory es un patrón de diseño creacional que proporciona una interfaz para crear objetos en una superclase, pero permite a las subclases alterar el tipo de objetos que se crearán.
Permite crear objetos sin especificar la clase exacta que se creará. En lugar de eso, delegamos
la creación de objetos a subclases o métodos que encapsulan esta lógica.
Es útil cuando una clase no puede anticipar la clase de objetos que debe crear.
public interface Product {
void operation();
}
public class ProductA implements Product {
@Override
public void operation() {
System.out.println("ProductA operation");
}
}
public class ProductB implements Product {
@Override
public void operation() {
System.out.println("ProductB operation");
}
}
public abstract class Factory {
abstract Product factoryMethod();
public void someOperation() {
Product product = this.factoryMethod();
product.operation();
}
}
public class FactoryA extends Factory {
@Override
public Product factoryMethod() {
return new ProductA();
}
}
public class FactoryB extends Factory {
@Override
public Product factoryMethod() {
return new ProductB();
}
}
public class Main {
public static void main(String[] args) {
Factory factory;
if (true) {
factory = new FactoryA();
} else {
factory = new FactoryB();
}
factory.someOperation(); // ProductA operation
}
}
Patron Abstract Factory
El patrón Abstract Factory es una versión más compleja del patrón Factory que permite crear familias de objetos relacionados sin especificar sus clases concretas. Agrega un nivel mas de abstraccion al patron Factory.
En lugar de crear objetos individuales directamente, creamos fabricas que producen un conjunto de objetos relacionados.
Es útil cuando necesitas crear objetos que son parte de una familia y quieres asegurarte de que de que estos objetos se complementen entre si.
public interface Vehicule {
void operation();
}
public class VehiculeA implements Vehicule {
@Override
public void operation() {
System.out.println("VehiculeA operation");
}
}
public class VehiculeB implements Vehicule {
@Override
public void operation() {
System.out.println("VehiculeB operation");
}
}
public interface Engine {
void operation();
}
public class EngineA implements Engine {
@Override
public void operation() {
System.out.println("EngineA operation");
}
}
public class EngineB implements Engine {
@Override
public void operation() {
System.out.println("EngineB operation");
}
}
public interface VehiculeFactory {
Vehicule createVehicule();
Engine createEngine();
}
public class VehiculeAFactory implements VehiculeFactory {
@Override
public Vehicule createVehicule() {
return new VehiculeA();
}
@Override
public Engine createEngine() {
return new EngineA();
}
}
public class VehiculeBFactory implements VehiculeFactory {
@Override
public Vehicule createVehicule() {
return new VehiculeB();
}
@Override
public Engine createEngine() {
return new EngineB();
}
}
public class VehiculeFactoryImpl {
VehiculeFactory vehiculeFactory;
public void generateFactory(VehiculeFactory vehiculeFactory) {
this.vehiculeFactory = vehiculeFactory;
Vehicule vehicule = vehiculeFactory.createVehicule();
Engine engine = vehiculeFactory.createEngine();
vehicule.assemble();
engine.start();
}
}
public class Main {
public static void main(String[] args) {
VehiculeFactoryImpl vehiculeFactory = new VehiculeFactoryImpl();
if (true) {
vehiculeFactory.generateFactory(new VehiculeAFactory());
} else {
vehiculeFactory.generateFactory(new VehiculeBFactory());
}
// VehiculeA operation
// EngineA operation
}
}
Patron Builder
El patrón Builder es un patrón de diseño creacional que permite construir objetos complejos paso a paso. El patrón nos permite producir distintos tipos y representaciones de un objeto empleando el mismo código de construcción. El patrón Builder separa la construcción de un objeto complejo de su representación, de modo que el mismo proceso de construcción pueda crear diferentes representaciones.
Se puede crear el patron Builder con Lombok para Java con el @Builder.
public class Product {
private String attribute1;
private String attribute2;
public void setAttribute1(String attribute1) {
this.attribute1 = attribute1;
}
public void setAttribute2(String attribute2) {
this.attribute2 = attribute2;
}
}
public interface Builder {
Product build();
Builder builder();
Builder buildAttribute1(String text);
Builder buildAttribute2(String text);
}
public class ProductBuilder implements Builder {
private Product product;
@Override
public Builder builder() {
this.product = new Product();
return this;
}
@Override
public void buildAttribute1(String text) {
product.setAttribute1(text);
return this;
}
@Override
public void buildAttribute2(String text()) {
product.setAttribute2(text);
return this;
}
@Override
public Product build() {
return this.product;
}
}
public class Main {
public static void main(String[] args) {
Builder productBuilder = new ProductBuilder();
Product product = productBuilder.builder()
.buildAttribute1("Attribute 1")
.buildAttribute2("Attribute 2")
.build();
}
}
Patron Prototype
El patrón Prototype es un patrón de diseño creacional que permite copiar objetos existentes sin hacer que el código dependa de sus clases.
Es util cuando queremos duplicar cualquier objeto complejo por ejemplo el contenido, el titulo y el autor de un documento.
public interface Prototype {
Prototype clone();
}
public class ConcretePrototypeA implements Prototype {
@Override
public Prototype clone() {
return new ConcretePrototypeA();
}
}
public class ConcretePrototypeB implements Prototype {
@Override
public Prototype clone() {
return new ConcretePrototypeB();
}
}
public class Main {
public static void main(String[] args) {
Prototype prototypeA = new ConcretePrototypeA();
Prototype prototypeAClone = prototypeA.clone();
Prototype prototypeB = new ConcretePrototypeB();
Prototype prototypeBClone = prototypeB.clone();
}
}
Patrones Estructurales
Patron Adapter
El patrón Adapter es un patrón de diseño estructural que permite a objetos con interfaces incompatibles colaborar entre sí. Es muy util para utilizar librerias de terceros en nuestra aplicación sin depender directamente de ellas, es decir, ayuda a crear una capa de abstracción en medio y no tener que modificar nuestro código si.
public interface Target {
void request();
}
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specificRequest");
}
}
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
public class Main {
public static void main(String[] args) {
Target target = new Adapter(new Adaptee());
target.request();
}
}
Patron Bridge
El patrón Bridge es un patrón de diseño estructural que permite desacoplar una abstracción de su implementación, de modo que ambas puedan variar de forma independiente. Permite dividir una clase grande, o un grupo de clases estrechamente relacionadas, en dos jerarquías separadas (abstracción e implementación) que pueden desarrollarse independientemente la una de la otra.
Es util cuando se tienen muchas implementaciones de una abstracción. Se puede utilizar para separar la lógica de negocio de la lógica de presentación.
public interface Implementor {
void operation();
}
public class ConcreteImplementorA implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorA operation");
}
}
public class ConcreteImplementorB implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorB operation");
}
}
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
public class ConcreteAbstractionA extends Abstraction {
public ConcreteAbstractionA(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("ConcreteAbstractionA operation");
this.implementor.operation();
}
}
public class ConcreteAbstractionB extends Abstraction {
public ConcreteAbstractionB(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("ConcreteAbstractionB operation");
this.implementor.operation();
}
}
public class Main {
public static void main(String[] args) {
Abstraction abstractionA = new ConcreteAbstractionA(new ConcreteImplementorA());
abstractionA.operation(); // ConcreteAbstractionA operation
// ConcreteImplementorA operation
abstractionA = new ConcreteAbstractionA(new ConcreteImplementorB());
abstractionA.operation(); // ConcreteAbstractionA operation
// ConcreteImplementorB operation
Abstraction abstractionB = new ConcreteAbstractionB(new ConcreteImplementorB());
abstractionB.operation(); // ConcreteAbstractionB operation
// ConcreteImplementorB operation
}
}
Patron Composite
El patrón Composite es un patrón de diseño estructural, permite componer objetos en estructuras de árbol para representar jerarquías y trabajar con esas estructuras como si fueran objetos individuales. El patrón permite a los clientes tratar los objetos individuales y los compuestos de la misma manera.
Esto simplifica el tratamiento de los objetos creados, ya que al poseer todos ellos una interfaz común, se tratan todos de la misma manera. Dependiendo de la implementación, pueden aplicarse procedimientos al total o una de las partes de la estructura compuesta como si de un nodo final se tratara, aunque dicha parte esté compuesta a su vez de muchas otras.
public interface ComponentComposite {
void operation();
}
public class Leaf implements ComponentComposite {
@Override
public void operation() {
System.out.println("Leaf operation");
}
}
public class Composite implements ComponentComposite {
private List<ComponentComposite> components = new ArrayList<>();
public void addComponent(ComponentComposite component) {
components.add(component);
}
public void removeComponent(ComponentComposite component) {
components.remove(component);
}
public ComponentComposite getComponent(int index) {
return components.get(index);
}
@Override
public void operation() {
System.out.println("Composite operation");
for (ComponentComposite component : components) {
component.operation();
}
}
}
public class Main {
public static void main(String[] args) {
ComponentComposite leaf = new Leaf();
leaf.operation(); // Leaf operation
Composite composite = new Composite();
composite.addComponent(leaf);
composite.operation(); // Composite operation
// Leaf operation
}
}
Patron Decorator
El patrón Decorator es un patrón de diseño estructural que permite añadir funcionalidades a un objeto
dinámicamente. Es una alternativa flexible a la herencia para extender la funcionalidad de una clase.
Permite añadir funcionalidades a objetos colocando estos objetos dentro de objetos encapsuladores
que contienen estas funcionalidades.
Es util cuando se necesitan añadir funcionalidades a un objeto de forma dinámica y sin afectar a otros
objetos de la misma clase.
public interface Component {
void operation();
}
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
}
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
System.out.println("ConcreteDecoratorA operation");
this.component.operation();
}
}
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
System.out.println("ConcreteDecoratorB operation");
this.component.operation();
}
}
public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.operation(); // ConcreteComponent operation
Component decoratorA = new ConcreteDecoratorA(component);
decoratorA.operation(); // ConcreteDecoratorA operation
// ConcreteComponent operation
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation(); // ConcreteDecoratorB operation
// ConcreteDecoratorA operation
// ConcreteComponent operation
}
}
Patron Facade
El patrón Facade es un patrón de diseño estructural que proporciona una interfaz simplificada a una biblioteca,
un framework o cualquier conjunto complejo de clases.
Define una interfaz de nivel superior que hace que el subsistema sea más fácil de usar.
Es util cuando se tiene un subsistema complejo y se quiere proporcionar una interfaz simple para
interactuar con él a traves del cliente.
public class SubsystemA {
public void operation1() {
System.out.println("SubsystemA operation1");
}
public void operation2() {
System.out.println("SubsystemA operation2");
}
}
public class SubsystemB {
public void operation1() {
System.out.println("SubsystemB operation1");
}
public void operation2() {
System.out.println("SubsystemB operation2");
}
}
public class ExternalFacade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
public ExternalFacade(SubsystemA subsystemA, SubsystemB subsystemB) {
this.subsystemA = subsystemA;
this.subsystemB = subsystemB;
}
public void operation1() {
subsystemA.operation1();
subsystemB.operation1();
}
public void operation2() {
subsystemA.operation2();
subsystemB.operation2();
}
}
public class Main {
public static void main(String[] args) {
SubsystemA subsystemA = new SubsystemA();
SubsystemB subsystemB = new SubsystemB();
ExternalFacade externalFacade = new ExternalFacade(subsystemA, subsystemB);
externalFacade.operation1(); // SubsystemA operation1
// SubsystemB operation1
externalFacade.operation2(); // SubsystemA operation2
// SubsystemB operation2
}
}
Patron Flyweight
El patrón Flyweight es un patrón de diseño estructural que permite mantener más objetos dentro de la cantidad disponible de RAM compartiendo las partes comunes del estado entre varios objetos en lugar de mantener toda la información en cada objeto.
Es util cuando se necesitan crear muchas instancias de un objeto y se quiere ahorrar memoria compartiendo instancias similares.
public class Flyweight {
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
System.out.println("Flyweight operation: " + intrinsicState + " " + extrinsicState);
}
}
public class Main {
public static void main(String[] args) {
Flyweight flyweight = new Flyweight("intrinsicState");
flyweight.operation("extrinsicState");
}
}
Patron Proxy
Es un patrón de diseño estructural que te permite proporcionar un sustituto o marcador de posición para otro objeto. Un proxy controla el acceso al objeto original, permitiéndote hacer algo antes o después de que la solicitud llegue al objeto original. Crea un objeto que actúa como intermediario entre el cliente y el objeto real, lo que permite agregar funcionalidad adicional al objeto real sin cambiar su código.
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject request");
}
}
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (this.realSubject == null) {
this.realSubject = new RealSubject();
}
realSubject.request();
}
}
public class Main {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request(); // RealSubject request
}
}
Patrones Comportamiento
Patron Chain of Responsibility
El patrón Chain of Responsibility es un patrón de diseño de comportamiento que permite pasar solicitudes a lo largo de una cadena de manejadores. Al recibir una solicitud, cada manejador decide si la procesa o si la pasa al siguiente manejador de la cadena. Es util cuando se necesita procesar datos en una secuencia y de diferentes maneras, pero no se sabe de antemano qué tipo de procesamiento necesita o en que orden.
public abstract class Handler {
protected Handler successor;
public Handler setSuccessor(Handler successor) {
this.successor = successor;
return this.successor;
}
public abstract void handleRequest(Request request);
}
public class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("ConcreteHandlerA handleRequest");
} else if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("No handler found");
}
}
}
public class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("ConcreteHandlerB handleRequest");
} else if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("No handler found");
}
}
}
public class ConcreteHandlerC extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("C")) {
System.out.println("ConcreteHandlerC handleRequest");
} else if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("No handler found");
}
}
}
public class Main {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
Handler handlerC = new ConcreteHandlerC();
handlerA.setSuccessor(handlerB).setSuccessor(handlerC);
handlerA.handleRequest("B"); // ConcreteHandlerB handleRequest
handlerA.handleRequest("C"); // ConcreteHandlerC handleRequest
handlerA.handleRequest("D"); // No handler found
}
}
Patron Command
El patrón Command es un patrón de diseño de comportamiento que convierte una solicitud en un objeto
independiente que contiene toda la información sobre la solicitud. Esta transformación te permite
parametrizar los métodos con diferentes solicitudes, retrasar o poner en cola la ejecución de una
solicitud y soportar operaciones que no se pueden realizar.
Es util cuando se necesita desacoplar el objeto que invoca la operacion del objeto que sabe como realizarla.
public interface Command {
void execute();
}
public class ConcreteCommandA implements Command {
@Override
public void execute() {
System.out.println("ConcreteCommandA execute");
}
}
public class ConcreteCommandB implements Command {
@Override
public void execute() {
System.out.println("ConcreteCommandB execute");
}
}
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
public class Main {
public static void main(String[] args) {
Invoker invoker = new Invoker();
invoker.setCommand(new ConcreteCommandA());
invoker.executeCommand(); // ConcreteCommandA execute
invoker.setCommand(new ConcreteCommandB());
invoker.executeCommand(); // ConcreteCommandB execute
}
}
Patron Observer
El patrón Observer es un patrón de diseño de comportamiento que permite a un objeto, llamado sujeto, notificar a otros objetos, llamados observadores, cuando cambia su estado.
public abstract class Subject {
protected List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
};
public void removeObserver(Observer observer) {
observers.remove(observer);
};
public void notifyObservers() {
for (Observer obs : this.observers) {
obs.update(this);
}
};
}
public interface Observer {
void update(Subject subject);
}
public class ConcreteSubject extends Subject {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
this.notifyObservers();
}
}
public class ConcreteObserverA implements Observer {
@Override
public void update(Subject subject) {
System.out.println("ConcreteObserverA update");
}
}
public class ConcreteObserverB implements Observer {
@Override
public void update(Subject subject) {
System.out.println("ConcreteObserverB update");
}
}
public class Main {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.addObserver(new ConcreteObserverA());
subject.addObserver(new ConcreteObserverB());
subject.setState("state");
}
}
Patron Strategy
El patrón Strategy es un patrón de diseño de comportamiento que permite tener multiples metodos
para resolver un mismo problema y se elige dinamicamente segun el contexto.
Es util cuando se tienen multiples algoritmos que se pueden utilizar para resolver un problema y se
quiere elegir el algoritmo en tiempo de ejecucion.
El patron Strategy se compone de 3 partes:
- Strategy: Interfaz que define el algoritmo a utilizar.
- ConcreteStrategy: Implementacion concreta de la interfaz Strategy.
- Context: Clase que utiliza la estrategia para ejecutar el algoritmo.
public interface Strategy {
void algorithm();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("ConcreteStrategyA algorithm");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
System.out.println("ConcreteStrategyB algorithm");
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
this.strategy.algorithm();
}
}
public class Main {
public static void main(String[] args) {
String algorithm = "A"; // Obtener el algoritmo a utilizar
Strategy strategy;
if (algorithm.equals("A")) {
strategy = new ConcreteStrategyA();
} else {
strategy = new ConcreteStrategyB();
}
Context context = new Context(strategy);
context.execute(); // ConcreteStrategyA algorithm
}
}