translation: Translated missing behavioral patterns category to Spanish (Task of issue #2277) (#2895)
* Translate behavioral patterns to Spanish * Translate behavioral patterns to Spanish * Translate behavioral patterns to Spanish * Update bytecode.urm.png image in spanish translation * Update translations --------- Co-authored-by: luis.hincapie <luis.hincapie@blankfactor.com>
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: Bytecode
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Game programming
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Permite codificar el comportamiento como instrucciones para una máquina virtual.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Un equipo está trabajando en un nuevo juego en el que los magos luchan entre sí. El comportamiento de los magos necesita ser cuidadosamente ajustado e iterado cientos de veces a través de pruebas de juego. No es óptimo pedir al programador que haga cambios cada vez que el diseñador del juego quiere variar el comportamiento, así que el comportamiento del mago se implementa como una máquina virtual basada en datos.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón Bytecode permite un comportamiento dirigido por datos en lugar de por código.
|
||||
|
||||
[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) indica la documentación:
|
||||
|
||||
> Un conjunto de instrucciones define las operaciones de bajo nivel que pueden realizarse. Una serie de instrucciones se codifica como una secuencia de bytes. Una máquina virtual ejecuta estas instrucciones de una en una, utilizando una pila para los valores intermedios. La combinación de instrucciones permite definir comportamientos complejos de alto nivel.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Uno de los objetos más importantes del juego es la clase Mago `Wizard`.
|
||||
|
||||
```java
|
||||
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class Wizard {
|
||||
|
||||
private int health;
|
||||
private int agility;
|
||||
private int wisdom;
|
||||
private int numberOfPlayedSounds;
|
||||
private int numberOfSpawnedParticles;
|
||||
|
||||
public void playSound() {
|
||||
LOGGER.info("Playing sound");
|
||||
numberOfPlayedSounds++;
|
||||
}
|
||||
|
||||
public void spawnParticles() {
|
||||
LOGGER.info("Spawning particles");
|
||||
numberOfSpawnedParticles++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A continuación, mostramos las instrucciones disponibles para nuestra máquina virtual. Cada una de las instrucciones tiene su propia semántica sobre cómo opera con los datos de la pila. Por ejemplo, la instrucción ADD toma los dos elementos superiores de la pila, los suma y coloca el resultado en la pila.
|
||||
|
||||
```java
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Instruction {
|
||||
|
||||
LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
|
||||
SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
|
||||
SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
|
||||
SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
|
||||
PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
|
||||
SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
|
||||
GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
|
||||
GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
|
||||
GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
|
||||
ADD(10), // e.g. "ADD", pop 2 values, push their sum
|
||||
DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
En el corazón de nuestro ejemplo está la clase `VirtualMachine`. Toma instrucciones como entrada y las ejecuta para proporcionar el comportamiento del objeto de juego.
|
||||
|
||||
```java
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class VirtualMachine {
|
||||
|
||||
private final Stack<Integer> stack = new Stack<>();
|
||||
|
||||
private final Wizard[] wizards = new Wizard[2];
|
||||
|
||||
public VirtualMachine() {
|
||||
wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
|
||||
0, 0);
|
||||
wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
|
||||
0, 0);
|
||||
}
|
||||
|
||||
public VirtualMachine(Wizard wizard1, Wizard wizard2) {
|
||||
wizards[0] = wizard1;
|
||||
wizards[1] = wizard2;
|
||||
}
|
||||
|
||||
public void execute(int[] bytecode) {
|
||||
for (var i = 0; i < bytecode.length; i++) {
|
||||
Instruction instruction = Instruction.getInstruction(bytecode[i]);
|
||||
switch (instruction) {
|
||||
case LITERAL:
|
||||
// Read the next byte from the bytecode.
|
||||
int value = bytecode[++i];
|
||||
// Push the next value to stack
|
||||
stack.push(value);
|
||||
break;
|
||||
case SET_AGILITY:
|
||||
var amount = stack.pop();
|
||||
var wizard = stack.pop();
|
||||
setAgility(wizard, amount);
|
||||
break;
|
||||
case SET_WISDOM:
|
||||
amount = stack.pop();
|
||||
wizard = stack.pop();
|
||||
setWisdom(wizard, amount);
|
||||
break;
|
||||
case SET_HEALTH:
|
||||
amount = stack.pop();
|
||||
wizard = stack.pop();
|
||||
setHealth(wizard, amount);
|
||||
break;
|
||||
case GET_HEALTH:
|
||||
wizard = stack.pop();
|
||||
stack.push(getHealth(wizard));
|
||||
break;
|
||||
case GET_AGILITY:
|
||||
wizard = stack.pop();
|
||||
stack.push(getAgility(wizard));
|
||||
break;
|
||||
case GET_WISDOM:
|
||||
wizard = stack.pop();
|
||||
stack.push(getWisdom(wizard));
|
||||
break;
|
||||
case ADD:
|
||||
var a = stack.pop();
|
||||
var b = stack.pop();
|
||||
stack.push(a + b);
|
||||
break;
|
||||
case DIVIDE:
|
||||
a = stack.pop();
|
||||
b = stack.pop();
|
||||
stack.push(b / a);
|
||||
break;
|
||||
case PLAY_SOUND:
|
||||
wizard = stack.pop();
|
||||
getWizards()[wizard].playSound();
|
||||
break;
|
||||
case SPAWN_PARTICLES:
|
||||
wizard = stack.pop();
|
||||
getWizards()[wizard].spawnParticles();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid instruction value");
|
||||
}
|
||||
LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
|
||||
}
|
||||
}
|
||||
|
||||
public void setHealth(int wizard, int amount) {
|
||||
wizards[wizard].setHealth(amount);
|
||||
}
|
||||
// other setters ->
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Ahora podemos mostrar el ejemplo completo utilizando la máquina virtual.
|
||||
|
||||
```java
|
||||
public static void main(String[]args){
|
||||
|
||||
var vm=new VirtualMachine(
|
||||
new Wizard(45,7,11,0,0),
|
||||
new Wizard(36,18,8,0,0));
|
||||
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));
|
||||
vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));
|
||||
}
|
||||
```
|
||||
|
||||
Aquí está la salida de la consola.
|
||||
|
||||
```
|
||||
16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0]
|
||||
16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18]
|
||||
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2]
|
||||
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9]
|
||||
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54]
|
||||
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains []
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utiliza el patrón Bytecode cuando tengas que definir muchos comportamientos y el lenguaje de implementación de tu juego no sea el adecuado porque:
|
||||
|
||||
* Es demasiado de bajo nivel, por lo que es tedioso o propenso a errores para programar.
|
||||
* Iterar en él lleva demasiado tiempo debido a tiempos de compilación lentos u otros problemas de herramientas.
|
||||
* Tiene demasiada confianza. Si quieres asegurarte de que el comportamiento definido no puede romper el juego, necesitas separarlo del resto del código base.
|
||||
|
||||
## Usos Conocidos
|
||||
|
||||
* Java Virtual Machine (JVM) utiliza bytecode para permitir que los programas Java se ejecuten en cualquier dispositivo que tenga JVM instalado.
|
||||
* Python compila sus scripts a bytecode que luego es interpretado por la Máquina Virtual Python.
|
||||
* NET Framework utiliza una forma de bytecode llamada Microsoft Intermediate Language (MSIL).
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Ventajas:
|
||||
|
||||
* Portabilidad: Los programas pueden ejecutarse en cualquier plataforma que disponga de una máquina virtual compatible.
|
||||
* Seguridad: La máquina virtual puede aplicar controles de seguridad al código de bytes.
|
||||
* Rendimiento: Los compiladores JIT pueden optimizar el código de bytes en tiempo de ejecución, mejorando potencialmente el rendimiento respecto al código interpretado.
|
||||
|
||||
Desventajas:
|
||||
|
||||
* Sobrecarga: Ejecutar bytecode normalmente implica más sobrecarga que ejecutar código nativo, lo que puede afectar al rendimiento.
|
||||
* Complejidad: Implementar y mantener una máquina virtual añade complejidad al sistema.
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
* [Intérprete](https://java-design-patterns.com/patterns/interpreter/) se utiliza a menudo dentro de la implementación de VMs para interpretar instrucciones bytecode.
|
||||
* [Comando](https://java-design-patterns.com/patterns/command/): Las instrucciones bytecode pueden ser vistas como comandos ejecutados por la VM.
|
||||
* [Método de fábrica](https://java-design-patterns.com/patterns/factory-method/): Las VMs pueden utilizar métodos de fábrica para instanciar operaciones o instrucciones definidas en el bytecode.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Game programming patterns](http://gameprogrammingpatterns.com/bytecode.html)
|
||||
* [Programming Language Pragmatics](https://amzn.to/49Tusnn)
|
||||
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,200 @@
|
||||
---
|
||||
title: Chain of responsibility
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Gang of Four
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
* Chain of Command
|
||||
* Chain of Objects
|
||||
* Responsibility Chain
|
||||
|
||||
## Propósito
|
||||
|
||||
Evita acoplar el emisor de una petición a su receptor dando a más de un objeto la oportunidad de gestionar la petición. Encadena los objetos receptores y pasa la solicitud a lo largo de la cadena hasta que un objeto la gestione.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> El Rey Orco da órdenes en voz alta a su ejército. El más cercano a reaccionar es el comandante, luego un oficial y después un soldado. El comandante, el oficial y el soldado forman una cadena de responsabilidad.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> Ayuda a construir una cadena de objetos. Una solicitud entra por un extremo y sigue pasando de un objeto a otro hasta que encuentra un gestor adecuado.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en una fuente de objetos de comando y una serie de objetos de procesamiento. Cada objeto de procesamiento contiene lógica que define los tipos de objetos de comando que puede manejar; el resto se pasa al siguiente objeto de procesamiento de la cadena.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Traduciendo nuestro ejemplo con los orcos de arriba. Primero, tenemos la clase `Request`:
|
||||
|
||||
```java
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Request {
|
||||
|
||||
private final RequestType requestType;
|
||||
private final String requestDescription;
|
||||
private boolean handled;
|
||||
|
||||
public Request(final RequestType requestType, final String requestDescription) {
|
||||
this.requestType = Objects.requireNonNull(requestType);
|
||||
this.requestDescription = Objects.requireNonNull(requestDescription);
|
||||
}
|
||||
|
||||
public void markHandled() {
|
||||
this.handled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getRequestDescription();
|
||||
}
|
||||
}
|
||||
|
||||
public enum RequestType {
|
||||
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
|
||||
}
|
||||
```
|
||||
|
||||
A continuación, mostramos la jerarquía del gestor de peticiones.
|
||||
|
||||
```java
|
||||
public interface RequestHandler {
|
||||
|
||||
boolean canHandleRequest(Request req);
|
||||
|
||||
int getPriority();
|
||||
|
||||
void handle(Request req);
|
||||
|
||||
String name();
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class OrcCommander implements RequestHandler {
|
||||
@Override
|
||||
public boolean canHandleRequest(Request req) {
|
||||
return req.getRequestType() == RequestType.DEFEND_CASTLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Request req) {
|
||||
req.markHandled();
|
||||
LOGGER.info("{} handling request \"{}\"", name(), req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Orc commander";
|
||||
}
|
||||
}
|
||||
|
||||
// OrcOfficer and OrcSoldier are defined similarly as OrcCommander
|
||||
|
||||
```
|
||||
|
||||
El Rey Orco da las órdenes y forma la cadena.
|
||||
|
||||
```java
|
||||
public class OrcKing {
|
||||
|
||||
private List<RequestHandler> handlers;
|
||||
|
||||
public OrcKing() {
|
||||
buildChain();
|
||||
}
|
||||
|
||||
private void buildChain() {
|
||||
handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier());
|
||||
}
|
||||
|
||||
public void makeRequest(Request req) {
|
||||
handlers
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(RequestHandler::getPriority))
|
||||
.filter(handler -> handler.canHandleRequest(req))
|
||||
.findFirst()
|
||||
.ifPresent(handler -> handler.handle(req));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La cadena de responsabilidad en acción.
|
||||
|
||||
```java
|
||||
var king=new OrcKing();
|
||||
king.makeRequest(new Request(RequestType.DEFEND_CASTLE,"defend castle"));
|
||||
king.makeRequest(new Request(RequestType.TORTURE_PRISONER,"torture prisoner"));
|
||||
king.makeRequest(new Request(RequestType.COLLECT_TAX,"collect tax"));
|
||||
```
|
||||
|
||||
La salida de la consola.
|
||||
|
||||
```
|
||||
Orc commander handling request "defend castle"
|
||||
Orc officer handling request "torture prisoner"
|
||||
Orc soldier handling request "collect tax"
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice Cadena de Responsabilidad cuando
|
||||
|
||||
* Más de un objeto puede gestionar una petición, y el gestor no se conoce a priori. El gestor debe determinarse automáticamente.
|
||||
* Se desea enviar una petición a uno de varios objetos sin especificar explícitamente el receptor.
|
||||
* El conjunto de objetos que pueden gestionar una solicitud debe especificarse dinámicamente.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* Burbujeo de eventos en frameworks GUI donde un evento puede ser manejado en múltiples niveles de la jerarquía de un componente UI.
|
||||
* Frameworks de middleware en los que una petición pasa a través de una cadena de objetos de procesamiento.
|
||||
* Marcos de trabajo de registro donde los mensajes pueden pasar a través de una serie de registradores, cada uno posiblemente manejándolos de manera diferente.
|
||||
* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29)
|
||||
* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html)
|
||||
* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-)
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Ventajas:
|
||||
|
||||
* Acoplamiento reducido. El emisor de una petición no necesita conocer el manejador concreto que procesará la petición.
|
||||
* Mayor flexibilidad a la hora de asignar responsabilidades a los objetos. Se pueden añadir o cambiar responsabilidades para gestionar una petición cambiando los miembros y el orden de la cadena.
|
||||
* Permite establecer un gestor por defecto si no hay ningún gestor concreto que pueda gestionar la solicitud.
|
||||
|
||||
Desventajas:
|
||||
|
||||
* Puede ser difícil depurar y entender el flujo, especialmente si la cadena es larga y compleja.
|
||||
* La petición puede quedar sin gestionar si la cadena no incluye un gestor "catch-all".
|
||||
* Pueden surgir problemas de rendimiento debido a la posibilidad de pasar por varios gestores antes de encontrar el correcto, o no encontrarlo en absoluto.
|
||||
|
||||
## Patrones Relacionados
|
||||
|
||||
* [Comando](https://java-design-patterns.com/patterns/command/): puede ser usado para encapsular una petición como un objeto, que puede ser pasado a lo largo de la cadena.
|
||||
* [Composite](https://java-design-patterns.com/patterns/composite/): la Cadena de Responsabilidad se aplica a menudo junto con el patrón Composite.
|
||||
* [Decorator](https://java-design-patterns.com/patterns/decorator/): los decoradores pueden encadenarse de forma similar a las responsabilidades en el patrón Cadena de responsabilidad.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5)
|
||||
* [Refactoring to Patterns](https://amzn.to/3VOO4F5)
|
||||
* [Pattern languages of program design 3](https://amzn.to/4a4NxTH)
|
||||
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Client Session
|
||||
category: Behavioral
|
||||
language: es
|
||||
tags:
|
||||
- Session management
|
||||
- Web development
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
* User session
|
||||
|
||||
## Propósito
|
||||
|
||||
El patrón de diseño Client Session tiene como objetivo mantener el estado y los datos de un usuario a través de múltiples peticiones dentro de una aplicación web, asegurando una experiencia de usuario continua y personalizada.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Quieres crear una aplicación de gestión de datos que permita a los usuarios enviar peticiones al servidor para modificar y realizar cambios en los datos almacenados en sus dispositivos. Estas peticiones son pequeñas y los datos son individuales para cada usuario, negando la necesidad de una implementación de base de datos a gran escala. Utilizando el patrón de sesión de cliente, se pueden gestionar múltiples peticiones simultáneas, equilibrando la carga de clientes entre diferentes servidores con facilidad debido a que los servidores permanecen sin estado. También se elimina la necesidad de almacenar identificadores de sesión en el lado del servidor debido a que los clientes proporcionan toda la información que un servidor necesita para realizar su proceso.
|
||||
|
||||
En pocas palabras
|
||||
|
||||
> En lugar de almacenar información sobre el cliente actual y la información a la que se está accediendo en el servidor, se mantiene sólo en el lado del cliente. El cliente tiene que enviar datos de sesión con cada solicitud al servidor y tiene que enviar un estado actualizado de vuelta al cliente, que se almacena en la máquina del cliente. El servidor no tiene que almacenar la información del cliente. ([ref](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client))
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Aquí está el código de ejemplo para describir el patrón cliente-sesión. En el siguiente código estamos creando primero una instancia del Servidor. Esta instancia del servidor se utilizará entonces para obtener objetos Session para dos clientes. Como puedes ver en el siguiente código, el objeto Session puede ser utilizado para almacenar cualquier información relevante que sea requerida por el servidor para procesar la petición del cliente. Estos objetos Session serán pasados con cada Request al servidor. La solicitud tendrá el objeto Session que almacena los detalles relevantes del cliente junto con los datos requeridos para procesar la solicitud. La información de sesión en cada solicitud ayuda al servidor a identificar al cliente y procesar la solicitud en consecuencia.
|
||||
|
||||
```java
|
||||
public class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
var server = new Server("localhost", 8080);
|
||||
var session1 = server.getSession("Session1");
|
||||
var session2 = server.getSession("Session2");
|
||||
var request1 = new Request("Data1", session1);
|
||||
var request2 = new Request("Data2", session2);
|
||||
server.process(request1);
|
||||
server.process(request2);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class Session {
|
||||
|
||||
/**
|
||||
* Session id.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Client name.
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class Request {
|
||||
|
||||
private String data;
|
||||
|
||||
private Session session;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Diagrama de arquitectura
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón de estado del cliente cuando:
|
||||
|
||||
* Aplicaciones web que requieran autenticación y autorización del usuario.
|
||||
* Aplicaciones que necesiten realizar un seguimiento de las actividades y preferencias del usuario a lo largo de múltiples peticiones o visitas.
|
||||
* Sistemas donde los recursos del servidor necesitan ser optimizados descargando la gestión del estado al lado del cliente.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* Sitios web de comercio electrónico para rastrear el contenido de la cesta de la compra a lo largo de las sesiones.
|
||||
* Plataformas en línea que ofrecen contenidos personalizados basados en las preferencias y el historial del usuario.
|
||||
* Aplicaciones web que requieren el inicio de sesión del usuario para acceder a contenidos personalizados o seguros.
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Beneficios:
|
||||
|
||||
* Mejora del rendimiento del servidor al reducir la necesidad de almacenar el estado del usuario en el servidor.
|
||||
* Mejora de la experiencia del usuario a través de contenidos personalizados y navegación fluida a través de las diferentes partes de la aplicación.
|
||||
* Flexibilidad en la gestión de sesiones a través de varios mecanismos de almacenamiento del lado del cliente (por ejemplo, cookies, Web Storage API).
|
||||
|
||||
Desventajas:
|
||||
|
||||
* Riesgos potenciales de seguridad si se almacena información sensible en las sesiones del cliente sin el cifrado y la validación adecuados.
|
||||
* Dependencia de las capacidades y ajustes del cliente, como las políticas de cookies, que pueden variar según el navegador y la configuración del usuario.
|
||||
* Mayor complejidad en la lógica de gestión de sesiones, especialmente en la gestión de la caducidad, renovación y sincronización de sesiones en varios dispositivos o pestañas.
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
* Sesión Servidor: A menudo se utiliza junto con el patrón Client Session para proporcionar un equilibrio entre la eficiencia del lado del cliente y el control del lado del servidor.
|
||||
* [Singleton](https://java-design-patterns.com/patterns/singleton/): Asegurar una única instancia de la sesión de un usuario en toda la aplicación.
|
||||
* [Estado](https://java-design-patterns.com/patterns/state/): Gestionar las transiciones de estado en una sesión, como los estados autenticado, invitado o caducado.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [DZone - Practical PHP patterns](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client)
|
||||
* [Client Session State Design Pattern - Ram N Java](https://www.youtube.com/watch?v=ycOSj9g41pc)
|
||||
* [Professional Java for Web Applications](https://amzn.to/4aazY59)
|
||||
* [Securing Web Applications with Spring Security](https://amzn.to/3PCCEA1)
|
||||
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,219 @@
|
||||
---
|
||||
title: Collecting Parameter
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Accumulation
|
||||
- Generic
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
* Collector
|
||||
* Accumulator
|
||||
|
||||
## Propósito
|
||||
|
||||
Su objetivo es simplificar los métodos que recopilan información pasando un único objeto de colección a través de varias llamadas a métodos, permitiéndoles añadir resultados a esta colección en lugar de que cada método cree su propia colección.
|
||||
|
||||
## Explicación
|
||||
|
||||
### Ejemplo del mundo real
|
||||
|
||||
Dentro de un gran edificio corporativo, existe una cola de impresión global que es una colección de todos los trabajos de impresión que están actualmente pendientes. Las diferentes plantas contienen diferentes modelos de impresoras, cada una con una política de impresión diferente. Debemos construir un programa que pueda añadir continuamente trabajos de impresión apropiados a una colección, que se llama el *parámetro de recogida*.
|
||||
|
||||
### En palabras sencillas
|
||||
|
||||
En lugar de tener un método gigante que contenga numerosas políticas para recoger información en una variable, podemos crear numerosas funciones más pequeñas que tomen cada parámetro, y añadan nueva información. Podemos pasar el parámetro a todas estas funciones más pequeñas y al final, tendremos lo que queríamos originalmente. Esta vez, el código es más limpio y fácil de entender. Debido a que la función más grande se ha dividido, el código también es más fácil de modificar ya que los cambios se localizan en las funciones más pequeñas.
|
||||
|
||||
### Wikipedia dice
|
||||
|
||||
En el modismo de Parámetros de Recolección una colección (lista, mapa, etc.) se pasa repetidamente como parámetro a un método que añade elementos a la colección.
|
||||
|
||||
### Ejemplo programático
|
||||
|
||||
Codificando nuestro ejemplo anterior, podemos utilizar la colección `resultado` como parámetro recolector. Se implementan las siguientes restricciones:
|
||||
|
||||
- Si un papel A4 es de color, también debe ser de una sola cara. Se aceptan todos los demás papeles no coloreados.
|
||||
- El papel A3 no debe ser de color y debe ser de una sola cara.
|
||||
- El papel A2 debe ser de una sola página, a una cara y sin colorear.
|
||||
|
||||
```java
|
||||
package com.iluwatar.collectingparameter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
public class App {
|
||||
static PrinterQueue printerQueue = PrinterQueue.getInstance();
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
/*
|
||||
Initialising the printer queue with jobs
|
||||
*/
|
||||
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A4, 5, false, false));
|
||||
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A3, 2, false, false));
|
||||
printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A2, 5, false, false));
|
||||
|
||||
/*
|
||||
This variable is the collecting parameter.
|
||||
*/
|
||||
var result = new LinkedList<PrinterItem>();
|
||||
|
||||
/*
|
||||
* Using numerous sub-methods to collaboratively add information to the result collecting parameter
|
||||
*/
|
||||
addA4Papers(result);
|
||||
addA3Papers(result);
|
||||
addA2Papers(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Utilizamos los métodos `addA4Paper`, `addA3Paper` y `addA2Paper` para rellenar el parámetro de recogida `result` con los trabajos de impresión adecuados según la política descrita anteriormente. Las tres políticas se codifican a continuación,
|
||||
|
||||
```java
|
||||
public class App {
|
||||
static PrinterQueue printerQueue = PrinterQueue.getInstance();
|
||||
|
||||
/**
|
||||
* Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client
|
||||
* (the print center) wants.
|
||||
*
|
||||
* @param printerItemsCollection the collecting parameter
|
||||
*/
|
||||
public static void addA4Papers(Queue<PrinterItem> printerItemsCollection) {
|
||||
/*
|
||||
Iterate through the printer queue, and add A4 papers according to the correct policy to the collecting parameter,
|
||||
which is 'printerItemsCollection' in this case.
|
||||
*/
|
||||
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
|
||||
if (nextItem.paperSize.equals(PaperSizes.A4)) {
|
||||
var isColouredAndSingleSided =
|
||||
nextItem.isColour && !nextItem.isDoubleSided;
|
||||
if (isColouredAndSingleSided) {
|
||||
printerItemsCollection.add(nextItem);
|
||||
} else if (!nextItem.isColour) {
|
||||
printerItemsCollection.add(nextItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds A3 document jobs to the collecting parameter according to some policy that can be whatever the client
|
||||
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
|
||||
* the wants of the client.
|
||||
*
|
||||
* @param printerItemsCollection the collecting parameter
|
||||
*/
|
||||
public static void addA3Papers(Queue<PrinterItem> printerItemsCollection) {
|
||||
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
|
||||
if (nextItem.paperSize.equals(PaperSizes.A3)) {
|
||||
|
||||
// Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at the same time
|
||||
var isNotColouredAndSingleSided =
|
||||
!nextItem.isColour && !nextItem.isDoubleSided;
|
||||
if (isNotColouredAndSingleSided) {
|
||||
printerItemsCollection.add(nextItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds A2 document jobs to the collecting parameter according to some policy that can be whatever the client
|
||||
* (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate
|
||||
* the wants of the client.
|
||||
*
|
||||
* @param printerItemsCollection the collecting parameter
|
||||
*/
|
||||
public static void addA2Papers(Queue<PrinterItem> printerItemsCollection) {
|
||||
for (PrinterItem nextItem : printerQueue.getPrinterQueue()) {
|
||||
if (nextItem.paperSize.equals(PaperSizes.A2)) {
|
||||
|
||||
// Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and non-coloured.
|
||||
var isNotColouredSingleSidedAndOnePage =
|
||||
nextItem.pageCount == 1 &&
|
||||
!nextItem.isDoubleSided
|
||||
&& !nextItem.isColour;
|
||||
if (isNotColouredSingleSidedAndOnePage) {
|
||||
printerItemsCollection.add(nextItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cada método toma como argumento un parámetro de recogida. A continuación, añade elementos, tomados de una variable global, a este parámetro de recogida si cada elemento satisface un criterio determinado. Estos métodos pueden tener la política que desee el cliente.
|
||||
|
||||
En este ejemplo de programación, se añaden tres trabajos de impresión a la cola. Sólo los dos primeros trabajos de impresión deben añadirse al parámetro de recogida según la política. Los elementos de la variable `result` después de la ejecución son,
|
||||
|
||||
| paperSize | pageCount | isDoubleSided | isColour |
|
||||
|-----------|-----------|---------------|----------|
|
||||
| A4 | 5 | false | false |
|
||||
| A3 | 2 | false | false |
|
||||
|
||||
que es lo que esperábamos.
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón de diseño Recopilación de parámetros cuando
|
||||
|
||||
- Cuando múltiples métodos producen una colección de resultados y quieres agregar estos resultados de una manera unificada.
|
||||
- En escenarios donde reducir el número de colecciones creadas por métodos puede mejorar la eficiencia de memoria y el rendimiento.
|
||||
- Al refactorizar métodos grandes que realizan varias tareas, incluida la recopilación de resultados de varias operaciones.
|
||||
|
||||
## Tutoriales
|
||||
|
||||
Los tutoriales para este método se encuentran en:
|
||||
|
||||
- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) por Joshua Kerivsky
|
||||
- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) por Kent Beck
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
Joshua Kerivsky da un ejemplo real en su libro 'Refactoring to Patterns'. Da un ejemplo de uso del patrón de diseño "Collecting Parameter" para crear un método `toString()` para un árbol XML. Sin utilizar este patrón de diseño, esto requeriría una función voluminosa con condicionales y concatenación que empeoraría la legibilidad del código. Un método de este tipo puede dividirse en métodos más pequeños, cada uno de los cuales añade su propio conjunto de información al parámetro de recogida. Véase esto en [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf).
|
||||
|
||||
Otros ejemplos son:
|
||||
|
||||
- Agregar mensajes de error o fallos de validación en un proceso de validación complejo.
|
||||
- Recopilar elementos o información mientras se recorre una estructura de datos compleja.
|
||||
- Refactorización de funcionalidades de informes complejas en las que varias partes de un informe se generan mediante métodos diferentes.
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Ventajas:
|
||||
|
||||
- Reduce la duplicación de código centralizando el manejo de las colecciones en un único lugar.
|
||||
- Mejora la claridad y la capacidad de mantenimiento al hacer explícito dónde y cómo se recogen los resultados.
|
||||
- Mejora el rendimiento al minimizar la creación y gestión de múltiples objetos de recopilación.
|
||||
|
||||
Desventajas:
|
||||
|
||||
- Aumenta el acoplamiento entre el invocador y los métodos invocados, ya que deben ponerse de acuerdo sobre la colección a utilizar.
|
||||
- Puede introducir efectos secundarios en los métodos si no se gestionan con cuidado, ya que los métodos ya no son autónomos en su gestión de resultados.
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
- [Composite](https://java-design-patterns.com/patterns/composite/): Puede utilizarse junto con Collecting Parameter cuando se trabaja con estructuras jerárquicas, permitiendo que los resultados se recojan a través de una estructura compuesta.
|
||||
- [Visitante](https://java-design-patterns.com/patterns/visitor/): A menudo se utiliza conjuntamente, donde Visitor se encarga de recorrer y realizar operaciones en una estructura, y Collecting Parameter acumula los resultados.
|
||||
- [Comando](https://java-design-patterns.com/patterns/command/): Los comandos pueden utilizar el parámetro de recopilación para agregar resultados de varias operaciones ejecutadas por los objetos de comando.
|
||||
|
||||
## Créditos
|
||||
|
||||
- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) by Joshua Kerivsky
|
||||
- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) by Kent Beck
|
||||
- [Wiki](https://wiki.c2.com/?CollectingParameter)
|
||||
- [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB)
|
||||
- [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/4aApLP0)
|
||||
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,243 @@
|
||||
---
|
||||
title: Command
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
* Action
|
||||
* Transaction
|
||||
|
||||
## Propósito
|
||||
|
||||
El patrón de diseño Command encapsula una petición como un objeto, permitiendo así la parametrización de clientes con colas, peticiones y operaciones. También permite soportar operaciones deshechas.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Hay un mago lanzando hechizos sobre un goblin. Los hechizos se ejecutan sobre el duende uno a uno. El primer hechizo encoge al duende y el segundo lo hace invisible. A continuación, el mago invierte los hechizos uno a uno. Cada hechizo es un objeto de comando que se puede deshacer.
|
||||
|
||||
En palabras simples:
|
||||
|
||||
> Almacenar peticiones como objetos de comando permite realizar una acción o deshacerla en un momento posterior.
|
||||
|
||||
Wikipedia dice:
|
||||
|
||||
> En programación orientada a objetos, el patrón de comandos es un patrón de diseño de comportamiento en el que un objeto se utiliza para encapsular toda la información necesaria para realizar una acción o desencadenar un evento en un momento posterior.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Aquí está el código de ejemplo con mago `Wizard` y duende `Goblin`. Empecemos por la clase Mago `Wizard`.
|
||||
|
||||
```java
|
||||
|
||||
@Slf4j
|
||||
public class Wizard {
|
||||
|
||||
private final Deque<Runnable> undoStack = new LinkedList<>();
|
||||
private final Deque<Runnable> redoStack = new LinkedList<>();
|
||||
|
||||
public Wizard() {
|
||||
}
|
||||
|
||||
public void castSpell(Runnable runnable) {
|
||||
runnable.run();
|
||||
undoStack.offerLast(runnable);
|
||||
}
|
||||
|
||||
public void undoLastSpell() {
|
||||
if (!undoStack.isEmpty()) {
|
||||
var previousSpell = undoStack.pollLast();
|
||||
redoStack.offerLast(previousSpell);
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void redoLastSpell() {
|
||||
if (!redoStack.isEmpty()) {
|
||||
var previousSpell = redoStack.pollLast();
|
||||
undoStack.offerLast(previousSpell);
|
||||
previousSpell.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Wizard";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A continuación, tenemos al duende `Goblin` que es el objetivo `Target` de los hechizos.
|
||||
|
||||
```java
|
||||
|
||||
@Slf4j
|
||||
public abstract class Target {
|
||||
|
||||
private Size size;
|
||||
|
||||
private Visibility visibility;
|
||||
|
||||
public Size getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Size size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(Visibility visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
public void printStatus() {
|
||||
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
public class Goblin extends Target {
|
||||
|
||||
public Goblin() {
|
||||
setSize(Size.NORMAL);
|
||||
setVisibility(Visibility.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Goblin";
|
||||
}
|
||||
|
||||
public void changeSize() {
|
||||
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
|
||||
setSize(oldSize);
|
||||
}
|
||||
|
||||
public void changeVisibility() {
|
||||
var visible = getVisibility() == Visibility.INVISIBLE
|
||||
? Visibility.VISIBLE : Visibility.INVISIBLE;
|
||||
setVisibility(visible);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Por último, tenemos al mago en la función principal lanzando hechizos.
|
||||
|
||||
```java
|
||||
public static void main(String[]args){
|
||||
var wizard=new Wizard();
|
||||
var goblin=new Goblin();
|
||||
|
||||
// casts shrink/unshrink spell
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
|
||||
// casts visible/invisible spell
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
|
||||
// undo and redo casts
|
||||
wizard.undoLastSpell();
|
||||
wizard.redoLastSpell();
|
||||
```
|
||||
|
||||
Este es el ejemplo en acción.
|
||||
|
||||
```java
|
||||
var wizard=new Wizard();
|
||||
var goblin=new Goblin();
|
||||
|
||||
goblin.printStatus();
|
||||
wizard.castSpell(goblin::changeSize);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.castSpell(goblin::changeVisibility);
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.undoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
|
||||
wizard.redoLastSpell();
|
||||
goblin.printStatus();
|
||||
```
|
||||
|
||||
Aquí está la salida del programa:
|
||||
|
||||
```java
|
||||
Goblin,[size=normal][visibility=visible]
|
||||
Goblin,[size=small][visibility=visible]
|
||||
Goblin,[size=small][visibility=invisible]
|
||||
Goblin,[size=small][visibility=visible]
|
||||
Goblin,[size=normal][visibility=visible]
|
||||
Goblin,[size=small][visibility=visible]
|
||||
Goblin,[size=small][visibility=invisible]
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Comando (Command) para:
|
||||
|
||||
* Parametrizar objetos mediante una acción a realizar. Puedes expresar dicha parametrización en un lenguaje procedimental con una función callback, es decir, una función que se registra en algún lugar para ser llamada en un momento posterior. Los comandos son un sustituto orientado a objetos de las retrollamadas.
|
||||
* Especifican, ponen en cola y ejecutan peticiones en diferentes momentos. Un objeto Command puede tener una vida independiente de la petición original. Si el receptor de una petición puede ser representado de una manera independiente del espacio de direcciones, entonces puedes transferir un objeto comando para la petición a un proceso diferente y cumplir la petición allí.
|
||||
* Soporta deshacer. La operación de ejecución del comando puede almacenar el estado para revertir sus efectos en el propio comando. La interfaz del Comando debe tener una operación añadida de des-ejecutar que revierta los efectos de una llamada previa a ejecutar. Los comandos ejecutados se almacenan en una lista de historial. La funcionalidad de deshacer y rehacer a nivel ilimitado se consigue recorriendo esta lista hacia atrás y hacia delante llamando a un-ejecutar y ejecutar, respectivamente.
|
||||
* Soporta el registro de cambios para que puedan volver a aplicarse en caso de caída del sistema. Al aumentar la interfaz de comandos con operaciones de carga y almacenamiento, puede mantener un registro persistente de los cambios. La recuperación de un fallo implica volver a cargar los comandos registrados desde el disco y volver a ejecutarlos con la operación de ejecución.
|
||||
* Estructurar un sistema en torno a operaciones de alto nivel construidas sobre operaciones primitivas. Esta estructura es común en los sistemas de información que admiten transacciones. Una transacción encapsula un conjunto de cambios de datos. El patrón Command ofrece una forma de modelar las transacciones. Los comandos tienen una interfaz común que permite invocar todas las transacciones de la misma manera. El patrón también facilita la ampliación del sistema con nuevas transacciones.
|
||||
* Mantener un historial de peticiones.
|
||||
* Implementar la funcionalidad de callback.
|
||||
* Implementar la funcionalidad de deshacer.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* Botones GUI y elementos de menú en aplicaciones de escritorio.
|
||||
* Operaciones en sistemas de bases de datos y sistemas transaccionales que soportan rollback.
|
||||
* Grabación de macros en aplicaciones como editores de texto y hojas de cálculo.
|
||||
* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html)
|
||||
* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java)
|
||||
* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki)
|
||||
* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html)
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Ventajas:
|
||||
|
||||
* Desacopla el objeto que invoca la operación del que sabe cómo realizarla.
|
||||
* Es fácil añadir nuevos Comandos, porque no tienes que cambiar las clases existentes.
|
||||
* Puedes ensamblar un conjunto de comandos en un comando compuesto.
|
||||
|
||||
Desventajas:
|
||||
|
||||
* Aumenta el número de clases para cada comando individual.
|
||||
* Puede complicar el diseño al añadir múltiples capas entre emisores y receptores.
|
||||
|
||||
## Patrones Relacionados
|
||||
|
||||
* [Composite](https://java-design-patterns.com/patterns/composite/): Los comandos pueden ser compuestos usando el patrón Composite para crear macro comandos.
|
||||
* [Memento](https://java-design-patterns.com/patterns/memento/): Puede usarse para implementar mecanismos de deshacer.
|
||||
* [Observador](https://java-design-patterns.com/patterns/observer/): El patrón puede ser observado para cambios que activan comandos.
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
||||
* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY)
|
||||
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Commander
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Cloud distributed
|
||||
- Microservices
|
||||
- Transactions
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
* Distributed Transaction Commander
|
||||
* Transaction Coordinator
|
||||
|
||||
## Propósito
|
||||
|
||||
La intención del patrón Commander en el contexto de las transacciones distribuidas es gestionar y coordinar transacciones complejas a través de múltiples componentes o servicios distribuidos, asegurando la consistencia e integridad de la transacción global. Encapsula comandos de transacciones y lógica de coordinación, facilitando la implementación de protocolos de transacciones distribuidas como commit de dos fases o Saga.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Imagine que organiza un gran festival internacional de música en el que están programadas actuaciones de varios grupos de todo el mundo. La llegada, la prueba de sonido y la actuación de cada grupo son como transacciones individuales en un sistema distribuido. El organizador del festival actúa como el "Comandante", coordinando estas transacciones para garantizar que si el vuelo de una banda se retrasa (similar a un fallo de transacción), hay un plan de respaldo, como reprogramar o intercambiar franjas horarias con otra banda (acciones compensatorias), para mantener intacto el programa general. Esta configuración refleja el patrón del Comandante en las transacciones distribuidas, en las que varios componentes deben coordinarse para lograr un resultado satisfactorio a pesar de los fallos individuales.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón Commander convierte una petición en un objeto independiente, permitiendo la parametrización de comandos, la puesta en cola de acciones y la implementación de operaciones de deshacer.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
La gestión de transacciones a través de diferentes servicios en un sistema distribuido, como una plataforma de comercio electrónico con microservicios separados de Pago y Envío, requiere una cuidadosa coordinación para evitar problemas. Cuando un usuario realiza un pedido pero un servicio (por ejemplo, Pago) no está disponible mientras que el otro (por ejemplo, Envío) está listo, necesitamos una solución robusta para manejar esta discrepancia.
|
||||
|
||||
Una estrategia para resolver este problema consiste en utilizar un componente Commander que orqueste el proceso. Inicialmente, el pedido es procesado por el servicio disponible (Envío en este caso). A continuación, el comandante intenta sincronizar el pedido con el servicio no disponible en ese momento (Pago) almacenando los detalles del pedido en una base de datos o poniéndolo en cola para su procesamiento futuro. Este sistema de colas también debe tener en cuenta posibles fallos al añadir solicitudes a la cola.
|
||||
|
||||
El comandante intenta repetidamente procesar los pedidos en cola para garantizar que ambos servicios reflejen finalmente los mismos datos de transacción. Este proceso implica garantizar la idempotencia, lo que significa que incluso si la misma solicitud de sincronización de pedidos se realiza varias veces, sólo se ejecutará una vez, evitando transacciones duplicadas. El objetivo es lograr una coherencia final entre los servicios, en la que todos los sistemas se sincronicen a lo largo del tiempo a pesar de los fallos o retrasos iniciales.
|
||||
|
||||
En el código proporcionado, el patrón Commander se utiliza para manejar transacciones distribuidas a través de múltiples servicios (PaymentService, ShippingService, MessagingService, EmployeeHandle). Cada servicio tiene su propia base de datos y puede lanzar excepciones para simular fallos.
|
||||
|
||||
La clase Commander es la parte central de este patrón. Toma instancias de todos los servicios y sus bases de datos, junto con algunos parámetros de configuración. El método placeOrder de la clase Commander se utiliza para realizar un pedido, lo que implica interactuar con todos los servicios.
|
||||
|
||||
```java
|
||||
public class Commander {
|
||||
// ... constructor and other methods ...
|
||||
|
||||
public void placeOrder(Order order) {
|
||||
// ... implementation ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Las clases Usuario y Pedido representan un usuario y un pedido respectivamente. Un pedido lo realiza un usuario.
|
||||
|
||||
```java
|
||||
public class User {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
|
||||
public class Order {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
```
|
||||
|
||||
Cada servicio (por ejemplo, PaymentService, ShippingService, MessagingService, EmployeeHandle) tiene su propia base de datos y puede lanzar excepciones para simular fallos. Por ejemplo, el PaymentService puede lanzar una DatabaseUnavailableException si su base de datos no está disponible.
|
||||
|
||||
```java
|
||||
public class PaymentService {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
```
|
||||
|
||||
Las clases DatabaseUnavailableException, ItemUnavailableException y ShippingNotPossibleException representan diferentes tipos de excepciones que pueden ocurrir.
|
||||
|
||||
```java
|
||||
public class DatabaseUnavailableException extends Exception {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
|
||||
public class ItemUnavailableException extends Exception {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
|
||||
public class ShippingNotPossibleException extends Exception {
|
||||
// ... constructor and other methods ...
|
||||
}
|
||||
```
|
||||
|
||||
En el método principal de cada clase (AppQueueFailCases, AppShippingFailCases), se simulan diferentes escenarios creando instancias de la clase Commander con diferentes configuraciones y llamando al método placeOrder.
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Commander para transacciones distribuidas cuando:
|
||||
|
||||
* Necesites asegurar la consistencia de los datos entre servicios distribuidos en caso de fallos parciales del sistema.
|
||||
* Las transacciones abarcan múltiples microservicios o componentes distribuidos que requieren un commit o rollback coordinado.
|
||||
* Está implementando transacciones de larga duración que requieren acciones compensatorias para la reversión.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* Protocolos Two-Phase Commit (2PC): Coordinación de commit o rollback a través de bases de datos o servicios distribuidos.
|
||||
* Implementaciones del patrón Saga: Gestión de procesos de negocio de larga duración que abarcan múltiples microservicios, con cada paso teniendo una acción compensatoria para la reversión.
|
||||
* Transacciones distribuidas en arquitectura de microservicios: Coordinación de operaciones complejas entre microservicios manteniendo la integridad y consistencia de los datos.
|
||||
|
||||
## Consecuencias
|
||||
|
||||
Beneficios:
|
||||
|
||||
* Proporciona un mecanismo claro para gestionar transacciones distribuidas complejas, mejorando la fiabilidad del sistema.
|
||||
* Permite la implementación de transacciones compensatorias, que son cruciales para mantener la coherencia en transacciones de larga duración.
|
||||
* Facilita la integración de sistemas heterogéneos en un contexto transaccional.
|
||||
|
||||
Contrapartidas:
|
||||
|
||||
* Aumenta la complejidad, especialmente en situaciones de fallo, debido a la necesidad de mecanismos de reversión coordinados.
|
||||
* Potencialmente afecta al rendimiento debido a la sobrecarga de la coordinación y las comprobaciones de coherencia.
|
||||
* Las implementaciones basadas en Saga pueden llevar a una mayor complejidad en la comprensión del flujo global del proceso de negocio.
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
[Saga Pattern](https://java-design-patterns.com/patterns/saga/): A menudo se discute junto con el patrón Commander para transacciones distribuidas, centrándose en transacciones de larga duración con acciones compensatorias.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Distributed Transactions: The Icebergs of Microservices](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/)
|
||||
* [Microservices Patterns: With examples in Java](https://amzn.to/4axjnYW)
|
||||
* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/4axHwOV)
|
||||
* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4aATcRe)
|
||||
|
After Width: | Height: | Size: 315 KiB |
@@ -26,7 +26,7 @@ Ejemplo real
|
||||
> En una consola, puede haber muchas interfaces que necesiten ser gestionadas y controladas. Usando el patrón de entidad
|
||||
> compuesta, objetos dependientes como mensajes y señales pueden ser combinados y controlados usando un único objeto.
|
||||
|
||||
En palabras llanas
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón de entidad compuesta permite representar y gestionar un conjunto de objetos relacionados mediante un objeto
|
||||
> unificado.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Data Locality
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Game programming
|
||||
- Performance
|
||||
---
|
||||
|
||||
## Propósito
|
||||
Acelera el acceso a la memoria organizando los datos para aprovechar la caché de la CPU.
|
||||
|
||||
Las CPU modernas disponen de cachés para acelerar el acceso a la memoria. Éstas pueden acceder mucho más rápido a la memoria adyacente a la memoria a la que se ha accedido recientemente. Aprovéchate de ello para mejorar el rendimiento aumentando la localidad de los datos, manteniéndolos en memoria contigua en el orden en que los procesas.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
* Como la mayoría de las optimizaciones, la primera pauta para usar el patrón Data Locality es cuando se tiene un problema de rendimiento.
|
||||
* Con este patrón específicamente, también querrás estar seguro de que tus problemas de rendimiento son causados por pérdidas de caché.
|
||||
|
||||
## Ejemplo del mundo real
|
||||
|
||||
* El motor de juego [Artemis](http://gamadu.com/artemis/) es uno de los primeros y más conocidos frameworks que utiliza IDs simples para las entidades del juego.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Game Programming Patterns Optimization Patterns: Data Locality](http://gameprogrammingpatterns.com/data-locality.html)
|
||||
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Dirty Flag
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Game programming
|
||||
- Performance
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
* IsDirty pattern
|
||||
|
||||
## Propósito
|
||||
Evitar la costosa readquisición de recursos. Los recursos conservan su identidad, se guardan en algún almacenamiento de acceso rápido y se reutilizan para evitar tener que adquirirlos de nuevo.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón Dirty Flag cuando
|
||||
|
||||
* La adquisición, inicialización y liberación repetitiva del mismo recurso causa una sobrecarga de rendimiento innecesaria.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: Double Buffer
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Performance
|
||||
- Game programming
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Doble búfer es un término utilizado para describir un dispositivo que tiene dos búferes. El uso de varios búferes aumenta el rendimiento global de un dispositivo y ayuda a evitar cuellos de botella. Este ejemplo muestra el uso de doble búfer en gráficos. Se utiliza para mostrar una imagen o un fotograma mientras se almacena en el búfer otro fotograma que se mostrará a continuación. Este método hace que las animaciones y los juegos parezcan más realistas que los realizados en modo de búfer único.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
> Un ejemplo típico, y que todo motor de juego debe abordar, es el renderizado. Cuando el juego dibuja el mundo que ven los usuarios, lo hace pieza a pieza: las montañas a lo lejos, las colinas ondulantes, los árboles, cada uno a su vez. Si el usuario viera cómo se dibuja la vista de forma incremental, se rompería la ilusión de un mundo coherente. La escena debe actualizarse con fluidez y rapidez, mostrando una serie de fotogramas completos, cada uno de los cuales aparece al instante. La doble memoria intermedia resuelve el problema.
|
||||
|
||||
En pocas palabras
|
||||
> Garantiza un estado que se renderiza correctamente mientras ese estado se modifica de forma incremental. Se utiliza mucho en gráficos por ordenador.
|
||||
|
||||
Wikipedia dice
|
||||
> En informática, el almacenamiento en búfer múltiple es el uso de más de un búfer para contener un bloque de datos, de modo que un "lector" vea una versión completa (aunque quizás antigua) de los datos, en lugar de una versión parcialmente actualizada de los datos que está creando un "escritor". Se utiliza mucho en las imágenes de ordenador.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Interfaz `Buffer` que asegura las funcionalidades básicas de un buffer.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Buffer interface.
|
||||
*/
|
||||
public interface Buffer {
|
||||
|
||||
/**
|
||||
* Clear the pixel in (x, y).
|
||||
*
|
||||
* @param x X coordinate
|
||||
* @param y Y coordinate
|
||||
*/
|
||||
void clear(int x, int y);
|
||||
|
||||
/**
|
||||
* Draw the pixel in (x, y).
|
||||
*
|
||||
* @param x X coordinate
|
||||
* @param y Y coordinate
|
||||
*/
|
||||
void draw(int x, int y);
|
||||
|
||||
/**
|
||||
* Clear all the pixels.
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* Get all the pixels.
|
||||
*
|
||||
* @return pixel list
|
||||
*/
|
||||
Pixel[] getPixels();
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Una de las implementaciones de la interfaz `Buffer`.
|
||||
|
||||
```java
|
||||
/**
|
||||
* FrameBuffer implementation class.
|
||||
*/
|
||||
public class FrameBuffer implements Buffer {
|
||||
|
||||
public static final int WIDTH = 10;
|
||||
public static final int HEIGHT = 8;
|
||||
|
||||
private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT];
|
||||
|
||||
public FrameBuffer() {
|
||||
clearAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int x, int y) {
|
||||
pixels[getIndex(x, y)] = Pixel.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int x, int y) {
|
||||
pixels[getIndex(x, y)] = Pixel.BLACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAll() {
|
||||
Arrays.fill(pixels, Pixel.WHITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pixel[] getPixels() {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private int getIndex(int x, int y) {
|
||||
return x + WIDTH * y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* Pixel enum. Each pixel can be white (not drawn) or black (drawn).
|
||||
*/
|
||||
public enum Pixel {
|
||||
|
||||
WHITE, BLACK;
|
||||
}
|
||||
```
|
||||
|
||||
`Scene` representa la escena del juego en la que ya se ha renderizado el búfer actual.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Scene class. Render the output frame.
|
||||
*/
|
||||
@Slf4j
|
||||
public class Scene {
|
||||
|
||||
private final Buffer[] frameBuffers;
|
||||
|
||||
private int current;
|
||||
|
||||
private int next;
|
||||
|
||||
/**
|
||||
* Constructor of Scene.
|
||||
*/
|
||||
public Scene() {
|
||||
frameBuffers = new FrameBuffer[2];
|
||||
frameBuffers[0] = new FrameBuffer();
|
||||
frameBuffers[1] = new FrameBuffer();
|
||||
current = 0;
|
||||
next = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the next frame.
|
||||
*
|
||||
* @param coordinateList list of pixels of which the color should be black
|
||||
*/
|
||||
public void draw(List<? extends Pair<Integer, Integer>> coordinateList) {
|
||||
LOGGER.info("Start drawing next frame");
|
||||
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
||||
frameBuffers[next].clearAll();
|
||||
coordinateList.forEach(coordinate -> {
|
||||
var x = coordinate.getKey();
|
||||
var y = coordinate.getValue();
|
||||
frameBuffers[next].draw(x, y);
|
||||
});
|
||||
LOGGER.info("Swap current and next buffer");
|
||||
swap();
|
||||
LOGGER.info("Finish swapping");
|
||||
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
||||
}
|
||||
|
||||
public Buffer getBuffer() {
|
||||
LOGGER.info("Get current buffer: " + current);
|
||||
return frameBuffers[current];
|
||||
}
|
||||
|
||||
private void swap() {
|
||||
current = current ^ next;
|
||||
next = current ^ next;
|
||||
current = current ^ next;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
final var scene = new Scene();
|
||||
var drawPixels1 = List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2));
|
||||
scene.draw(drawPixels1);
|
||||
var buffer1 = scene.getBuffer();
|
||||
printBlackPixelCoordinate(buffer1);
|
||||
|
||||
var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1));
|
||||
scene.draw(drawPixels2);
|
||||
var buffer2 = scene.getBuffer();
|
||||
printBlackPixelCoordinate(buffer2);
|
||||
}
|
||||
|
||||
private static void printBlackPixelCoordinate(Buffer buffer) {
|
||||
StringBuilder log = new StringBuilder("Black Pixels: ");
|
||||
var pixels = buffer.getPixels();
|
||||
for (var i = 0; i < pixels.length; ++i) {
|
||||
if (pixels[i] == Pixel.BLACK) {
|
||||
var y = i / FrameBuffer.WIDTH;
|
||||
var x = i % FrameBuffer.WIDTH;
|
||||
log.append(" (").append(x).append(", ").append(y).append(")");
|
||||
}
|
||||
}
|
||||
LOGGER.info(log.toString());
|
||||
}
|
||||
```
|
||||
|
||||
La salida de la consola
|
||||
|
||||
```text
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 1
|
||||
[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (1, 1) (3, 2) (5, 6)
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1
|
||||
[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 0
|
||||
[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (6, 1) (3, 7)
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Este patrón es uno de esos que sabrás cuándo lo necesitas. Si tienes un sistema que carece de doble búfer, probablemente tendrá un aspecto visiblemente incorrecto (tearing, etc.) o se comportará de forma incorrecta. Pero decir "lo sabrás cuando lo necesites" no da mucho de sí. Más concretamente, este patrón es apropiado cuando todo esto es cierto:
|
||||
|
||||
- Tenemos algún estado que está siendo modificado incrementalmente.
|
||||
- Ese mismo estado puede ser accedido en medio de la modificación.
|
||||
- Queremos evitar que el código que está accediendo al estado vea el trabajo en curso.
|
||||
- Queremos poder leer el estado y no queremos tener que esperar mientras se escribe.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html)
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Extension objects
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
# Extention Objects Pattern
|
||||
|
||||
## Propósito
|
||||
Anticipar que la interfaz de un objeto debe ampliarse en el futuro. Las interfaces adicionales se definen mediante objetos de extensión (Extension objects).
|
||||
|
||||
## Explicación
|
||||
Ejemplo real
|
||||
|
||||
> Suponga que está desarrollando un juego basado en Java para un cliente y, en mitad del proceso de desarrollo, le sugieren nuevas funcionalidades. El patrón Extension Objects permite a su programa adaptarse a cambios imprevistos con una refactorización mínima, especialmente al integrar funcionalidades adicionales en su proyecto.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón Extension Objects se utiliza para añadir dinámicamente funcionalidad a los objetos sin modificar sus clases principales. Es un patrón de diseño de comportamiento utilizado para añadir nuevas funcionalidades a clases y objetos existentes dentro de un programa. Este patrón proporciona a los programadores la capacidad de extender/modificar la funcionalidad de las clases sin tener que refactorizar el código fuente existente.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En la programación informática orientada a objetos, un patrón de objetos de extensión es un patrón de diseño añadido a un objeto después de que el objeto original fue compilado. El objeto modificado es a menudo una clase, un prototipo o un tipo. Los patrones de objetos de extensión son características de algunos lenguajes de programación orientados a objetos. No hay diferencia sintáctica entre llamar a un método de extensión y llamar a un método declarado en la definición del tipo.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
El objetivo de utilizar el patrón de objetos de extensión (Extension objects) es implementar nuevas características/funcionalidades sin tener que refactorizar cada clase.
|
||||
Los siguientes ejemplos muestran la utilización de este patrón para una clase Enemigo que extiende Entidad dentro de un juego:
|
||||
|
||||
Clase App primaria desde la que ejecutar nuestro programa.
|
||||
|
||||
```java
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
Entity enemy = new Enemy("Enemy");
|
||||
checkExtensionsForEntity(enemy);
|
||||
}
|
||||
|
||||
private static void checkExtensionsForEntity(Entity entity) {
|
||||
Logger logger = Logger.getLogger(App.class.getName());
|
||||
String name = entity.getName();
|
||||
Function<String, Runnable> func = (e) -> () -> logger.info(name + " without " + e);
|
||||
|
||||
String extension = "EnemyExtension";
|
||||
Optional.ofNullable(entity.getEntityExtension(extension))
|
||||
.map(e -> (EnemyExtension) e)
|
||||
.ifPresentOrElse(EnemyExtension::extendedAction, func.apply(extension));
|
||||
}
|
||||
}
|
||||
```
|
||||
Clase de enemigo con acciones iniciales y extensiones.
|
||||
|
||||
```java
|
||||
class Enemy extends Entity {
|
||||
public Enemy(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performInitialAction() {
|
||||
super.performInitialAction();
|
||||
System.out.println("Enemy wants to attack you.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityExtension getEntityExtension(String extensionName) {
|
||||
if (extensionName.equals("EnemyExtension")) {
|
||||
return Optional.ofNullable(entityExtension).orElseGet(EnemyExtension::new);
|
||||
}
|
||||
return super.getEntityExtension(extensionName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clase EnemyExtension con sobreescritura del método extendAction().
|
||||
|
||||
```java
|
||||
class EnemyExtension implements EntityExtension {
|
||||
@Override
|
||||
public void extendedAction() {
|
||||
System.out.println("Enemy has advanced towards you!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clase de entidad que será extendida por Enemy.
|
||||
|
||||
```java
|
||||
class Entity {
|
||||
private String name;
|
||||
protected EntityExtension entityExtension;
|
||||
|
||||
public Entity(String name) {
|
||||
this.name = name;
|
||||
performInitialAction();
|
||||
}
|
||||
|
||||
protected void performInitialAction() {
|
||||
System.out.println(name + " performs the initial action.");
|
||||
}
|
||||
|
||||
public EntityExtension getEntityExtension(String extensionName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
```
|
||||
Interfaz EntityExtension que utilizará EnemyExtension.
|
||||
|
||||
```java
|
||||
interface EntityExtension {
|
||||
void extendedAction();
|
||||
}
|
||||
```
|
||||
Salida del programa:
|
||||
|
||||
```markdown
|
||||
Enemy performs the initial action.
|
||||
Enemy wants to attack you.
|
||||
Enemy has advanced towards you!
|
||||
```
|
||||
En este ejemplo, el patrón de Objetos de Extensión permite a la entidad enemiga realizar acciones iniciales únicas y acciones avanzadas cuando se aplican extensiones específicas. Este patrón proporciona flexibilidad y extensibilidad a la base de código a la vez que minimiza la necesidad de realizar cambios importantes en el código.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón de Objetos de Extensión (Extension objects) cuando:
|
||||
|
||||
* Necesita soportar la adición de interfaces nuevas o imprevistas a clases existentes y no quieres impactar a clientes que no necesitan esta nueva interfaz. Los objetos de extensión te permiten mantener juntas operaciones relacionadas definiéndolas en una clase separada
|
||||
* Una clase que representa una abstracción clave desempeña diferentes funciones para diferentes clientes. El número de funciones que puede desempeñar la clase debe ser ilimitado. Es necesario preservar la propia abstracción clave. Por ejemplo, un objeto cliente sigue siendo un objeto cliente aunque distintos subsistemas lo vean de forma diferente.
|
||||
* Una clase debe ser extensible con nuevos comportamientos sin necesidad de subclasificar a partir de ella.
|
||||
|
||||
## Ejemplos del mundo real
|
||||
|
||||
* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc)
|
||||
* [Object Linking and Embedding](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding)
|
||||
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Feature Toggle
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
Feature Flag
|
||||
|
||||
## Propósito
|
||||
Técnica utilizada en el desarrollo de software para controlar y gestionar el despliegue de características o funcionalidades específicas en un programa sin cambiar el código. Puede actuar como un interruptor de encendido/apagado de funciones en función del estado o las propiedades de otros valores del programa. Esto es similar a las pruebas A/B, en las que las funciones se despliegan en función de propiedades como la ubicación o el dispositivo. La implementación de este patrón de diseño puede aumentar la complejidad del código, y es importante recordar eliminar el código redundante si este patrón de diseño se utiliza para eliminar gradualmente un sistema o característica.
|
||||
|
||||
## Explicación
|
||||
Ejemplo del mundo real
|
||||
> Este patrón de diseño funciona realmente bien en cualquier tipo de desarrollo, en particular en el desarrollo móvil. Digamos que quieres introducir una característica como el modo oscuro, pero quieres asegurarte de que la característica funciona correctamente y no quieres desplegar la característica a todo el mundo inmediatamente. Escribes el código y lo desactivas por defecto. A partir de aquí, es fácil activar el código para usuarios específicos basándose en criterios de selección, o aleatoriamente. Esto también permitirá que la función se desactive fácilmente sin ningún cambio drástico en el código, o cualquier necesidad de redistribución o actualizaciones.
|
||||
|
||||
En pocas palabras
|
||||
> Feature Toggle es una forma de introducir nuevas funciones gradualmente en lugar de desplegarlas todas a la vez.
|
||||
|
||||
Wikipedia dice
|
||||
> Una función de conmutación en el desarrollo de software proporciona una alternativa al mantenimiento de múltiples ramas de características en el código fuente. Una condición dentro del código activa o desactiva una característica durante el tiempo de ejecución. En un entorno ágil, el conmutador se utiliza en producción, para activar la función bajo demanda, para algunos o todos los usuarios.
|
||||
|
||||
**Ejemplo programático**
|
||||
Este ejemplo muestra código Java que permite mostrar una funcionalidad cuando es activada por el desarrollador, y cuando un usuario es miembro Premium de la aplicación. Esto es útil para características bloqueadas por suscripción.
|
||||
|
||||
```java
|
||||
public class FeatureToggleExample {
|
||||
// Bool for feature enabled or disabled
|
||||
private static boolean isNewFeatureEnabled = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
boolean userIsPremium = true; // Example: Check if the user is a premium user
|
||||
|
||||
// Check if the new feature should be enabled for the user
|
||||
if (userIsPremium && isNewFeatureEnabled) {
|
||||
// User is premium and the new feature is enabled
|
||||
showNewFeature();
|
||||
}
|
||||
}
|
||||
|
||||
private static void showNewFeature() {
|
||||
// If user is allowed to see locked feature, this is where the code would go
|
||||
}
|
||||
}
|
||||
```
|
||||
El código muestra lo sencillo que es aplicar este patrón de diseño, y los criterios pueden refinarse o ampliarse aún más si los desarrolladores así lo deciden.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón de alternancia de funciones cuando
|
||||
|
||||
* Dar diferentes características a diferentes usuarios.
|
||||
* Desplegar una nueva característica de forma incremental.
|
||||
* Cambiar entre entornos de desarrollo y producción.
|
||||
* Desactivar rápidamente funciones problemáticas.
|
||||
* Gestión externa del despliegue de características.
|
||||
* Capacidad de mantener múltiples versiones de una característica.
|
||||
* Despliegue "oculto", liberando una característica en código para pruebas designadas, pero sin ponerla a disposición del público.
|
||||
|
||||
## Consecuencias
|
||||
Consecuencias del uso del patrón de alternancia de funciones
|
||||
|
||||
* Aumenta la complejidad del código
|
||||
* Probar múltiples estados es más difícil y consume más tiempo
|
||||
* Confusión entre amigos sobre por qué faltan características
|
||||
* Mantener la documentación actualizada con todas las características puede ser difícil.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Martin Fowler 29 October 2010 (2010-10-29).](http://martinfowler.com/bliki/FeatureToggle.html)
|
||||
* [Feature Toggle - Java Design Patterns](https://java-design-patterns.com/patterns/feature-toggle/)
|
||||
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,245 @@
|
||||
---
|
||||
title: Game Loop
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Game programming
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Un bucle de juego se ejecuta continuamente durante la partida. En cada vuelta del bucle, procesa las entradas del usuario sin bloquearse, actualiza el estado del juego y lo renderiza. Realiza un seguimiento del paso del tiempo para controlar el ritmo de juego.
|
||||
|
||||
Este patrón desvincula la progresión del tiempo de juego de la entrada del usuario y de la velocidad del procesador.
|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Este patrón se utiliza en todos los motores de juego.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> El bucle de juego es el proceso principal de todos los hilos de renderizado del juego. Está presente en todos los juegos modernos. Controla el proceso de entrada, la actualización del estado interno, el renderizado, la IA y todos los demás procesos.
|
||||
|
||||
En pocas palabras
|
||||
|
||||
> El patrón de bucle de juego garantiza que el tiempo de juego progrese a la misma velocidad en todas las configuraciones de hardware diferentes.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> El componente central de cualquier juego, desde el punto de vista de la programación, es el bucle de juego. El bucle de juego permite que el juego se ejecute sin problemas, independientemente de la entrada de un usuario, o la falta de ella.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Empecemos con algo sencillo. Aquí está la clase `Bullet`. Las balas se moverán en nuestro juego. Para propósitos de demostración es suficiente que tenga una posición unidimensional.
|
||||
|
||||
```java
|
||||
public class Bullet {
|
||||
|
||||
private float position;
|
||||
|
||||
public Bullet() {
|
||||
position = 0.0f;
|
||||
}
|
||||
|
||||
public float getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(float position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El `GameController` es el responsable de mover los objetos del juego, incluida la mencionada bala.
|
||||
|
||||
```java
|
||||
public class GameController {
|
||||
|
||||
protected final Bullet bullet;
|
||||
|
||||
public GameController() {
|
||||
bullet = new Bullet();
|
||||
}
|
||||
|
||||
public void moveBullet(float offset) {
|
||||
var currentPosition = bullet.getPosition();
|
||||
bullet.setPosition(currentPosition + offset);
|
||||
}
|
||||
|
||||
public float getBulletPosition() {
|
||||
return bullet.getPosition();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ahora introducimos el bucle de juego. O en realidad en esta demo tenemos 3 bucles de juego diferentes. Veamos primero la clase base `GameLoop`.
|
||||
|
||||
```java
|
||||
public enum GameStatus {
|
||||
|
||||
RUNNING, STOPPED
|
||||
}
|
||||
|
||||
public abstract class GameLoop {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
protected volatile GameStatus status;
|
||||
|
||||
protected GameController controller;
|
||||
|
||||
private Thread gameThread;
|
||||
|
||||
public GameLoop() {
|
||||
controller = new GameController();
|
||||
status = GameStatus.STOPPED;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
status = GameStatus.RUNNING;
|
||||
gameThread = new Thread(this::processGameLoop);
|
||||
gameThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
status = GameStatus.STOPPED;
|
||||
}
|
||||
|
||||
public boolean isGameRunning() {
|
||||
return status == GameStatus.RUNNING;
|
||||
}
|
||||
|
||||
protected void processInput() {
|
||||
try {
|
||||
var lag = new Random().nextInt(200) + 50;
|
||||
Thread.sleep(lag);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected void render() {
|
||||
var position = controller.getBulletPosition();
|
||||
logger.info("Current bullet position: " + position);
|
||||
}
|
||||
|
||||
protected abstract void processGameLoop();
|
||||
}
|
||||
```
|
||||
|
||||
Aquí está la primera implementación del bucle de juego, `FrameBasedGameLoop`:
|
||||
|
||||
```java
|
||||
public class FrameBasedGameLoop extends GameLoop {
|
||||
|
||||
@Override
|
||||
protected void processGameLoop() {
|
||||
while (isGameRunning()) {
|
||||
processInput();
|
||||
update();
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
protected void update() {
|
||||
controller.moveBullet(0.5f);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Por último, mostramos todos los bucles del juego en acción.
|
||||
|
||||
```java
|
||||
try {
|
||||
LOGGER.info("Start frame-based game loop:");
|
||||
var frameBasedGameLoop = new FrameBasedGameLoop();
|
||||
frameBasedGameLoop.run();
|
||||
Thread.sleep(GAME_LOOP_DURATION_TIME);
|
||||
frameBasedGameLoop.stop();
|
||||
LOGGER.info("Stop frame-based game loop.");
|
||||
|
||||
LOGGER.info("Start variable-step game loop:");
|
||||
var variableStepGameLoop = new VariableStepGameLoop();
|
||||
variableStepGameLoop.run();
|
||||
Thread.sleep(GAME_LOOP_DURATION_TIME);
|
||||
variableStepGameLoop.stop();
|
||||
LOGGER.info("Stop variable-step game loop.");
|
||||
|
||||
LOGGER.info("Start fixed-step game loop:");
|
||||
var fixedStepGameLoop = new FixedStepGameLoop();
|
||||
fixedStepGameLoop.run();
|
||||
Thread.sleep(GAME_LOOP_DURATION_TIME);
|
||||
fixedStepGameLoop.stop();
|
||||
LOGGER.info("Stop variable-step game loop.");
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```java
|
||||
Start frame-based game loop:
|
||||
Current bullet position: 0.5
|
||||
Current bullet position: 1.0
|
||||
Current bullet position: 1.5
|
||||
Current bullet position: 2.0
|
||||
Current bullet position: 2.5
|
||||
Current bullet position: 3.0
|
||||
Current bullet position: 3.5
|
||||
Current bullet position: 4.0
|
||||
Current bullet position: 4.5
|
||||
Current bullet position: 5.0
|
||||
Current bullet position: 5.5
|
||||
Current bullet position: 6.0
|
||||
Stop frame-based game loop.
|
||||
Start variable-step game loop:
|
||||
Current bullet position: 6.5
|
||||
Current bullet position: 0.038
|
||||
Current bullet position: 0.084
|
||||
Current bullet position: 0.145
|
||||
Current bullet position: 0.1805
|
||||
Current bullet position: 0.28
|
||||
Current bullet position: 0.32
|
||||
Current bullet position: 0.42549998
|
||||
Current bullet position: 0.52849996
|
||||
Current bullet position: 0.57799995
|
||||
Current bullet position: 0.63199997
|
||||
Current bullet position: 0.672
|
||||
Current bullet position: 0.778
|
||||
Current bullet position: 0.848
|
||||
Current bullet position: 0.8955
|
||||
Current bullet position: 0.9635
|
||||
Stop variable-step game loop.
|
||||
Start fixed-step game loop:
|
||||
Current bullet position: 0.0
|
||||
Current bullet position: 1.086
|
||||
Current bullet position: 0.059999995
|
||||
Current bullet position: 0.12999998
|
||||
Current bullet position: 0.24000004
|
||||
Current bullet position: 0.33999994
|
||||
Current bullet position: 0.36999992
|
||||
Current bullet position: 0.43999985
|
||||
Current bullet position: 0.5399998
|
||||
Current bullet position: 0.65999967
|
||||
Current bullet position: 0.68999964
|
||||
Current bullet position: 0.7299996
|
||||
Current bullet position: 0.79999954
|
||||
Current bullet position: 0.89999944
|
||||
Current bullet position: 0.98999935
|
||||
Stop variable-step game loop.
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Créditos
|
||||
|
||||
* [Game Programming Patterns - Game Loop](http://gameprogrammingpatterns.com/game-loop.html)
|
||||
* [Game Programming Patterns](https://www.amazon.com/gp/product/0990582906/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0990582906&linkId=1289749a703b3fe0e24cd8d604d7c40b)
|
||||
* [Game Engine Architecture, Third Edition](https://www.amazon.com/gp/product/1138035459/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1138035459&linkId=94502746617211bc40e0ef49d29333ac)
|
||||
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,451 @@
|
||||
---
|
||||
title: Health Check Pattern
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Performance
|
||||
- Microservices
|
||||
- Resilience
|
||||
- Observability
|
||||
---
|
||||
|
||||
# Health Check Pattern
|
||||
|
||||
## También conocido como
|
||||
Health Monitoring, Service Health Check
|
||||
|
||||
## Propósito
|
||||
Garantizar la estabilidad y resistencia de los servicios en una arquitectura de microservicios proporcionando una forma de supervisar y diagnosticar su estado.
|
||||
|
||||
## Explicación
|
||||
En la arquitectura de microservicios, es crítico comprobar continuamente la salud de los servicios individuales. El Health Check Pattern es un mecanismo para que los microservicios expongan su estado de salud. Este patrón se implementa incluyendo un punto final de comprobación de salud en los microservicios que devuelve el estado actual del servicio. Esto es vital para mantener la resistencia del sistema y la disponibilidad operativa.
|
||||
|
||||
Para obtener más información, consulte el patrón API Health Check en [Microservices.io](https://microservices.io/patterns/observability/health-check-api.html).
|
||||
|
||||
|
||||
## Ejemplo del mundo real
|
||||
En un entorno nativo en la nube, como Kubernetes o AWS ECS, las comprobaciones de estado se utilizan para garantizar que los contenedores se ejecutan correctamente. Si un servicio falla su chequeo de salud, puede ser reiniciado o reemplazado automáticamente, asegurando alta disponibilidad y resiliencia.
|
||||
|
||||
## En pocas palabras
|
||||
El patrón de comprobación de la salud es como una visita periódica al médico para los servicios en una arquitectura de microservicios. Ayuda en la detección temprana de problemas y asegura que los servicios estén sanos y disponibles.
|
||||
|
||||
|
||||
## Ejemplo Programático
|
||||
Aquí, se proporcionan ejemplos detallados de implementaciones de chequeo de salud en un entorno de microservicios.
|
||||
|
||||
### AsynchronousHealthChecker
|
||||
Un componente de comprobación de salud asíncrono que ejecuta comprobaciones de salud en un hilo separado.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Performs a health check asynchronously using the provided health check logic with a specified
|
||||
* timeout.
|
||||
*
|
||||
* @param healthCheck the health check logic supplied as a {@code Supplier<Health>}
|
||||
* @param timeoutInSeconds the maximum time to wait for the health check to complete, in seconds
|
||||
* @return a {@code CompletableFuture<Health>} object that represents the result of the health
|
||||
* check
|
||||
*/
|
||||
public CompletableFuture<Health> performCheck(
|
||||
Supplier<Health> healthCheck, long timeoutInSeconds) {
|
||||
CompletableFuture<Health> future =
|
||||
CompletableFuture.supplyAsync(healthCheck, healthCheckExecutor);
|
||||
|
||||
// Schedule a task to enforce the timeout
|
||||
healthCheckExecutor.schedule(
|
||||
() -> {
|
||||
if (!future.isDone()) {
|
||||
LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE);
|
||||
future.completeExceptionally(new TimeoutException(HEALTH_CHECK_TIMEOUT_MESSAGE));
|
||||
}
|
||||
},
|
||||
timeoutInSeconds,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
return future.handle(
|
||||
(result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, throwable);
|
||||
// Check if the throwable is a TimeoutException or caused by a TimeoutException
|
||||
Throwable rootCause =
|
||||
throwable instanceof CompletionException ? throwable.getCause() : throwable;
|
||||
if (!(rootCause instanceof TimeoutException)) {
|
||||
LOGGER.error(HEALTH_CHECK_FAILED_MESSAGE, rootCause);
|
||||
return Health.down().withException(rootCause).build();
|
||||
} else {
|
||||
LOGGER.error(HEALTH_CHECK_TIMEOUT_MESSAGE, rootCause);
|
||||
// If it is a TimeoutException, rethrow it wrapped in a CompletionException
|
||||
throw new CompletionException(rootCause);
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### CpuHealthIndicator
|
||||
Un indicador de salud que comprueba la salud de la CPU del sistema.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Checks the health of the system's CPU and returns a health indicator object.
|
||||
*
|
||||
* @return a health indicator object
|
||||
*/
|
||||
@Override
|
||||
public Health health() {
|
||||
if (!(osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean)) {
|
||||
LOGGER.error("Unsupported operating system MXBean: {}", osBean.getClass().getName());
|
||||
return Health.unknown()
|
||||
.withDetail(ERROR_MESSAGE, "Unsupported operating system MXBean")
|
||||
.build();
|
||||
}
|
||||
|
||||
double systemCpuLoad = sunOsBean.getCpuLoad() * 100;
|
||||
double processCpuLoad = sunOsBean.getProcessCpuLoad() * 100;
|
||||
int availableProcessors = sunOsBean.getAvailableProcessors();
|
||||
double loadAverage = sunOsBean.getSystemLoadAverage();
|
||||
|
||||
Map<String, Object> details = new HashMap<>();
|
||||
details.put("timestamp", Instant.now());
|
||||
details.put("systemCpuLoad", String.format("%.2f%%", systemCpuLoad));
|
||||
details.put("processCpuLoad", String.format("%.2f%%", processCpuLoad));
|
||||
details.put("availableProcessors", availableProcessors);
|
||||
details.put("loadAverage", loadAverage);
|
||||
|
||||
if (systemCpuLoad > systemCpuLoadThreshold) {
|
||||
LOGGER.error(HIGH_SYSTEM_CPU_LOAD_MESSAGE, systemCpuLoad);
|
||||
return Health.down()
|
||||
.withDetails(details)
|
||||
.withDetail(ERROR_MESSAGE, HIGH_SYSTEM_CPU_LOAD_MESSAGE_WITHOUT_PARAM)
|
||||
.build();
|
||||
} else if (processCpuLoad > processCpuLoadThreshold) {
|
||||
LOGGER.error(HIGH_PROCESS_CPU_LOAD_MESSAGE, processCpuLoad);
|
||||
return Health.down()
|
||||
.withDetails(details)
|
||||
.withDetail(ERROR_MESSAGE, HIGH_PROCESS_CPU_LOAD_MESSAGE_WITHOUT_PARAM)
|
||||
.build();
|
||||
} else if (loadAverage > (availableProcessors * loadAverageThreshold)) {
|
||||
LOGGER.error(HIGH_LOAD_AVERAGE_MESSAGE, loadAverage);
|
||||
return Health.up()
|
||||
.withDetails(details)
|
||||
.withDetail(ERROR_MESSAGE, HIGH_LOAD_AVERAGE_MESSAGE_WITHOUT_PARAM)
|
||||
.build();
|
||||
} else {
|
||||
return Health.up().withDetails(details).build();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### CustomHealthIndicator
|
||||
Un indicador de estado personalizado que comprueba periódicamente el estado de una base de datos y almacena en caché el resultado. Aprovecha un comprobador de estado asíncrono para realizar las comprobaciones de estado.
|
||||
|
||||
- `AsynchronousHealthChecker`: Un componente para realizar comprobaciones de estado de forma asíncrona.
|
||||
- `CacheManager`: Gestiona el almacenamiento en caché de los resultados de los controles de salud.
|
||||
- `HealthCheckRepository`: Un repositorio para consultar datos relacionados con la salud desde la base de datos.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Perform a health check and cache the result.
|
||||
*
|
||||
* @return the health status of the application
|
||||
* @throws HealthCheckInterruptedException if the health check is interrupted
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(value = "health-check", unless = "#result.status == 'DOWN'")
|
||||
public Health health() {
|
||||
LOGGER.info("Performing health check");
|
||||
CompletableFuture<Health> healthFuture =
|
||||
healthChecker.performCheck(this::check, timeoutInSeconds);
|
||||
try {
|
||||
return healthFuture.get(timeoutInSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOGGER.error("Health check interrupted", e);
|
||||
throw new HealthCheckInterruptedException(e);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Health check failed", e);
|
||||
return Health.down(e).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the health of the database by querying for a simple constant value expected from the
|
||||
* database.
|
||||
*
|
||||
* @return Health indicating UP if the database returns the constant correctly, otherwise DOWN.
|
||||
*/
|
||||
private Health check() {
|
||||
Integer result = healthCheckRepository.checkHealth();
|
||||
boolean databaseIsUp = result != null && result == 1;
|
||||
LOGGER.info("Health check result: {}", databaseIsUp);
|
||||
return databaseIsUp
|
||||
? Health.up().withDetail("database", "reachable").build()
|
||||
: Health.down().withDetail("database", "unreachable").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all entries from the health check cache. This is scheduled to run at a fixed rate
|
||||
* defined in the application properties.
|
||||
*/
|
||||
@Scheduled(fixedRateString = "${health.check.cache.evict.interval:60000}")
|
||||
public void evictHealthCache() {
|
||||
LOGGER.info("Evicting health check cache");
|
||||
try {
|
||||
Cache healthCheckCache = cacheManager.getCache("health-check");
|
||||
LOGGER.info("Health check cache: {}", healthCheckCache);
|
||||
if (healthCheckCache != null) {
|
||||
healthCheckCache.clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to evict health check cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### DatabaseTransactionHealthIndicator
|
||||
Un indicador de salud que comprueba la salud de las transacciones de la base de datos intentando realizar una transacción de prueba utilizando un mecanismo de reintento.
|
||||
|
||||
- Repositorio de comprobaciones de estado**: Un repositorio para realizar comprobaciones de salud en la base de datos.
|
||||
- AsynchronousHealthChecker**: Un comprobador de salud asíncrono utilizado para ejecutar comprobaciones de salud en un hilo independiente.
|
||||
- Plantilla de reintento**: Una plantilla de reintento utilizada para reintentar la transacción de prueba si falla debido a un error transitorio.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Performs a health check by attempting to perform a test transaction with retry support.
|
||||
*
|
||||
* @return the health status of the database transactions
|
||||
*/
|
||||
@Override
|
||||
public Health health() {
|
||||
LOGGER.info("Calling performCheck with timeout {}", timeoutInSeconds);
|
||||
Supplier<Health> dbTransactionCheck =
|
||||
() -> {
|
||||
try {
|
||||
healthCheckRepository.performTestTransaction();
|
||||
return Health.up().build();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Database transaction health check failed", e);
|
||||
return Health.down(e).build();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return asynchronousHealthChecker.performCheck(dbTransactionCheck, timeoutInSeconds).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOGGER.error("Database transaction health check timed out or was interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return Health.down(e).build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### GarbageCollectionHealthIndicator
|
||||
Un indicador de salud personalizado que comprueba el estado de recogida de basura de la aplicación e informa del estado de salud en consecuencia.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Performs a health check by gathering garbage collection metrics and evaluating the overall
|
||||
* health of the garbage collection system.
|
||||
*
|
||||
* @return a {@link Health} object representing the health status of the garbage collection system
|
||||
*/
|
||||
@Override
|
||||
public Health health() {
|
||||
List<GarbageCollectorMXBean> gcBeans = getGarbageCollectorMxBeans();
|
||||
List<MemoryPoolMXBean> memoryPoolMxBeans = getMemoryPoolMxBeans();
|
||||
Map<String, Map<String, String>> gcDetails = new HashMap<>();
|
||||
|
||||
for (GarbageCollectorMXBean gcBean : gcBeans) {
|
||||
Map<String, String> collectorDetails = createCollectorDetails(gcBean, memoryPoolMxBeans);
|
||||
gcDetails.put(gcBean.getName(), collectorDetails);
|
||||
}
|
||||
return Health.up().withDetails(gcDetails).build();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### MemoryHealthIndicator
|
||||
Un indicador de salud personalizado que comprueba el uso de memoria de la aplicación e informa del estado de salud en consecuencia.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Performs a health check by checking the memory usage of the application.
|
||||
*
|
||||
* @return the health status of the application
|
||||
*/
|
||||
public Health checkMemory() {
|
||||
Supplier<Health> memoryCheck =
|
||||
() -> {
|
||||
MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
|
||||
MemoryUsage heapMemoryUsage = memoryMxBean.getHeapMemoryUsage();
|
||||
long maxMemory = heapMemoryUsage.getMax();
|
||||
long usedMemory = heapMemoryUsage.getUsed();
|
||||
|
||||
double memoryUsage = (double) usedMemory / maxMemory;
|
||||
String format = String.format("%.2f%% of %d max", memoryUsage * 100, maxMemory);
|
||||
|
||||
if (memoryUsage < memoryThreshold) {
|
||||
LOGGER.info("Memory usage is below threshold: {}", format);
|
||||
return Health.up().withDetail("memory usage", format).build();
|
||||
} else {
|
||||
return Health.down().withDetail("memory usage", format).build();
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
CompletableFuture<Health> future =
|
||||
asynchronousHealthChecker.performCheck(memoryCheck, timeoutInSeconds);
|
||||
return future.get();
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Health check interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return Health.down().withDetail("error", "Health check interrupted").build();
|
||||
} catch (ExecutionException e) {
|
||||
LOGGER.error("Health check failed", e);
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
return Health.down().withDetail("error", cause.toString()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the health status of the application by checking the memory usage.
|
||||
*
|
||||
* @return the health status of the application
|
||||
*/
|
||||
@Override
|
||||
public Health health() {
|
||||
return checkMemory();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Usando Spring Boot Actuator para Comprobaciones de Salud
|
||||
Spring Boot Actuator proporciona una funcionalidad de comprobación de salud incorporada que puede integrarse fácilmente en su aplicación. Añadiendo la dependencia Spring Boot Actuator, puede exponer la información de comprobación de estado a través de un punto final predefinido, normalmente `/actuator/health`.
|
||||
|
||||
## Salida
|
||||
Esto muestra la salida del patrón de comprobación de salud utilizando una petición GET al punto final de salud de Actuator.
|
||||
|
||||
### HTTP GET Request
|
||||
```
|
||||
curl -X GET "http://localhost:6161/actuator/health"
|
||||
```
|
||||
|
||||
### Output
|
||||
```json
|
||||
{
|
||||
"status": "UP",
|
||||
"components": {
|
||||
"cpu": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"processCpuLoad": "0.03%",
|
||||
"availableProcessors": 10,
|
||||
"systemCpuLoad": "21.40%",
|
||||
"loadAverage": 3.3916015625,
|
||||
"timestamp": "2023-12-03T08:44:19.488422Z"
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"database": "reachable"
|
||||
}
|
||||
},
|
||||
"databaseTransaction": {
|
||||
"status": "UP"
|
||||
},
|
||||
"db": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"database": "H2",
|
||||
"validationQuery": "isValid()"
|
||||
}
|
||||
},
|
||||
"diskSpace": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"total": 994662584320,
|
||||
"free": 377635827712,
|
||||
"threshold": 10485760,
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
"garbageCollection": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"G1 Young Generation": {
|
||||
"count": "11",
|
||||
"time": "30ms",
|
||||
"memoryPools": "G1 Old Gen: 0.005056262016296387%"
|
||||
},
|
||||
"G1 Old Generation": {
|
||||
"count": "0",
|
||||
"time": "0ms",
|
||||
"memoryPools": "G1 Old Gen: 0.005056262016296387%"
|
||||
}
|
||||
}
|
||||
},
|
||||
"livenessState": {
|
||||
"status": "UP"
|
||||
},
|
||||
"memory": {
|
||||
"status": "UP",
|
||||
"details": {
|
||||
"memory usage": "1.36% of 4294967296 max"
|
||||
}
|
||||
},
|
||||
"ping": {
|
||||
"status": "UP"
|
||||
},
|
||||
"readinessState": {
|
||||
"status": "UP"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"liveness",
|
||||
"readiness"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el Patrón de Comprobación de Salud cuando:
|
||||
- Tienes una aplicación compuesta por múltiples servicios y necesitas monitorizar la salud de cada servicio individualmente.
|
||||
- Desea implementar la recuperación o sustitución automática de servicios basándose en el estado de salud.
|
||||
- Está empleando herramientas de orquestación o automatización que se basan en comprobaciones de estado para gestionar instancias de servicio.
|
||||
|
||||
## Tutoriales
|
||||
- Implementación de Health Checks en Java usando Spring Boot Actuator.
|
||||
|
||||
## Usos conocidos
|
||||
- Kubernetes Liveness y Readiness Probes
|
||||
- Comprobaciones de estado de AWS Elastic Load Balancing
|
||||
- Actuador Spring Boot
|
||||
|
||||
## Consecuencias
|
||||
**Pros:**
|
||||
- Mejora la tolerancia a fallos del sistema detectando fallos y permitiendo una rápida recuperación.
|
||||
- Mejora la visibilidad del estado del sistema para la supervisión operativa y las alertas.
|
||||
|
||||
**Contras:**
|
||||
- Añade complejidad a la implementación del servicio.
|
||||
- Requiere una estrategia para gestionar los fallos en cascada cuando los servicios dependientes no están en buen estado.
|
||||
|
||||
## Patrones relacionados
|
||||
- Circuit Breaker
|
||||
- Retry Pattern
|
||||
- Timeout Pattern
|
||||
|
||||
## Créditos
|
||||
Inspirado en el patrón Health Check API de [microservices.io](https://microservices.io/patterns/observability/health-check-api.html), y el tema [#2695](https://github.com/iluwatar/java-design-patterns/issues/2695) en el repositorio de patrones de diseño Java de iluwatar.
|
||||
|
After Width: | Height: | Size: 158 KiB |
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: Identity Map
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Performance
|
||||
- Data access
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Garantiza que cada objeto se cargue una sola vez guardando cada objeto cargado en un mapa.
|
||||
Busca objetos utilizando el mapa cuando se refiere a ellos.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Estamos escribiendo un programa que el usuario puede utilizar para encontrar los registros de una persona determinada en una base de datos.
|
||||
|
||||
En palabras simples
|
||||
|
||||
> Construir un mapa de identidades que almacene los registros de los elementos buscados recientemente en la base de datos. Cuando busquemos el mismo registro la próxima vez lo cargaremos desde el mapa no iremos a la base de datos.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En el diseño de DBMS, el patrón de mapa de identidad es un patrón de diseño de acceso a base de datos utilizado para mejorar el rendimiento, proporcionando un contexto específico, en la memoria caché para evitar la recuperación duplicada de los mismos datos de objetos de la base de datos
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
* Para el propósito de esta demostración supongamos que ya hemos creado una instancia de base de datos **db**.
|
||||
* Veamos primero la implementación de una entidad persona y sus campos:
|
||||
|
||||
```java
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public final class Person implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@EqualsAndHashCode.Include
|
||||
private int personNationalId;
|
||||
private String name;
|
||||
private long phoneNum;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* La siguiente es la implementación del personFinder que es la entidad que el usuario utilizará para buscar un registro en nuestra base de datos. Tiene adjunta la BD correspondiente. También mantiene un IdentityMap para almacenar los registros leídos recientemente.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
public class PersonFinder {
|
||||
private static final long serialVersionUID = 1L;
|
||||
// Access to the Identity Map
|
||||
private IdentityMap identityMap = new IdentityMap();
|
||||
private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation();
|
||||
/**
|
||||
* get person corresponding to input ID.
|
||||
*
|
||||
* @param key : personNationalId to look for.
|
||||
*/
|
||||
public Person getPerson(int key) {
|
||||
// Try to find person in the identity map
|
||||
Person person = this.identityMap.getPerson(key);
|
||||
if (person != null) {
|
||||
LOGGER.info("Person found in the Map");
|
||||
return person;
|
||||
} else {
|
||||
// Try to find person in the database
|
||||
person = this.db.find(key);
|
||||
if (person != null) {
|
||||
this.identityMap.addPerson(person);
|
||||
LOGGER.info("Person found in DB.");
|
||||
return person;
|
||||
}
|
||||
LOGGER.info("Person with this ID does not exist.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* El campo de mapa de identidad en la clase anterior es simplemente una abstracción de un hashMap con **personNationalId** como claves y el objeto persona correspondiente como valor. Aquí está su implementación:
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class IdentityMap {
|
||||
private Map<Integer, Person> personMap = new HashMap<>();
|
||||
/**
|
||||
* Add person to the map.
|
||||
*/
|
||||
public void addPerson(Person person) {
|
||||
if (!personMap.containsKey(person.getPersonNationalId())) {
|
||||
personMap.put(person.getPersonNationalId(), person);
|
||||
} else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes.
|
||||
LOGGER.info("Key already in Map");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Person with given id.
|
||||
*
|
||||
* @param id : personNationalId as requested by user.
|
||||
*/
|
||||
public Person getPerson(int id) {
|
||||
Person person = personMap.get(id);
|
||||
if (person == null) {
|
||||
LOGGER.info("ID not in Map.");
|
||||
}
|
||||
return person;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the map.
|
||||
*/
|
||||
public int size() {
|
||||
if (personMap == null) {
|
||||
return 0;
|
||||
}
|
||||
return personMap.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Ahora debemos construir una persona ficticia para fines de demostración y poner a esa persona en nuestra base de datos.
|
||||
|
||||
```java
|
||||
Person person1 = new Person(1, "John", 27304159);
|
||||
db.insert(person1);
|
||||
```
|
||||
|
||||
* Ahora vamos a crear un objeto person finder y buscar a la persona con personNationalId = 1(supongamos que el objeto personFinder ya tiene la db y un IdentityMap adjunto):
|
||||
|
||||
```java
|
||||
PersonFinder finder = new PersonFinder();
|
||||
finder.getPerson(1);
|
||||
```
|
||||
|
||||
* En esta etapa este registro se cargará desde la base de datos y la salida sería:
|
||||
|
||||
```java
|
||||
ID not in Map.
|
||||
Person ID is:1;Person Name is:John;Phone Number is:27304159
|
||||
Person found in DB.
|
||||
```
|
||||
|
||||
* Sin embargo, la próxima vez que busquemos de nuevo este registro lo encontraremos en el mapa, por lo que no necesitaremos ir a la base de datos.
|
||||
|
||||
```java
|
||||
Person ID is:1;Person Name is:John;Phone Number is:27304159
|
||||
Person found in Map.
|
||||
```
|
||||
|
||||
* Si el registro correspondiente no está en la base de datos, se lanza una excepción. Esta es su implementación.
|
||||
|
||||
```java
|
||||
public class IdNotFoundException extends RuntimeException {
|
||||
public IdNotFoundException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
* La idea detrás del patrón Identity Map es que cada vez que leemos un registro de la base de datos, primero comprobamos el Identity Map para ver si el registro ya ha sido recuperado. Esto nos permite simplemente devolver una nueva referencia al registro en memoria en lugar de crear un nuevo objeto, manteniendo la integridad referencial.
|
||||
* Una ventaja secundaria del Mapa de Identidad es que, al actuar como caché, reduce el número de llamadas a la base de datos necesarias para recuperar objetos, lo que supone una mejora del rendimiento.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Identity Map](https://www.sourcecodeexamples.net/2018/04/identity-map-pattern.html)
|
||||
|
After Width: | Height: | Size: 81 KiB |
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: Intercepting Filter
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## Propósito
|
||||
Un filtro de intercepción es un útil patrón de diseño Java que se utiliza cuando se desea preprocesar o postprocesar una petición en una aplicación. Estos filtros se crean y se aplican a la solicitud antes de que se le da a la aplicación de destino. Algunos ejemplos de uso incluyen la autenticación, que es necesario procesar antes de que la solicitud se entregue a la aplicación.
|
||||
|
||||
## Explicación
|
||||
Ejemplo del mundo real
|
||||
> Un ejemplo de uso del patrón de diseño Filtro Interceptor es relevante a la hora de realizar una plataforma de comercio electrónico. Es importante implementar varios filtros para la autenticación de la cuenta, la autenticación del pago, el registro y el almacenamiento en caché. Los tipos de filtros importantes en este ejemplo son los filtros de autenticación, registro, seguridad y almacenamiento en caché.
|
||||
|
||||
En palabras sencillas
|
||||
> Un filtro de intercepción en Java es como una serie de puntos de control de seguridad para peticiones y respuestas en una aplicación de software. Comprueba y procesa los datos a medida que entran y salen, ayudando con tareas como la autenticación, el registro y la seguridad, mientras mantiene el núcleo de la aplicación seguro y limpio.
|
||||
|
||||
Wikipedia dice
|
||||
> El filtro de intercepción es un patrón de Java que crea filtros conectables para procesar servicios comunes de una manera estándar sin necesidad de realizar cambios en el código de procesamiento de solicitudes.
|
||||
|
||||
## Ejemplo Programático
|
||||
Como ejemplo, podemos crear una clase Filtro básica y definir un Filtro de Autenticación. Al filtro le falta lógica, pero
|
||||
|
||||
```java
|
||||
// 1. Define a Filter interface
|
||||
interface Filter {
|
||||
void runFilter(String request);
|
||||
}
|
||||
// 2. Create a Authentication filter
|
||||
class AuthenticationFilter implements Filter {
|
||||
public void runFilter(String request) {
|
||||
// Authentication logic would be passed in here
|
||||
if (request.contains("authenticated=true")) {
|
||||
System.out.println("Authentication successful for request: " + request);
|
||||
} else {
|
||||
System.out.println("Authentication failed for request: " + request);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. Create a Client to send requests and activate the filter
|
||||
class Client {
|
||||
// create an instance of the filter in the Client class
|
||||
private Filter filter;
|
||||
|
||||
// create constructor
|
||||
public Client(Filter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
// send the String request to the filter, the request does not have to be a string
|
||||
// it can be anything
|
||||
public void sendRequest(String request) {
|
||||
filter.runFilter(request);
|
||||
}
|
||||
}
|
||||
// 4. Demonstrate the Authentication Filter
|
||||
public class AuthenticationFilterExample {
|
||||
public static void main(String[] args) {
|
||||
Filter authenticationFilter = new AuthenticationFilter();
|
||||
Client client = new Client(authenticationFilter);
|
||||
|
||||
// Simulate requests for false
|
||||
client.sendRequest("GET /public-page");
|
||||
// this request would come back as true as the link includes an argument
|
||||
// for successful authentication
|
||||
client.sendRequest("GET /private-page?authenticated=true");
|
||||
}
|
||||
}
|
||||
```
|
||||
Este es un ejemplo básico de cómo implementar el esqueleto de un filtro. Falta la lógica de autenticación en AuthenticationFilterExample, pero se puede rellenar en los huecos.
|
||||
|
||||
Además, el cliente puede ser configurado para ejecutar múltiples filtros en su solicitud utilizando un bucle For poblado con filtros como se puede ver a continuación:
|
||||
|
||||
```java
|
||||
// 1. Define a Filter interface
|
||||
interface Filter {
|
||||
void runFilter(String request);
|
||||
}
|
||||
|
||||
// 2. Create an Authentication filter
|
||||
class AuthenticationFilter implements Filter {
|
||||
public void runFilter(String request) {
|
||||
// Authentication logic would be placed here
|
||||
if (request.contains("authenticated=true")) {
|
||||
System.out.println("Authentication successful for request: " + request);
|
||||
} else {
|
||||
System.out.println("Authentication failed for request: " + request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Create a Client to send requests and activate multiple filters
|
||||
class Client {
|
||||
// create a list of filters in the Client class
|
||||
private List<Filter> filters = new ArrayList<>();
|
||||
|
||||
// add filters to the list
|
||||
public void addFilter(Filter filter) {
|
||||
filters.add(filter);
|
||||
}
|
||||
|
||||
// send the request through all the filters
|
||||
public void sendRequest(String request) {
|
||||
for (Filter filter : filters) {
|
||||
filter.runFilter(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Demonstrate multiple filters
|
||||
public class MultipleFiltersExample {
|
||||
public static void main(String[] args) {
|
||||
// Create a client
|
||||
Client client = new Client();
|
||||
|
||||
// Add filters to the client
|
||||
Filter authenticationFilter = new AuthenticationFilter();
|
||||
client.addFilter(authenticationFilter);
|
||||
|
||||
// Add more filters as needed
|
||||
// Filter anotherFilter = new AnotherFilter();
|
||||
// client.addFilter(anotherFilter);
|
||||
|
||||
// Simulate requests
|
||||
client.sendRequest("GET /public-page");
|
||||
client.sendRequest("GET /private-page?authenticated=true");
|
||||
}
|
||||
}
|
||||
```
|
||||
Este método permite manipular y comprobar los datos de forma rápida y sencilla antes de autenticar un inicio de sesión o finalizar otro tipo de acción.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón Filtro interceptor cuando
|
||||
|
||||
* Un programa necesita preprocesar o postprocesar datos
|
||||
* Un sistema necesita servicios de autorización/autenticación para acceder a datos sensibles
|
||||
* Desea registrar/auditar peticiones o respuestas con fines de depuración o almacenamiento, como marcas de tiempo y acciones del usuario
|
||||
* Desea transformar datos de un tipo a otro antes de entregarlos al proceso final.
|
||||
* Desea implementar un manejo específico de excepciones
|
||||
|
||||
## Consecuencias
|
||||
Consecuencias de la aplicación del filtro de interceptación
|
||||
|
||||
* Aumento de la complejidad del código, disminuyendo la facilidad de lectura
|
||||
* Puede haber problemas en el orden en que se aplican los filtros si el orden es importante
|
||||
* Aplicar múltiples filtros a una petición puede crear un retraso en el tiempo de respuesta
|
||||
* Probar los efectos de múltiples filtros en una petición puede ser difícil.
|
||||
* La compatibilidad y la gestión de versiones pueden ser difíciles si se tienen muchos filtros.
|
||||
|
||||
## Tutoriales
|
||||
|
||||
* [Introduction to Intercepting Filter Pattern in Java](https://www.baeldung.com/intercepting-filter-pattern-in-java)
|
||||
|
||||
## Ejemplos del mundo real
|
||||
|
||||
* [javax.servlet.FilterChain](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/FilterChain.html) and [javax.servlet.Filter](https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/Filter.html)
|
||||
* [Struts 2 - Interceptors](https://struts.apache.org/core-developers/interceptors.html)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [TutorialsPoint - Intercepting Filter](http://www.tutorialspoint.com/design_pattern/intercepting_filter_pattern.htm)
|
||||
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1,169 @@
|
||||
---
|
||||
title: Interpreter
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Dado un lenguaje, defina una representación para su gramática junto con un intérprete que utilice la
|
||||
representación para interpretar las frases del lenguaje.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Los niños enanos están aprendiendo matemáticas básicas en la escuela. Empiezan por lo más básico: "1 + 1", "4 - 2", "5 + 5", etcétera.
|
||||
|
||||
En palabras simples
|
||||
|
||||
> El patrón intérprete interpreta las frases en el idioma deseado.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En programación informática, el patrón intérprete es un patrón de diseño que especifica cómo evaluar sentencias en un lenguaje. La idea básica es tener una clase para cada símbolo (terminal o no terminal) en un lenguaje informático especializado. El árbol sintáctico de una sentencia en el lenguaje es una instancia del patrón compuesto y se utiliza para evaluar (interpretar) la sentencia para un cliente.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Para poder interpretar matemáticas básicas, necesitamos una jerarquía de expresiones. La abstracción básica para ello es la clase `Expression`.
|
||||
|
||||
```java
|
||||
public abstract class Expression {
|
||||
|
||||
public abstract int interpret();
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
```
|
||||
|
||||
La más sencilla de las expresiones es la `NumberExpression` que contiene un único número entero.
|
||||
|
||||
```java
|
||||
public class NumberExpression extends Expression {
|
||||
|
||||
private final int number;
|
||||
|
||||
public NumberExpression(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public NumberExpression(String s) {
|
||||
this.number = Integer.parseInt(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int interpret() {
|
||||
return number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "number";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Las expresiones más complejas son operaciones como `PlusExpression`, `MinusExpression` y
|
||||
`MultiplicarExpresión`. Aquí está la primera de ellas, las otras son similares.
|
||||
|
||||
```java
|
||||
public class PlusExpression extends Expression {
|
||||
|
||||
private final Expression leftExpression;
|
||||
private final Expression rightExpression;
|
||||
|
||||
public PlusExpression(Expression leftExpression, Expression rightExpression) {
|
||||
this.leftExpression = leftExpression;
|
||||
this.rightExpression = rightExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int interpret() {
|
||||
return leftExpression.interpret() + rightExpression.interpret();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "+";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ahora podemos mostrar el patrón del intérprete en acción analizando algunas matemáticas sencillas.
|
||||
|
||||
```java
|
||||
// the halfling kids are learning some basic math at school
|
||||
// define the math string we want to parse
|
||||
final var tokenString = "4 3 2 - 1 + *";
|
||||
|
||||
// the stack holds the parsed expressions
|
||||
var stack = new Stack<Expression>();
|
||||
|
||||
// tokenize the string and go through them one by one
|
||||
var tokenList = tokenString.split(" ");
|
||||
for (var s : tokenList) {
|
||||
if (isOperator(s)) {
|
||||
// when an operator is encountered we expect that the numbers can be popped from the top of
|
||||
// the stack
|
||||
var rightExpression = stack.pop();
|
||||
var leftExpression = stack.pop();
|
||||
LOGGER.info("popped from stack left: {} right: {}",
|
||||
leftExpression.interpret(), rightExpression.interpret());
|
||||
var operator = getOperatorInstance(s, leftExpression, rightExpression);
|
||||
LOGGER.info("operator: {}", operator);
|
||||
var result = operator.interpret();
|
||||
// the operation result is pushed on top of the stack
|
||||
var resultExpression = new NumberExpression(result);
|
||||
stack.push(resultExpression);
|
||||
LOGGER.info("push result to stack: {}", resultExpression.interpret());
|
||||
} else {
|
||||
// numbers are pushed on top of the stack
|
||||
var i = new NumberExpression(s);
|
||||
stack.push(i);
|
||||
LOGGER.info("push to stack: {}", i.interpret());
|
||||
}
|
||||
}
|
||||
// in the end, the final result lies on top of the stack
|
||||
LOGGER.info("result: {}", stack.pop().interpret());
|
||||
```
|
||||
|
||||
La ejecución del programa produce la siguiente salida de consola.
|
||||
|
||||
```
|
||||
popped from stack left: 1 right: 1
|
||||
operator: +
|
||||
push result to stack: 2
|
||||
popped from stack left: 4 right: 2
|
||||
operator: *
|
||||
push result to stack: 8
|
||||
result: 8
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Intérprete cuando exista un lenguaje que interpretar, y pueda representar las sentencias
|
||||
del lenguaje como árboles sintácticos abstractos. El patrón Intérprete funciona mejor cuando
|
||||
|
||||
* La gramática es simple. Para gramáticas complejas, la jerarquía de clases para la gramática se hace grande e inmanejable. Herramientas como los generadores de analizadores sintácticos son una mejor alternativa en estos casos. Pueden interpretar expresiones sin construir árboles sintácticos abstractos, lo que puede ahorrar espacio y posiblemente tiempo.
|
||||
* La eficiencia no es una preocupación crítica. Por lo general, los intérpretes más eficientes no se implementan interpretando directamente los árboles de análisis sintáctico, sino traduciéndolos primero a otra forma. Por ejemplo, las expresiones regulares suelen transformarse en máquinas de estados. Pero incluso entonces, el traductor puede ser implementado por el patrón Intérprete, por lo que el patrón sigue siendo aplicable.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)
|
||||
* [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html)
|
||||
* Todas las subclases de [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html)
|
||||
* [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html)
|
||||
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,835 @@
|
||||
---
|
||||
title: Leader Election
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## Propósito
|
||||
El patrón de elección (Leader pattern) del líder se utiliza habitualmente en el diseño de sistemas en la nube. Puede ayudar a garantizar que las instancias de tarea seleccionen la instancia líder correctamente y no entren en conflicto entre sí, causen contención por recursos compartidos o interfieran inadvertidamente con el trabajo que otras instancias de tarea están realizando.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
> En un sistema basado en la nube de escalado horizontal, múltiples instancias de la misma tarea podrían estar ejecutándose al mismo tiempo con cada instancia sirviendo a un usuario diferente. Si estas instancias escriben en un recurso compartido, es necesario coordinar sus acciones para evitar que cada instancia sobrescriba los cambios realizados por las demás. En otro escenario, si las tareas están realizando elementos individuales de un cálculo complejo en paralelo, los resultados necesitan ser agregados cuando todos ellos se completan.
|
||||
|
||||
En palabras sencillas
|
||||
> este patrón se utiliza en sistemas distribuidos basados en la nube en los que el líder debe actuar como coordinador/agregador entre instancias de escalado horizontal para evitar conflictos en los recursos compartidos.
|
||||
|
||||
Wikipedia dice
|
||||
> En computación distribuida, la elección del líder es el proceso de designar un único proceso como organizador de alguna tarea distribuida entre varios ordenadores (nodos).
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
El algoritmo Ring y el algoritmo Bully implementan el patrón de elección de líder.
|
||||
Cada algoritmo requiere que cada instancia participante tenga un ID único.
|
||||
|
||||
```java
|
||||
public interface Instance {
|
||||
|
||||
/**
|
||||
* Comprueba si la instancia está viva o no.
|
||||
*
|
||||
* @return {@code true} si la instancia está viva.
|
||||
*/
|
||||
boolean isAlive();
|
||||
|
||||
/**
|
||||
* Establece el estado de salud de la instancia en cuestión.
|
||||
*
|
||||
* @param alive {@code true} por vivo.
|
||||
*/
|
||||
void setAlive(boolean alive);
|
||||
|
||||
/**
|
||||
* Consumir mensajes de otras instancias.
|
||||
*
|
||||
* @param message Mensaje enviado por otras instancias
|
||||
*/
|
||||
void onMessage(Message message);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Clase abstracta que implementa las interfaces Instance y Runnable.
|
||||
|
||||
```java
|
||||
public abstract class AbstractInstance implements Instance, Runnable {
|
||||
|
||||
protected static final int HEARTBEAT_INTERVAL = 5000;
|
||||
private static final String INSTANCE = "Instance ";
|
||||
|
||||
protected MessageManager messageManager;
|
||||
protected Queue<Message> messageQueue;
|
||||
protected final int localId;
|
||||
protected int leaderId;
|
||||
protected boolean alive;
|
||||
|
||||
/**
|
||||
* Constructor de BullyInstance.
|
||||
*/
|
||||
public AbstractInstance(MessageManager messageManager, int localId, int leaderId) {
|
||||
this.messageManager = messageManager;
|
||||
this.messageQueue = new ConcurrentLinkedQueue<>();
|
||||
this.localId = localId;
|
||||
this.leaderId = leaderId;
|
||||
this.alive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* La instancia ejecutará el mensaje en su cola de mensajes periódicamente una vez que esté viva.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("squid:S2189")
|
||||
public void run() {
|
||||
while (true) {
|
||||
if (!this.messageQueue.isEmpty()) {
|
||||
this.processMessage(this.messageQueue.remove());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Una vez que los mensajes son enviados a una instancia determinada, se añaden a la cola y esperan a ser ejecutados.
|
||||
*
|
||||
* @param message Mensaje enviado por otras instancias
|
||||
*/
|
||||
@Override
|
||||
public void onMessage(Message message) {
|
||||
messageQueue.offer(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprueba si la instancia está viva o no.
|
||||
*
|
||||
* @return {@code true} si la instancia está viva.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establece el estado de salud de la instancia en cuestión.
|
||||
*
|
||||
* @param alive {@code true} por vivo.
|
||||
*/
|
||||
@Override
|
||||
public void setAlive(boolean alive) {
|
||||
this.alive = alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa el mensaje según su tipo.
|
||||
*
|
||||
* @param message Mensaje sondeado desde la cola.
|
||||
*/
|
||||
private void processMessage(Message message) {
|
||||
switch (message.getType()) {
|
||||
case ELECTION -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Election Message handling...");
|
||||
handleElectionMessage(message);
|
||||
}
|
||||
case LEADER -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Leader Message handling...");
|
||||
handleLeaderMessage(message);
|
||||
}
|
||||
case HEARTBEAT -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Heartbeat Message handling...");
|
||||
handleHeartbeatMessage(message);
|
||||
}
|
||||
case ELECTION_INVOKE -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Election Invoke Message handling...");
|
||||
handleElectionInvokeMessage();
|
||||
}
|
||||
case LEADER_INVOKE -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Leader Invoke Message handling...");
|
||||
handleLeaderInvokeMessage();
|
||||
}
|
||||
case HEARTBEAT_INVOKE -> {
|
||||
LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling...");
|
||||
handleHeartbeatInvokeMessage();
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Métodos abstractos para manejar diferentes tipos de mensajes.
|
||||
* Estos métodos deben implementarse en la clase de instancia concreta para implementar el patrón de selección de líder correspondiente.
|
||||
*/
|
||||
protected abstract void handleElectionMessage(Message message);
|
||||
|
||||
protected abstract void handleElectionInvokeMessage();
|
||||
|
||||
protected abstract void handleLeaderMessage(Message message);
|
||||
|
||||
protected abstract void handleLeaderInvokeMessage();
|
||||
|
||||
protected abstract void handleHeartbeatMessage(Message message);
|
||||
|
||||
protected abstract void handleHeartbeatInvokeMessage();
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* Mensaje utilizado para transportar datos entre instancias.
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Message {
|
||||
|
||||
private MessageType type;
|
||||
private String content;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public interface MessageManager {
|
||||
|
||||
/**
|
||||
* Envía un mensaje heartbeat a la instancia líder para comprobar si la instancia líder está viva.
|
||||
*
|
||||
* @param leaderId ID de instancia de la instancia líder.
|
||||
* @return {@code true} si la instancia líder está viva, o {@code false} si no.
|
||||
*/
|
||||
boolean sendHeartbeatMessage(int leaderId);
|
||||
|
||||
/**
|
||||
* Enviar mensaje electoral a otras instancias.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
* @param content Contenido del mensaje electoral.
|
||||
* @return {@code true} si el mensaje es aceptado por las instancias de destino.
|
||||
*/
|
||||
boolean sendElectionMessage(int currentId, String content);
|
||||
|
||||
/**
|
||||
* Enviar mensaje de notificación de nuevo líder a otras instancias.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
* @param leaderId Contenido del mensaje del líder.
|
||||
* @return {@code true} si el mensaje es aceptado por las instancias de destino.
|
||||
*/
|
||||
boolean sendLeaderMessage(int currentId, int leaderId);
|
||||
|
||||
/**
|
||||
* Enviar mensaje de invocación de heartbeat. Esto invocará la tarea heartbeat en la instancia de destino.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
*/
|
||||
void sendHeartbeatInvokeMessage(int currentId);
|
||||
|
||||
}
|
||||
```
|
||||
These type of messages are used to pass among instances.
|
||||
```java
|
||||
/**
|
||||
* Enum de tipo de mensaje.
|
||||
*/
|
||||
public enum MessageType {
|
||||
|
||||
/**
|
||||
* Comienza la elección. El contenido del mensaje almacena ID(s) de la(s) instancia(s) candidata(s).
|
||||
*/
|
||||
ELECTION,
|
||||
|
||||
/**
|
||||
* Nodifica al nuevo líder. El contenido del mensaje debe ser el ID del líder.
|
||||
*/
|
||||
LEADER,
|
||||
|
||||
/**
|
||||
* Comprueba la salud de la instancia de líder actual.
|
||||
*/
|
||||
HEARTBEAT,
|
||||
|
||||
/**
|
||||
* Informar a la instancia de destino para iniciar la elección.
|
||||
*/
|
||||
ELECTION_INVOKE,
|
||||
|
||||
/**
|
||||
* Informar a la instancia de destino para que notifique a todas las demás instancias que es el nuevo líder.
|
||||
*/
|
||||
LEADER_INVOKE,
|
||||
|
||||
/**
|
||||
* Informar a la instancia de destino para que inicie el heartbeat.
|
||||
*/
|
||||
HEARTBEAT_INVOKE
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Clase abstracta que implementa la interfaz MessageManager. Ayuda a gestionar mensajes entre instancias.
|
||||
|
||||
```java
|
||||
/**
|
||||
* Clase abstracta de todas las clases de gestores de mensajes.
|
||||
*/
|
||||
public abstract class AbstractMessageManager implements MessageManager {
|
||||
|
||||
/**
|
||||
* Contiene todas las instancias del sistema. La clave es su ID y el valor es la propia instancia.
|
||||
*/
|
||||
|
||||
protected Map<Integer, Instance> instanceMap;
|
||||
|
||||
/**
|
||||
* Constructor de AbstractMessageManager.
|
||||
*/
|
||||
public AbstractMessageManager(Map<Integer, Instance> instanceMap) {
|
||||
this.instanceMap = instanceMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encuentra la siguiente instancia con el ID más pequeño.
|
||||
*
|
||||
* @return La siguiente instancia.
|
||||
*/
|
||||
protected Instance findNextInstance(int currentId) {
|
||||
Instance result = null;
|
||||
var candidateList = instanceMap.keySet()
|
||||
.stream()
|
||||
.filter((i) -> i > currentId && instanceMap.get(i).isAlive())
|
||||
.sorted()
|
||||
.toList();
|
||||
if (candidateList.isEmpty()) {
|
||||
var index = instanceMap.keySet()
|
||||
.stream()
|
||||
.filter((i) -> instanceMap.get(i).isAlive())
|
||||
.sorted()
|
||||
.toList()
|
||||
.get(0);
|
||||
result = instanceMap.get(index);
|
||||
} else {
|
||||
var index = candidateList.get(0);
|
||||
result = instanceMap.get(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Here the implementation of Ring Algorithm
|
||||
```java
|
||||
/**
|
||||
* Implementación con algoritmo token ring. Las instancias en el sistema se organizan como un anillo.
|
||||
* Cada instancia debe tener un id secuencial y la instancia con el id más pequeño (o más grande) debe
|
||||
* ser el líder inicial. Todas las demás instancias envían un mensaje heartbeat al líder periódicamente para
|
||||
* comprobar su salud. Si una instancia determinada encuentra que el servidor ha terminado, enviará un mensaje de elección
|
||||
* a la siguiente instancia viva en el anillo, que contiene su propio ID. Entonces la siguiente instancia añade su
|
||||
* ID en el mensaje y se lo pasa a la siguiente. Después de que todos los ID de las instancias vivas se añaden al * mensaje, el mensaje se devuelve.
|
||||
* mensaje, el mensaje es enviado de vuelta a la primera instancia y ésta elegirá la instancia con
|
||||
* menor ID para ser el nuevo líder, y entonces enviará un mensaje de líder a otras instancias para informar del
|
||||
* resultado.
|
||||
*/
|
||||
@Slf4j
|
||||
public class RingInstance extends AbstractInstance {
|
||||
private static final String INSTANCE = "Instance ";
|
||||
|
||||
/**
|
||||
* Constructor de RingInstance.
|
||||
*/
|
||||
public RingInstance(MessageManager messageManager, int localId, int leaderId) {
|
||||
super(messageManager, localId, leaderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar el mensaje de invocación heartbeat. Después de recibir el mensaje, la instancia enviará un
|
||||
* heartbeat (latido) al líder para comprobar su salud. Si está vivo, informará a la siguiente instancia para hacer el
|
||||
* heartbeat. Si no, iniciará el proceso de elección.
|
||||
*/
|
||||
@Override
|
||||
protected void handleHeartbeatInvokeMessage() {
|
||||
try {
|
||||
var isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId);
|
||||
if (isLeaderAlive) {
|
||||
LOGGER.info(INSTANCE + localId + "- Leader is alive. Start next heartbeat in 5 second.");
|
||||
Thread.sleep(HEARTBEAT_INTERVAL);
|
||||
messageManager.sendHeartbeatInvokeMessage(this.localId);
|
||||
} else {
|
||||
LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election.");
|
||||
messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.info(INSTANCE + localId + "- Interrupted.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar mensaje de elección. Si el ID local está contenido en la lista de ID, la instancia seleccionará
|
||||
* la instancia viva con menor ID para ser el nuevo líder, y enviará el mensaje de información al líder.
|
||||
* Si no, añadirá su ID local a la lista y enviará el mensaje a la siguiente instancia en el anillo.
|
||||
* anillo.
|
||||
*/
|
||||
@Override
|
||||
protected void handleElectionMessage(Message message) {
|
||||
var content = message.getContent();
|
||||
LOGGER.info(INSTANCE + localId + " - Election Message: " + content);
|
||||
var candidateList = Arrays.stream(content.trim().split(","))
|
||||
.map(Integer::valueOf)
|
||||
.sorted()
|
||||
.toList();
|
||||
if (candidateList.contains(localId)) {
|
||||
var newLeaderId = candidateList.get(0);
|
||||
LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + ".");
|
||||
messageManager.sendLeaderMessage(localId, newLeaderId);
|
||||
} else {
|
||||
content += "," + localId;
|
||||
messageManager.sendElectionMessage(localId, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar mensaje de líder. La instancia establecerá el ID de líder para que sea el nuevo y enviará el
|
||||
* mensaje a la siguiente instancia hasta que todas las instancias vivas del anillo estén informadas.
|
||||
*/
|
||||
@Override
|
||||
protected void handleLeaderMessage(Message message) {
|
||||
var newLeaderId = Integer.valueOf(message.getContent());
|
||||
if (this.leaderId != newLeaderId) {
|
||||
LOGGER.info(INSTANCE + localId + " - Update leaderID");
|
||||
this.leaderId = newLeaderId;
|
||||
messageManager.sendLeaderMessage(localId, newLeaderId);
|
||||
} else {
|
||||
LOGGER.info(INSTANCE + localId + " - Leader update done. Start heartbeat.");
|
||||
messageManager.sendHeartbeatInvokeMessage(localId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No se utiliza en la instancia Ring.
|
||||
*/
|
||||
@Override
|
||||
protected void handleLeaderInvokeMessage() {
|
||||
// No se utiliza en la instancia Ring.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHeartbeatMessage(Message message) {
|
||||
// No se utiliza en la instancia Ring.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleElectionInvokeMessage() {
|
||||
// No se utiliza en la instancia Ring.
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* Implementación de RingMessageManager.
|
||||
*/
|
||||
public class RingMessageManager extends AbstractMessageManager {
|
||||
|
||||
/**
|
||||
* Constructor de RingMessageManager.
|
||||
*/
|
||||
public RingMessageManager(Map<Integer, Instance> instanceMap) {
|
||||
super(instanceMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía un mensaje de heartbeat a la instancia líder actual para comprobar su estado.
|
||||
*
|
||||
* @param leaderId leaderID
|
||||
* @return {@code true} si el líder está vivo.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendHeartbeatMessage(int leaderId) {
|
||||
var leaderInstance = instanceMap.get(leaderId);
|
||||
var alive = leaderInstance.isAlive();
|
||||
return alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de elección a la siguiente instancia.
|
||||
*
|
||||
* @param currentId currentID
|
||||
* @param content contiene todos los ID de las instancias que han recibido este mensaje de elección
|
||||
* mensaje.
|
||||
* @return {@code true} si el mensaje de elección es aceptado por la instancia de destino.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendElectionMessage(int currentId, String content) {
|
||||
var nextInstance = this.findNextInstance(currentId);
|
||||
var electionMessage = new Message(MessageType.ELECTION, content);
|
||||
nextInstance.onMessage(electionMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de líder a la siguiente instancia.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
* @param leaderId Contenido del mensaje del líder.
|
||||
* @return {@code true} si el mensaje de líder es aceptado por la instancia de destino.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendLeaderMessage(int currentId, int leaderId) {
|
||||
var nextInstance = this.findNextInstance(currentId);
|
||||
var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId));
|
||||
nextInstance.onMessage(leaderMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de invocación de heartbeat a la siguiente instancia.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
*/
|
||||
@Override
|
||||
public void sendHeartbeatInvokeMessage(int currentId) {
|
||||
var nextInstance = this.findNextInstance(currentId);
|
||||
var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, "");
|
||||
nextInstance.onMessage(heartbeatInvokeMessage);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
|
||||
Map<Integer, Instance> instanceMap = new HashMap<>();
|
||||
var messageManager = new RingMessageManager(instanceMap);
|
||||
|
||||
var instance1 = new RingInstance(messageManager, 1, 1);
|
||||
var instance2 = new RingInstance(messageManager, 2, 1);
|
||||
var instance3 = new RingInstance(messageManager, 3, 1);
|
||||
var instance4 = new RingInstance(messageManager, 4, 1);
|
||||
var instance5 = new RingInstance(messageManager, 5, 1);
|
||||
|
||||
instanceMap.put(1, instance1);
|
||||
instanceMap.put(2, instance2);
|
||||
instanceMap.put(3, instance3);
|
||||
instanceMap.put(4, instance4);
|
||||
instanceMap.put(5, instance5);
|
||||
|
||||
instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, ""));
|
||||
|
||||
final var thread1 = new Thread(instance1);
|
||||
final var thread2 = new Thread(instance2);
|
||||
final var thread3 = new Thread(instance3);
|
||||
final var thread4 = new Thread(instance4);
|
||||
final var thread5 = new Thread(instance5);
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread3.start();
|
||||
thread4.start();
|
||||
thread5.start();
|
||||
|
||||
instance1.setAlive(false);
|
||||
}
|
||||
```
|
||||
|
||||
The console output
|
||||
```
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Heartbeat Invoke Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2- Leader is not alive. Start election.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Election Message: 2
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Election Message: 2,3
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Election Message handling...
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Election Message: 2,3,4
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Election Message: 2,3,4,5
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - New leader should be 2.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Update leaderID
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4 - Update leaderID
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling...
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 5 - Update leaderID
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Leader Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 2 - Update leaderID
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 3 - Leader update done. Start heartbeat.
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.ring.RingInstance - Instance 4- Leader is alive. Start next heartbeat in 5 second.
|
||||
```
|
||||
Here the implementation of Bully algorithm
|
||||
```java
|
||||
/**
|
||||
* Impelemetación con algoritmo bully. Cada instancia debe tener un id secuencial y es capaz de
|
||||
* comunicarse con otras instancias del sistema. Inicialmente la instancia con menor (o mayor)
|
||||
* ID más pequeño (o más grande) es seleccionado para ser el líder. Todas las demás instancias envían un mensaje heartbeat al líder
|
||||
* periódicamente para comprobar su salud. Si una instancia determinada considera que el servidor ha terminado, enviará un mensaje de elección a todas las instancias del sistema.
|
||||
* mensaje de elección a todas las instancias cuyo ID sea mayor. Si la instancia objetivo está viva,
|
||||
* devolverá un mensaje alive (en este ejemplo devolverá true) y entonces enviará un mensaje de elección con
|
||||
* su ID. Si no, la instancia original enviará un mensaje de líder a todas las demás instancias.
|
||||
*/
|
||||
@Slf4j
|
||||
public class BullyInstance extends AbstractInstance {
|
||||
private static final String INSTANCE = "Instance ";
|
||||
|
||||
/**
|
||||
* Constructor de BullyInstance.
|
||||
*/
|
||||
public BullyInstance(MessageManager messageManager, int localId, int leaderId) {
|
||||
super(messageManager, localId, leaderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar el mensaje de invocación heartbeat. Después de recibir el mensaje, la instancia enviará un
|
||||
* heartbeat al líder para comprobar su salud. Si está vivo, informará a la siguiente instancia para hacer el
|
||||
* heartbeat. Si no, iniciará el proceso de elección.
|
||||
*/
|
||||
@Override
|
||||
protected void handleHeartbeatInvokeMessage() {
|
||||
try {
|
||||
boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId);
|
||||
if (isLeaderAlive) {
|
||||
LOGGER.info(INSTANCE + localId + "- Leader is alive.");
|
||||
Thread.sleep(HEARTBEAT_INTERVAL);
|
||||
messageManager.sendHeartbeatInvokeMessage(localId);
|
||||
} else {
|
||||
LOGGER.info(INSTANCE + localId + "- Leader is not alive. Start election.");
|
||||
boolean electionResult =
|
||||
messageManager.sendElectionMessage(localId, String.valueOf(localId));
|
||||
if (electionResult) {
|
||||
LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification.");
|
||||
messageManager.sendLeaderMessage(localId, localId);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.info(INSTANCE + localId + "- Interrupted.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar mensaje de invocación de elección. Enviar mensaje de elección a todas las instancias con menor ID. Si
|
||||
* alguna de ellas está viva, no hacer nada. Si ninguna instancia está viva, enviar mensaje de líder a todas las instancias vivas y reiniciar el heartbeat.
|
||||
* instancia viva y reiniciar heartbeat.
|
||||
*/
|
||||
@Override
|
||||
protected void handleElectionInvokeMessage() {
|
||||
if (!isLeader()) {
|
||||
LOGGER.info(INSTANCE + localId + "- Start election.");
|
||||
boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId));
|
||||
if (electionResult) {
|
||||
LOGGER.info(INSTANCE + localId + "- Succeed in election. Start leader notification.");
|
||||
leaderId = localId;
|
||||
messageManager.sendLeaderMessage(localId, localId);
|
||||
messageManager.sendHeartbeatInvokeMessage(localId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar mensaje de líder. Actualizar la información del líder local.
|
||||
*/
|
||||
@Override
|
||||
protected void handleLeaderMessage(Message message) {
|
||||
leaderId = Integer.valueOf(message.getContent());
|
||||
LOGGER.info(INSTANCE + localId + " - Leader update done.");
|
||||
}
|
||||
|
||||
private boolean isLeader() {
|
||||
return localId == leaderId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleLeaderInvokeMessage() {
|
||||
// Not used in Bully Instance
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleHeartbeatMessage(Message message) {
|
||||
// Not used in Bully Instance
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleElectionMessage(Message message) {
|
||||
// Not used in Bully Instance
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* Implementación de BullyMessageManager.
|
||||
*/
|
||||
public class BullyMessageManager extends AbstractMessageManager {
|
||||
|
||||
/**
|
||||
* Constructor de BullyMessageManager.
|
||||
*/
|
||||
public BullyMessageManager(Map<Integer, Instance> instanceMap) {
|
||||
super(instanceMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía un mensaje de heartbeat a la instancia líder actual para comprobar su estado.
|
||||
*
|
||||
* @param leaderId leaderID
|
||||
* @return {@code true} si el líder está vivo.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendHeartbeatMessage(int leaderId) {
|
||||
var leaderInstance = instanceMap.get(leaderId);
|
||||
var alive = leaderInstance.isAlive();
|
||||
return alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje electoral a todas las instancias con menor ID.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
* @param content Contenido del mensaje electoral.
|
||||
* @return {@code true} si ninguna instancia viva tiene menor ID, para que se acepte la elección.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendElectionMessage(int currentId, String content) {
|
||||
var candidateList = findElectionCandidateInstanceList(currentId);
|
||||
if (candidateList.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
var electionMessage = new Message(MessageType.ELECTION_INVOKE, "");
|
||||
candidateList.stream().forEach((i) -> instanceMap.get(i).onMessage(electionMessage));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de líder a todas las instancias para notificar el nuevo líder.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
* @param leaderId Contenido del mensaje del líder.
|
||||
* @return {@code true} si se acepta el mensaje.
|
||||
*/
|
||||
@Override
|
||||
public boolean sendLeaderMessage(int currentId, int leaderId) {
|
||||
var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId));
|
||||
instanceMap.keySet()
|
||||
.stream()
|
||||
.filter((i) -> i != currentId)
|
||||
.forEach((i) -> instanceMap.get(i).onMessage(leaderMessage));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar mensaje de invocación de heartbeat a la siguiente instancia.
|
||||
*
|
||||
* @param currentId ID de la instancia que envía este mensaje.
|
||||
*/
|
||||
@Override
|
||||
public void sendHeartbeatInvokeMessage(int currentId) {
|
||||
var nextInstance = this.findNextInstance(currentId);
|
||||
var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, "");
|
||||
nextInstance.onMessage(heartbeatInvokeMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encuentra todas las instancias vivas con un ID menor que la instancia actual.
|
||||
*
|
||||
* @param currentId ID de la instancia actual.
|
||||
* @return Lista de ID de todas las instancias candidatas.
|
||||
*/
|
||||
private List<Integer> findElectionCandidateInstanceList(int currentId) {
|
||||
return instanceMap.keySet()
|
||||
.stream()
|
||||
.filter((i) -> i < currentId && instanceMap.get(i).isAlive())
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
|
||||
Map<Integer, Instance> instanceMap = new HashMap<>();
|
||||
var messageManager = new BullyMessageManager(instanceMap);
|
||||
|
||||
var instance1 = new BullyInstance(messageManager, 1, 1);
|
||||
var instance2 = new BullyInstance(messageManager, 2, 1);
|
||||
var instance3 = new BullyInstance(messageManager, 3, 1);
|
||||
var instance4 = new BullyInstance(messageManager, 4, 1);
|
||||
var instance5 = new BullyInstance(messageManager, 5, 1);
|
||||
|
||||
instanceMap.put(1, instance1);
|
||||
instanceMap.put(2, instance2);
|
||||
instanceMap.put(3, instance3);
|
||||
instanceMap.put(4, instance4);
|
||||
instanceMap.put(5, instance5);
|
||||
|
||||
instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, ""));
|
||||
|
||||
final var thread1 = new Thread(instance1);
|
||||
final var thread2 = new Thread(instance2);
|
||||
final var thread3 = new Thread(instance3);
|
||||
final var thread4 = new Thread(instance4);
|
||||
final var thread5 = new Thread(instance5);
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread3.start();
|
||||
thread4.start();
|
||||
thread5.start();
|
||||
|
||||
instance1.setAlive(false);
|
||||
}
|
||||
```
|
||||
|
||||
La salida de la consola
|
||||
```
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Heartbeat Invoke Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Leader is alive.
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Heartbeat Invoke Message handling...
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5- Leader is not alive. Start election.
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Election Invoke Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4- Start election.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Election Invoke Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Start election.
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Start election.
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 2- Succeed in election. Start leader notification.
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 4 - Leader Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Leader Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling...
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 5 - Leader Message handling...
|
||||
[Thread-0] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 1 - Leader Message handling...
|
||||
[Thread-1] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 2 - Election Invoke Message handling...
|
||||
[Thread-3] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 4 - Leader update done.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3 - Leader update done.
|
||||
[Thread-0] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 1 - Leader update done.
|
||||
[Thread-4] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 5 - Leader update done.
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.AbstractInstance - Instance 3 - Heartbeat Invoke Message handling...
|
||||
[Thread-2] INFO com.iluwatar.leaderelection.bully.BullyInstance - Instance 3- Leader is alive.
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice este patrón cuando
|
||||
|
||||
* las tareas en una aplicación distribuida, como una solución alojada en la nube, requieren una coordinación cuidadosa y no hay un líder natural.
|
||||
|
||||
No utilice este patrón cuando
|
||||
|
||||
* exista un líder natural o un proceso dedicado que pueda actuar siempre como líder. Por ejemplo, puede ser posible implementar un proceso singleton que coordine las instancias de tareas. Si este proceso falla o se vuelve insalubre, el sistema puede apagarlo y reiniciarlo.
|
||||
* la coordinación entre tareas puede lograrse fácilmente utilizando un mecanismo más ligero. Por ejemplo, si varias instancias de tareas simplemente requieren un acceso coordinado a un recurso compartido, una solución preferible podría ser utilizar un bloqueo optimista o pesimista para controlar el acceso a ese recurso.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Leader Election pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/leader-election)
|
||||
* [Raft Leader Election](https://github.com/ronenhamias/raft-leader-election)
|
||||
|
After Width: | Height: | Size: 170 KiB |
@@ -0,0 +1,190 @@
|
||||
---
|
||||
title: Mediator
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Gang Of Four
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Define un objeto que encapsula cómo interactúa un conjunto de objetos. Mediator fomenta el acoplamiento flexible al evitar que los objetos se refieran entre sí de forma explícita, y te permite variar su interacción de forma independiente.
|
||||
|
||||
## Explicación
|
||||
|
||||
Un ejemplo real
|
||||
|
||||
> Pícaro, mago, hobbit y cazador han decidido unir sus fuerzas y viajar en la misma party. Para evitar acoplar a cada miembro entre sí, utilizan la interfaz de la party para comunicarse entre ellos.
|
||||
|
||||
En pocas palabras
|
||||
|
||||
> Mediator desacopla un conjunto de clases forzando su flujo de comunicaciones a través de un objeto mediador.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En ingeniería de software, el patrón mediador define un objeto que encapsula cómo interactúa un conjunto de objetos. Este patrón se considera un patrón de comportamiento debido a la forma en que puede alterar el comportamiento de ejecución del programa. En la programación orientada a objetos, los programas suelen constar de muchas clases. La lógica de negocio y la computación se distribuyen entre estas clases. Sin embargo, a medida que se añaden más clases a un programa, especialmente durante el mantenimiento y/o la refactorización, el problema de la comunicación entre estas clases puede volverse más complejo. Esto hace que el programa sea más difícil de leer y mantener. Además, puede resultar difícil modificar el programa, ya que cualquier cambio puede afectar al código de otras clases. Con el patrón mediador, la comunicación entre objetos se encapsula en un objeto mediador. Los objetos ya no se comunican directamente entre sí, sino a través del mediador. Esto reduce las dependencias entre los objetos que se comunican, reduciendo así el acoplamiento.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
En este ejemplo, el mediador encapsula cómo interactúan un conjunto de objetos. En lugar de referirse unos a otros directamente, utilizan la interfaz del mediador.
|
||||
|
||||
Los miembros de la party `Rogue`, `Wizard`, `Hobbit`, y `Hunter` heredan del `PartyMemberBase` implementando la interfaz `PartyMember`.
|
||||
|
||||
```java
|
||||
public interface PartyMember {
|
||||
|
||||
void joinedParty(Party party);
|
||||
|
||||
void partyAction(Action action);
|
||||
|
||||
void act(Action action);
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public abstract class PartyMemberBase implements PartyMember {
|
||||
|
||||
protected Party party;
|
||||
|
||||
@Override
|
||||
public void joinedParty(Party party) {
|
||||
LOGGER.info("{} joins the party", this);
|
||||
this.party = party;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partyAction(Action action) {
|
||||
LOGGER.info("{} {}", this, action.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(Action action) {
|
||||
if (party != null) {
|
||||
LOGGER.info("{} {}", this, action);
|
||||
party.act(this, action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
|
||||
public class Rogue extends PartyMemberBase {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rogue";
|
||||
}
|
||||
}
|
||||
|
||||
// Wizard, Hobbit, and Hunter are implemented similarly
|
||||
```
|
||||
|
||||
Nuestro sistema mediador consta de la interfaz `Party` y su implementación.
|
||||
|
||||
```java
|
||||
public interface Party {
|
||||
|
||||
void addMember(PartyMember member);
|
||||
|
||||
void act(PartyMember actor, Action action);
|
||||
}
|
||||
|
||||
public class PartyImpl implements Party {
|
||||
|
||||
private final List<PartyMember> members;
|
||||
|
||||
public PartyImpl() {
|
||||
members = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(PartyMember actor, Action action) {
|
||||
for (var member : members) {
|
||||
if (!member.equals(actor)) {
|
||||
member.partyAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMember(PartyMember member) {
|
||||
members.add(member);
|
||||
member.joinedParty(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Aquí tienes una demo que muestra el patrón mediador (Mediator) en acción.
|
||||
|
||||
```java
|
||||
// create party and members
|
||||
Party party = new PartyImpl();
|
||||
var hobbit = new Hobbit();
|
||||
var wizard = new Wizard();
|
||||
var rogue = new Rogue();
|
||||
var hunter = new Hunter();
|
||||
|
||||
// add party members
|
||||
party.addMember(hobbit);
|
||||
party.addMember(wizard);
|
||||
party.addMember(rogue);
|
||||
party.addMember(hunter);
|
||||
|
||||
// perform actions -> the other party members
|
||||
// are notified by the party
|
||||
hobbit.act(Action.ENEMY);
|
||||
wizard.act(Action.TALE);
|
||||
rogue.act(Action.GOLD);
|
||||
hunter.act(Action.HUNT);
|
||||
```
|
||||
|
||||
Esta es la salida de la consola al ejecutar el ejemplo.
|
||||
|
||||
```
|
||||
Hobbit joins the party
|
||||
Wizard joins the party
|
||||
Rogue joins the party
|
||||
Hunter joins the party
|
||||
Hobbit spotted enemies
|
||||
Wizard runs for cover
|
||||
Rogue runs for cover
|
||||
Hunter runs for cover
|
||||
Wizard tells a tale
|
||||
Hobbit comes to listen
|
||||
Rogue comes to listen
|
||||
Hunter comes to listen
|
||||
Rogue found gold
|
||||
Hobbit takes his share of the gold
|
||||
Wizard takes his share of the gold
|
||||
Hunter takes his share of the gold
|
||||
Hunter hunted a rabbit
|
||||
Hobbit arrives for dinner
|
||||
Wizard arrives for dinner
|
||||
Rogue arrives for dinner
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Mediator cuando:
|
||||
|
||||
* Un conjunto de objetos se comunican de formas bien definidas pero complejas. Las interdependencias resultantes no están estructuradas y son difíciles de entender
|
||||
* La reutilización de un objeto es difícil porque hace referencia y se comunica con muchos otros objetos.
|
||||
* Un comportamiento que se distribuye entre varias clases debe ser personalizable sin muchas subclases.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* Todos los métodos scheduleXXX() de [java.util.Timer](http://docs.oracle.com/javase/8/docs/api/java/util/Timer.html)
|
||||
* [java.util.concurrent.Executor#execute()](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-)
|
||||
* Métodos submit() e invokeXXX() de [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html)
|
||||
* Método scheduleXXX() de [java.util.concurrent.ScheduledExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html)
|
||||
* [java.lang.reflect.Method#invoke()](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,169 @@
|
||||
---
|
||||
title: Memento
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
Token
|
||||
|
||||
## Propósito
|
||||
|
||||
Sin violar la encapsulación, capturar y externalizar el estado interno de un objeto para que el objeto pueda ser restaurado a este estado más tarde.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Estamos trabajando en una aplicación de astrología en la que necesitamos analizar las propiedades de las estrellas a lo largo del tiempo. Estamos creando instantáneas de los estados de las estrellas utilizando el patrón Memento.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón Memento captura el estado interno de los objetos facilitando su almacenamiento y restauración en cualquier punto del tiempo.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> El patrón memento es un patrón de diseño de software que proporciona la capacidad de restaurar un objeto a su estado anterior (deshacer vía rollback).
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Definamos primero los tipos de estrellas que somos capaces de manejar.
|
||||
|
||||
```java
|
||||
public enum StarType {
|
||||
SUN("sun"),
|
||||
RED_GIANT("red giant"),
|
||||
WHITE_DWARF("white dwarf"),
|
||||
SUPERNOVA("supernova"),
|
||||
DEAD("dead star");
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
A continuación, vayamos directamente a lo esencial. Aquí está la clase `Star` junto con los mementos que necesitamos manipular. Presta especial atención a los métodos `getMemento` y `setMemento`.
|
||||
|
||||
```java
|
||||
public interface StarMemento {
|
||||
}
|
||||
|
||||
public class Star {
|
||||
|
||||
private StarType type;
|
||||
private int ageYears;
|
||||
private int massTons;
|
||||
|
||||
public Star(StarType startType, int startAge, int startMass) {
|
||||
this.type = startType;
|
||||
this.ageYears = startAge;
|
||||
this.massTons = startMass;
|
||||
}
|
||||
|
||||
public void timePasses() {
|
||||
ageYears *= 2;
|
||||
massTons *= 8;
|
||||
switch (type) {
|
||||
case RED_GIANT -> type = StarType.WHITE_DWARF;
|
||||
case SUN -> type = StarType.RED_GIANT;
|
||||
case SUPERNOVA -> type = StarType.DEAD;
|
||||
case WHITE_DWARF -> type = StarType.SUPERNOVA;
|
||||
case DEAD -> {
|
||||
ageYears *= 2;
|
||||
massTons = 0;
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StarMemento getMemento() {
|
||||
var state = new StarMementoInternal();
|
||||
state.setAgeYears(ageYears);
|
||||
state.setMassTons(massTons);
|
||||
state.setType(type);
|
||||
return state;
|
||||
}
|
||||
|
||||
void setMemento(StarMemento memento) {
|
||||
var state = (StarMementoInternal) memento;
|
||||
this.type = state.getType();
|
||||
this.ageYears = state.getAgeYears();
|
||||
this.massTons = state.getMassTons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s age: %d years mass: %d tons", type.toString(), ageYears, massTons);
|
||||
}
|
||||
|
||||
private static class StarMementoInternal implements StarMemento {
|
||||
|
||||
private StarType type;
|
||||
private int ageYears;
|
||||
private int massTons;
|
||||
|
||||
// setters and getters ->
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Y, por último, así es como utilizamos los recuerdos para almacenar y restaurar estados estelares.
|
||||
|
||||
```java
|
||||
var states = new Stack<>();
|
||||
var star = new Star(StarType.SUN, 10000000, 500000);
|
||||
LOGGER.info(star.toString());
|
||||
states.add(star.getMemento());
|
||||
star.timePasses();
|
||||
LOGGER.info(star.toString());
|
||||
states.add(star.getMemento());
|
||||
star.timePasses();
|
||||
LOGGER.info(star.toString());
|
||||
states.add(star.getMemento());
|
||||
star.timePasses();
|
||||
LOGGER.info(star.toString());
|
||||
states.add(star.getMemento());
|
||||
star.timePasses();
|
||||
LOGGER.info(star.toString());
|
||||
while (states.size() > 0) {
|
||||
star.setMemento(states.pop());
|
||||
LOGGER.info(star.toString());
|
||||
}
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```
|
||||
sun age: 10000000 years mass: 500000 tons
|
||||
red giant age: 20000000 years mass: 4000000 tons
|
||||
white dwarf age: 40000000 years mass: 32000000 tons
|
||||
supernova age: 80000000 years mass: 256000000 tons
|
||||
dead star age: 160000000 years mass: 2048000000 tons
|
||||
supernova age: 80000000 years mass: 256000000 tons
|
||||
white dwarf age: 40000000 years mass: 32000000 tons
|
||||
red giant age: 20000000 years mass: 4000000 tons
|
||||
sun age: 10000000 years mass: 500000 tons
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Memento cuando:
|
||||
|
||||
* Es necesario guardar una instantánea del estado de un objeto para poder restaurarlo más tarde, y
|
||||
* Una interfaz directa para obtener el estado expondría detalles de implementación y rompería la encapsulación del objeto.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* [java.util.Date](http://docs.oracle.com/javase/8/docs/api/java/util/Date.html)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: Null Object
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
En la mayoría de los lenguajes orientados a objetos, como Java o C#, las referencias pueden ser nulas. Estas referencias deben comprobarse para asegurarse de que no son nulas antes de invocar cualquier método, porque normalmente los métodos no pueden invocarse sobre referencias nulas. En lugar de utilizar una referencia nula para indicar la ausencia de un objeto (por ejemplo, un cliente inexistente), se utiliza un objeto que implementa la interfaz esperada, pero cuyo cuerpo de método está vacío. La ventaja de este enfoque sobre una implementación por defecto es que un objeto nulo es muy predecible y no tiene efectos secundarios: no hace nada.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Estamos construyendo un árbol binario a partir de nodos. Hay nodos normales y nodos "vacíos". Recorrer el árbol normalmente no debería causar errores, por lo que utilizamos el patrón de objetos nulos cuando es necesario.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón de objetos nulos maneja los objetos "vacíos" con elegancia.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En programación informática orientada a objetos, un objeto nulo es un objeto sin valor referenciado o con un comportamiento neutro ("nulo") definido. El patrón de diseño de objetos nulos describe los usos de tales objetos y su comportamiento (o la falta del mismo).
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Esta es la definición de la interfaz `Node`.
|
||||
|
||||
```java
|
||||
public interface Node {
|
||||
|
||||
String getName();
|
||||
|
||||
int getTreeSize();
|
||||
|
||||
Node getLeft();
|
||||
|
||||
Node getRight();
|
||||
|
||||
void walk();
|
||||
}
|
||||
```
|
||||
|
||||
Tenemos dos implementaciones de `Node`. La implementación normal `NodeImpl` y `NullNode` para nodos vacíos.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class NodeImpl implements Node {
|
||||
|
||||
private final String name;
|
||||
private final Node left;
|
||||
private final Node right;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public NodeImpl(String name, Node left, Node right) {
|
||||
this.name = name;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTreeSize() {
|
||||
return 1 + left.getTreeSize() + right.getTreeSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void walk() {
|
||||
LOGGER.info(name);
|
||||
if (left.getTreeSize() > 0) {
|
||||
left.walk();
|
||||
}
|
||||
if (right.getTreeSize() > 0) {
|
||||
right.walk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class NullNode implements Node {
|
||||
|
||||
private static final NullNode instance = new NullNode();
|
||||
|
||||
private NullNode() {
|
||||
}
|
||||
|
||||
public static NullNode getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTreeSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getLeft() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getRight() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void walk() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Entonces podemos construir y recorrer el árbol binario sin errores de la siguiente manera.
|
||||
|
||||
```java
|
||||
var root = new NodeImpl("1",
|
||||
new NodeImpl("11",
|
||||
new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()),
|
||||
NullNode.getInstance()
|
||||
),
|
||||
new NodeImpl("12",
|
||||
NullNode.getInstance(),
|
||||
new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance())
|
||||
)
|
||||
);
|
||||
root.walk();
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```
|
||||
1
|
||||
11
|
||||
111
|
||||
12
|
||||
122
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Objeto nulo cuando
|
||||
|
||||
* Desea evitar la comprobación explícita de nulos y mantener el algoritmo elegante y fácil de leer.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Pattern Languages of Program Design 3](https://www.amazon.com/gp/product/0201310112/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201310112&linkCode=as2&tag=javadesignpat-20&linkId=7372ffb8a4e39a3bb10f199b89aef921)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Observer
|
||||
category: Behavioral
|
||||
language: en
|
||||
tag:
|
||||
- Gang Of Four
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
|
||||
Dependents, Publish-Subscribe
|
||||
|
||||
## Propósito
|
||||
|
||||
Defina una dependencia de uno a muchos entre objetos para que cuando un objeto cambie de estado, todos sus dependientes sean notificados y actualizados automáticamente.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> En una tierra muy lejana viven las razas de los hobbits y los orcos. Ambos viven principalmente al aire libre, por lo que siguen de cerca los cambios meteorológicos. Se podría decir que observan constantemente el tiempo.
|
||||
|
||||
En palabras simples
|
||||
|
||||
> Se registran como observadores para recibir los cambios de estado del objeto.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> El patrón observador es un patrón de diseño de software en el que un objeto, llamado sujeto, mantiene una lista de sus dependientes, llamados observadores, y les notifica automáticamente cualquier cambio de estado, normalmente llamando a uno de sus métodos.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Presentemos primero la interfaz `WeatherObserver` y nuestras razas, `Orcs` y `Hobbits`.
|
||||
|
||||
```java
|
||||
public interface WeatherObserver {
|
||||
|
||||
void update(WeatherType currentWeather);
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class Orcs implements WeatherObserver {
|
||||
|
||||
@Override
|
||||
public void update(WeatherType currentWeather) {
|
||||
LOGGER.info("The orcs are facing " + currentWeather.getDescription() + " weather now");
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class Hobbits implements WeatherObserver {
|
||||
|
||||
@Override
|
||||
public void update(WeatherType currentWeather) {
|
||||
switch (currentWeather) {
|
||||
LOGGER.info("The hobbits are facing " + currentWeather.getDescription() + " weather now");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Luego está el clima `Weather`, que cambia constantemente.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class Weather {
|
||||
|
||||
private WeatherType currentWeather;
|
||||
private final List<WeatherObserver> observers;
|
||||
|
||||
public Weather() {
|
||||
observers = new ArrayList<>();
|
||||
currentWeather = WeatherType.SUNNY;
|
||||
}
|
||||
|
||||
public void addObserver(WeatherObserver obs) {
|
||||
observers.add(obs);
|
||||
}
|
||||
|
||||
public void removeObserver(WeatherObserver obs) {
|
||||
observers.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes time pass for weather.
|
||||
*/
|
||||
public void timePasses() {
|
||||
var enumValues = WeatherType.values();
|
||||
currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length];
|
||||
LOGGER.info("The weather changed to {}.", currentWeather);
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
private void notifyObservers() {
|
||||
for (var obs : observers) {
|
||||
obs.update(currentWeather);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Aquí está el ejemplo completo en acción.
|
||||
|
||||
```java
|
||||
var weather = new Weather();
|
||||
weather.addObserver(new Orcs());
|
||||
weather.addObserver(new Hobbits());
|
||||
weather.timePasses();
|
||||
weather.timePasses();
|
||||
weather.timePasses();
|
||||
weather.timePasses();
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```
|
||||
The weather changed to rainy.
|
||||
The orcs are facing rainy weather now
|
||||
The hobbits are facing rainy weather now
|
||||
The weather changed to windy.
|
||||
The orcs are facing windy weather now
|
||||
The hobbits are facing windy weather now
|
||||
The weather changed to cold.
|
||||
The orcs are facing cold weather now
|
||||
The hobbits are facing cold weather now
|
||||
The weather changed to sunny.
|
||||
The orcs are facing sunny weather now
|
||||
The hobbits are facing sunny weather now
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Observador en cualquiera de las siguientes situaciones:
|
||||
|
||||
* Cuando una abstracción tiene dos aspectos, uno dependiente del otro. Encapsular estos aspectos en objetos separados te permite variarlos y reutilizarlos independientemente.
|
||||
* Cuando un cambio en un objeto requiere cambiar otros, y no sabes cuántos objetos necesitan ser cambiados.
|
||||
* Cuando un objeto debe ser capaz de notificar a otros objetos sin hacer suposiciones sobre quiénes son estos objetos. En otras palabras, no quieres que estos objetos estén estrechamente acoplados.
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html)
|
||||
* [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html)
|
||||
* [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html)
|
||||
* [RxJava](https://github.com/ReactiveX/RxJava)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Java Generics and Collections](https://www.amazon.com/gp/product/0596527756/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596527756&linkCode=as2&tag=javadesignpat-20&linkId=246e5e2c26fe1c3ada6a70b15afcb195)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Parameter Object
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
La sintaxis del lenguaje Java no permite declarar un método con un valor predefinido para un parámetro. Probablemente la mejor opción para conseguir parámetros de método predefinidos en Java es utilizar la sobrecarga de métodos. La sobrecarga de métodos permite declarar varios métodos con el mismo nombre pero con un número diferente de parámetros. Pero el principal problema con la sobrecarga de métodos como solución para los valores de los parámetros por defecto se revela cuando un método acepta múltiples parámetros. Crear un método sobrecargado para cada posible combinación de parámetros puede resultar engorroso. Para solucionar este problema, se utiliza el patrón Objeto Parámetro.
|
||||
|
||||
## Explicación
|
||||
|
||||
El Objeto Parámetro (Parameter Object) es simplemente un objeto envoltorio para todos los parámetros de un método. No es más que un POJO normal. La ventaja del Objeto Parámetro sobre una lista regular de parámetros de método es el hecho de que los campos de clase pueden tener valores por defecto. Una vez creada la clase envoltorio para la lista de parámetros del método, se crea también una clase constructora correspondiente. Normalmente es una clase estática interna. El paso final es utilizar el constructor para construir un nuevo objeto parámetro. Para aquellos parámetros que se omitan, se utilizarán sus valores por defecto.
|
||||
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Aquí está la simple clase `SearchService` donde se utiliza la sobrecarga de métodos para los valores por defecto aquí. Para utilizar la sobrecarga de métodos, el número de argumentos o el tipo de argumento tiene que ser diferente.
|
||||
|
||||
```java
|
||||
public class SearchService {
|
||||
//Method Overloading example. SortOrder is defaulted in this method
|
||||
public String search(String type, String sortBy) {
|
||||
return getQuerySummary(type, sortBy, SortOrder.DESC);
|
||||
}
|
||||
|
||||
/* Method Overloading example. SortBy is defaulted in this method. Note that the type has to be
|
||||
different here to overload the method */
|
||||
public String search(String type, SortOrder sortOrder) {
|
||||
return getQuerySummary(type, "price", sortOrder);
|
||||
}
|
||||
|
||||
private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) {
|
||||
return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \""
|
||||
+ sortOrder.getValue() + "ending\" order...";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
A continuación presentamos el `SearchService` con `ParameterObject` creado con el patrón Builder.
|
||||
|
||||
```java
|
||||
public class SearchService {
|
||||
|
||||
/* Parameter Object example. Default values are abstracted into the Parameter Object
|
||||
at the time of Object creation */
|
||||
public String search(ParameterObject parameterObject) {
|
||||
return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(),
|
||||
parameterObject.getSortOrder());
|
||||
}
|
||||
|
||||
private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) {
|
||||
return "Requesting shoes of type \"" + type + "\" sorted by \"" + sortBy + "\" in \""
|
||||
+ sortOrder.getValue() + "ending\" order...";
|
||||
}
|
||||
}
|
||||
|
||||
public class ParameterObject {
|
||||
public static final String DEFAULT_SORT_BY = "price";
|
||||
public static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASC;
|
||||
|
||||
private String type;
|
||||
private String sortBy = DEFAULT_SORT_BY;
|
||||
private SortOrder sortOrder = DEFAULT_SORT_ORDER;
|
||||
|
||||
private ParameterObject(Builder builder) {
|
||||
type = builder.type;
|
||||
sortBy = builder.sortBy != null && !builder.sortBy.isBlank() ? builder.sortBy : sortBy;
|
||||
sortOrder = builder.sortOrder != null ? builder.sortOrder : sortOrder;
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
//Getters and Setters...
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private String type;
|
||||
private String sortBy;
|
||||
private SortOrder sortOrder;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder withType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sortBy(String sortBy) {
|
||||
this.sortBy = sortBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sortOrder(SortOrder sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterObject build() {
|
||||
return new ParameterObject(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Este patrón nos muestra la forma de tener parámetros por defecto para un método en Java ya que el lenguaje no tiene la característica de parámetros por defecto fuera de la caja.
|
||||
|
||||
## Créditos
|
||||
|
||||
- [Does Java have default parameters?](http://dolszewski.com/java/java-default-parameters)
|
||||
|
After Width: | Height: | Size: 179 KiB |
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Partial Response
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## Propósito
|
||||
Enviar una respuesta parcial del servidor al cliente en función de sus necesidades. El cliente especificará los campos que necesita al servidor, en lugar de servir todos los detalles del recurso.
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón de respuesta parcial cuando
|
||||
|
||||
* El cliente sólo necesita un subconjunto de datos del recurso.
|
||||
* Para evitar demasiada transferencia de datos por cable
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Common Design Patterns](https://cloud.google.com/apis/design/design_patterns)
|
||||
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: Pipeline
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Permite procesar los datos en una serie de etapas, introduciendo una entrada inicial y pasando la salida procesada para que la utilicen las etapas siguientes.
|
||||
|
||||
## Explicación
|
||||
|
||||
El patrón Pipeline utiliza etapas ordenadas para procesar una secuencia de valores de entrada. Cada tarea implementada está representada por una etapa de la tubería. Puede pensar en las canalizaciones como algo similar a las líneas de ensamblaje de una fábrica, donde cada elemento de la línea de ensamblaje se construye por etapas. El artículo parcialmente ensamblado pasa de una etapa de ensamblaje a otra. Las salidas de la cadena de montaje se producen en el mismo orden que las entradas.
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Supongamos que queremos pasar una cadena a una serie de etapas de filtrado y convertirla como una matriz char en la última etapa.
|
||||
|
||||
En palabras sencillas
|
||||
|
||||
> El patrón pipeline es una cadena de montaje donde los resultados parciales pasan de una etapa a otra.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En ingeniería de software, un pipeline consiste en una cadena de elementos de procesamiento (procesos, hilos, coroutines, funciones, etc.), dispuestos de forma que la salida de cada elemento es la entrada del siguiente; el nombre es por analogía a un pipeline físico.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Las etapas de nuestro pipeline se llaman `Handler`s.
|
||||
```java
|
||||
interface Handler<I, O> {
|
||||
O process(I input);
|
||||
}
|
||||
```
|
||||
|
||||
En nuestro ejemplo de procesamiento de cadenas tenemos 3 `Handler`s concretos diferentes.
|
||||
|
||||
```java
|
||||
class RemoveAlphabetsHandler implements Handler<String, String> {
|
||||
...
|
||||
}
|
||||
|
||||
class RemoveDigitsHandler implements Handler<String, String> {
|
||||
...
|
||||
}
|
||||
|
||||
class ConvertToCharArrayHandler implements Handler<String, char[]> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Aquí está el `Pipeline` que recogerá y ejecutará los handlers uno a uno.
|
||||
|
||||
```java
|
||||
class Pipeline<I, O> {
|
||||
|
||||
private final Handler<I, O> currentHandler;
|
||||
|
||||
Pipeline(Handler<I, O> currentHandler) {
|
||||
this.currentHandler = currentHandler;
|
||||
}
|
||||
|
||||
<K> Pipeline<I, K> addHandler(Handler<O, K> newHandler) {
|
||||
return new Pipeline<>(input -> newHandler.process(currentHandler.process(input)));
|
||||
}
|
||||
|
||||
O execute(I input) {
|
||||
return currentHandler.process(input);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Y aquí está la `Pipeline` en acción procesando la cadena.
|
||||
|
||||
```java
|
||||
var filters = new Pipeline<>(new RemoveAlphabetsHandler())
|
||||
.addHandler(new RemoveDigitsHandler())
|
||||
.addHandler(new ConvertToCharArrayHandler());
|
||||
filters.execute("GoYankees123!");
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón Pipeline cuando desee
|
||||
|
||||
* Ejecutar etapas individuales que produzcan un valor final.
|
||||
* Añadir legibilidad a secuencias complejas de operaciones proporcionando un constructor fluido como interfaz.
|
||||
* Mejorar la comprobabilidad del código, ya que las etapas probablemente harán una sola cosa, cumpliendo con el [Principio de Responsabilidad Única (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle)
|
||||
|
||||
## Usos conocidos
|
||||
|
||||
* [java.util.Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html)
|
||||
* [Maven Build Lifecycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
|
||||
* [Functional Java](https://github.com/functionaljava/functionaljava)
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
* [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [The Pipeline Pattern — for fun and profit](https://medium.com/@aaronweatherall/the-pipeline-pattern-for-fun-and-profit-9b5f43a98130)
|
||||
* [The Pipeline design pattern (in Java)](https://medium.com/@deepakbapat/the-pipeline-design-pattern-in-java-831d9ce2fe21)
|
||||
* [Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10))
|
||||
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,216 @@
|
||||
---
|
||||
title: Poison Pill
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Cloud distributed
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
La píldora venenosa es un elemento de datos predefinido conocido que permite proporcionar un cierre graceful para un proceso de consumo distribuido independiente.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Pensemos en una cola de mensajes con un productor y un consumidor. El productor sigue introduciendo nuevos mensajes en la cola y el consumidor sigue leyéndolos. Finalmente, cuando llega el momento de cerrar la cola, el productor envía el mensaje de píldora venenosa.
|
||||
|
||||
En pocas palabras
|
||||
|
||||
> Poison Pill es una estructura de mensaje conocida que pone fin al intercambio de mensajes.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Definamos primero la estructura del mensaje. Hay una interfaz `Message` y una implementación `SimpleMessage`.
|
||||
|
||||
```java
|
||||
public interface Message {
|
||||
|
||||
...
|
||||
|
||||
enum Headers {
|
||||
DATE, SENDER
|
||||
}
|
||||
|
||||
void addHeader(Headers header, String value);
|
||||
|
||||
String getHeader(Headers header);
|
||||
|
||||
Map<Headers, String> getHeaders();
|
||||
|
||||
void setBody(String body);
|
||||
|
||||
String getBody();
|
||||
}
|
||||
|
||||
public class SimpleMessage implements Message {
|
||||
|
||||
private final Map<Headers, String> headers = new HashMap<>();
|
||||
private String body;
|
||||
|
||||
@Override
|
||||
public void addHeader(Headers header, String value) {
|
||||
headers.put(header, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(Headers header) {
|
||||
return headers.get(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Headers, String> getHeaders() {
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para pasar mensajes utilizamos colas de mensajes. Aquí definimos los tipos relacionados con la cola de mensajes: `MqPublishPoint`, `MqSubscribePoint` y `MessageQueue`. `SimpleMessageQueue` implementa todas estas interfaces.
|
||||
|
||||
```java
|
||||
public interface MqPublishPoint {
|
||||
|
||||
void put(Message msg) throws InterruptedException;
|
||||
}
|
||||
|
||||
public interface MqSubscribePoint {
|
||||
|
||||
Message take() throws InterruptedException;
|
||||
}
|
||||
|
||||
public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {
|
||||
}
|
||||
|
||||
public class SimpleMessageQueue implements MessageQueue {
|
||||
|
||||
private final BlockingQueue<Message> queue;
|
||||
|
||||
public SimpleMessageQueue(int bound) {
|
||||
queue = new ArrayBlockingQueue<>(bound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Message msg) throws InterruptedException {
|
||||
queue.put(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A continuación necesitamos los mensajes `Producer` y `Consumer`. Internamente utilizan las colas de mensajes de arriba. Es importante notar que cuando `Producer` se detiene, envía la píldora venenosa para informar a `Consumer` que la mensajería ha terminado.
|
||||
|
||||
```java
|
||||
public class Producer {
|
||||
|
||||
...
|
||||
|
||||
public void send(String body) {
|
||||
if (isStopped) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Producer %s was stopped and fail to deliver requested message [%s].", body, name));
|
||||
}
|
||||
var msg = new SimpleMessage();
|
||||
msg.addHeader(Headers.DATE, new Date().toString());
|
||||
msg.addHeader(Headers.SENDER, name);
|
||||
msg.setBody(body);
|
||||
|
||||
try {
|
||||
queue.put(msg);
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
LOGGER.error("Exception caught.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
isStopped = true;
|
||||
try {
|
||||
queue.put(Message.POISON_PILL);
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
LOGGER.error("Exception caught.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Consumer {
|
||||
|
||||
...
|
||||
|
||||
public void consume() {
|
||||
while (true) {
|
||||
try {
|
||||
var msg = queue.take();
|
||||
if (Message.POISON_PILL.equals(msg)) {
|
||||
LOGGER.info("Consumer {} receive request to terminate.", name);
|
||||
break;
|
||||
}
|
||||
var sender = msg.getHeader(Headers.SENDER);
|
||||
var body = msg.getBody();
|
||||
LOGGER.info("Message [{}] from [{}] received by [{}]", body, sender, name);
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
LOGGER.error("Exception caught.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Por último, estamos listos para presentar todo el ejemplo en acción.
|
||||
|
||||
```java
|
||||
var queue = new SimpleMessageQueue(10000);
|
||||
|
||||
final var producer = new Producer("PRODUCER_1", queue);
|
||||
final var consumer = new Consumer("CONSUMER_1", queue);
|
||||
|
||||
new Thread(consumer::consume).start();
|
||||
|
||||
new Thread(() -> {
|
||||
producer.send("hand shake");
|
||||
producer.send("some very important information");
|
||||
producer.send("bye!");
|
||||
producer.stop();
|
||||
}).start();
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```
|
||||
Message [hand shake] from [PRODUCER_1] received by [CONSUMER_1]
|
||||
Message [some very important information] from [PRODUCER_1] received by [CONSUMER_1]
|
||||
Message [bye!] from [PRODUCER_1] received by [CONSUMER_1]
|
||||
Consumer CONSUMER_1 receive request to terminate.
|
||||
```
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utiliza la expresión "píldora envenenada" (Poison pill) cuando:
|
||||
|
||||
* Hay una necesidad de enviar una señal de un hilo/proceso a otro para terminar.
|
||||
|
||||
## Ejemplos del mundo real
|
||||
|
||||
* [akka.actor.PoisonPill](http://doc.akka.io/docs/akka/2.1.4/java/untyped-actors.html)
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Presentation Model
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## También conocido como
|
||||
Application Model
|
||||
|
||||
## Propósito
|
||||
El modelo de presentación extrae el estado y el comportamiento de la vista a una clase modelo que forma parte de la presentación.
|
||||
|
||||
## Explicación
|
||||
|
||||
Ejemplo del mundo real
|
||||
|
||||
> Cuando necesitamos escribir un programa con GUI, no es necesario que pongamos todo el comportamiento de la presentación en la clase view. Porque será más difícil de probar. Así que podemos utilizar Presentation Model Pattern para separar el comportamiento y la vista. La vista solo necesita cargar los datos y estados de otra clase y mostrar estos datos en la pantalla de acuerdo a los estados.
|
||||
|
||||
En palabras simples
|
||||
|
||||
> un patrón que divide la presentación y el control.
|
||||
|
||||
Ejemplo de código
|
||||
|
||||
La clase `view` es la GUI de los álbumes. Los métodos `saveToPMod` y `loadFromPMod` se utilizan para lograr la sincronización.
|
||||
|
||||
```java
|
||||
public class View {
|
||||
/**
|
||||
* the model that controls this view.
|
||||
*/
|
||||
private final PresentationModel model;
|
||||
|
||||
private TextField txtTitle;
|
||||
private TextField txtArtist;
|
||||
private JCheckBox chkClassical;
|
||||
private TextField txtComposer;
|
||||
private JList<String> albumList;
|
||||
private JButton apply;
|
||||
private JButton cancel;
|
||||
|
||||
public View() {
|
||||
model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* save the data to PresentationModel.
|
||||
*/
|
||||
public void saveToPMod() {
|
||||
LOGGER.info("Save data to PresentationModel");
|
||||
model.setArtist(txtArtist.getText());
|
||||
model.setTitle(txtTitle.getText());
|
||||
model.setIsClassical(chkClassical.isSelected());
|
||||
model.setComposer(txtComposer.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* load the data from PresentationModel.
|
||||
*/
|
||||
public void loadFromPMod() {
|
||||
LOGGER.info("Load data from PresentationModel");
|
||||
txtArtist.setText(model.getArtist());
|
||||
txtTitle.setText(model.getTitle());
|
||||
chkClassical.setSelected(model.getIsClassical());
|
||||
txtComposer.setEditable(model.getIsClassical());
|
||||
txtComposer.setText(model.getComposer());
|
||||
}
|
||||
|
||||
public void createView() {
|
||||
// the detail of GUI information like size, listenser and so on.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La clase `Album` sirve para almacenar información de un álbum.
|
||||
|
||||
```java
|
||||
public class Album {
|
||||
|
||||
private String title;
|
||||
private String artist;
|
||||
private boolean isClassical;
|
||||
/**
|
||||
* only when the album is classical,
|
||||
* composer can have content.
|
||||
*/
|
||||
private String composer;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
La clase `DisplatedAlbums` almacena la información de todos los álbumes que se mostrarán en la GUI.
|
||||
|
||||
```java
|
||||
public class DisplayedAlbums {
|
||||
private final List<Album> albums;
|
||||
|
||||
public DisplayedAlbums() {
|
||||
this.albums = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addAlbums(final String title,
|
||||
final String artist, final boolean isClassical,
|
||||
final String composer) {
|
||||
if (isClassical) {
|
||||
this.albums.add(new Album(title, artist, true, composer));
|
||||
} else {
|
||||
this.albums.add(new Album(title, artist, false, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clase `PresentationMod` se utiliza para controlar toda la acción de GUI.
|
||||
|
||||
```java
|
||||
public class PresentationModel {
|
||||
private final DisplayedAlbums data;
|
||||
|
||||
private int selectedAlbumNumber;
|
||||
private Album selectedAlbum;
|
||||
|
||||
public PresentationModel(final DisplayedAlbums dataOfAlbums) {
|
||||
this.data = dataOfAlbums;
|
||||
this.selectedAlbumNumber = 1;
|
||||
this.selectedAlbum = this.data.getAlbums().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of selectedAlbumNumber.
|
||||
*
|
||||
* @param albumNumber the number of album which is shown on the view.
|
||||
*/
|
||||
public void setSelectedAlbumNumber(final int albumNumber) {
|
||||
LOGGER.info("Change select number from {} to {}",
|
||||
this.selectedAlbumNumber, albumNumber);
|
||||
this.selectedAlbumNumber = albumNumber;
|
||||
this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return selectedAlbum.getTitle();
|
||||
}
|
||||
// other get methods are like this, which are used to get information of selected album.
|
||||
|
||||
public void setTitle(final String value) {
|
||||
LOGGER.info("Change album title from {} to {}",
|
||||
selectedAlbum.getTitle(), value);
|
||||
selectedAlbum.setTitle(value);
|
||||
}
|
||||
// other set methods are like this, which are used to get information of selected album.
|
||||
|
||||
/**
|
||||
* Gets a list of albums.
|
||||
*
|
||||
* @return the names of all the albums.
|
||||
*/
|
||||
public String[] getAlbumList() {
|
||||
var result = new String[data.getAlbums().size()];
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
result[i] = data.getAlbums().get(i).getTitle();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Podemos ejecutar la clase `App` para iniciar esta demo. la casilla de verificación es el álbum clásico; el primer campo de texto es el nombre del artista del álbum; el segundo es el nombre del título del álbum; el último es el nombre del compositor:
|
||||
|
||||

|
||||
|
||||
|
||||
## Diagrama de clases
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
Utilice el patrón de modelo de presentación cuando
|
||||
|
||||
* Probar una presentación a través de una ventana GUI es a menudo incómodo, y en algunos casos imposible.
|
||||
* No determine qué GUI se utilizará.
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
- [Supervising Controller](https://martinfowler.com/eaaDev/SupervisingPresenter.html)
|
||||
- [Passive View](https://martinfowler.com/eaaDev/PassiveScreen.html)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Presentation Model Patterns](https://martinfowler.com/eaaDev/PresentationModel.html)
|
||||
|
||||
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: Priority Queue Pattern
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Decoupling
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## Propósito
|
||||
Priorizar las peticiones enviadas a los servicios de forma que las peticiones con mayor prioridad se reciban y procesen más rápidamente que las de menor prioridad. Este patrón es útil en aplicaciones que ofrecen diferentes garantías de nivel de servicio a clientes individuales.
|
||||
|
||||
## Explicación
|
||||
|
||||
Las aplicaciones pueden delegar tareas específicas en otros servicios; por ejemplo, para realizar procesamientos en segundo plano o para integrarse con otras aplicaciones o servicios. En la nube, se suele utilizar una cola de mensajes para delegar tareas al procesamiento en segundo plano. En muchos casos, el orden en que un servicio recibe las solicitudes no es importante. Sin embargo, en algunos casos puede ser necesario priorizar peticiones específicas. Estas peticiones deben ser procesadas antes que otras de menor prioridad que puedan haber sido enviadas previamente por la aplicación.
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Imagine un servicio de procesamiento de vídeo con clientes gratuitos y premium. Las solicitudes procedentes de los clientes premium de pago deben tener prioridad sobre las demás.
|
||||
|
||||
En pocas palabras
|
||||
|
||||
> La cola prioritaria permite procesar primero los mensajes de alta prioridad, independientemente del tamaño de la cola o de la antigüedad del mensaje.
|
||||
|
||||
Wikipedia dice
|
||||
|
||||
> En informática, una cola prioritaria es un tipo de datos abstracto similar a una cola normal o a una estructura de datos de pila en la que cada elemento tiene además una "prioridad" asociada. En una cola de prioridad, un elemento con prioridad alta se sirve antes que un elemento con prioridad baja.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
Si observamos el ejemplo anterior de procesamiento de vídeo, veamos primero la estructura `Message`.
|
||||
|
||||
```java
|
||||
public class Message implements Comparable<Message> {
|
||||
|
||||
private final String message;
|
||||
private final int priority; // define message priority in queue
|
||||
|
||||
public Message(String message, int priority) {
|
||||
this.message = message;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Message o) {
|
||||
return priority - o.priority;
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Aquí está `PriorityMessageQueue` que se encarga de almacenar los mensajes y servirlos en orden de prioridad.
|
||||
|
||||
```java
|
||||
public class PriorityMessageQueue<T extends Comparable> {
|
||||
|
||||
...
|
||||
|
||||
public T remove() {
|
||||
if (isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final var root = queue[0];
|
||||
queue[0] = queue[size - 1];
|
||||
size--;
|
||||
maxHeapifyDown();
|
||||
return root;
|
||||
}
|
||||
|
||||
public void add(T t) {
|
||||
ensureCapacity();
|
||||
queue[size] = t;
|
||||
size++;
|
||||
maxHeapifyUp();
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
El `QueueManager` tiene una `PriorityMessageQueue` y facilita la publicación de mensajes `publishMessage` y la recepción de mensajes `receiveMessage`.
|
||||
|
||||
```java
|
||||
public class QueueManager {
|
||||
|
||||
private final PriorityMessageQueue<Message> messagePriorityMessageQueue;
|
||||
|
||||
public QueueManager(int initialCapacity) {
|
||||
messagePriorityMessageQueue = new PriorityMessageQueue<>(new Message[initialCapacity]);
|
||||
}
|
||||
|
||||
public void publishMessage(Message message) {
|
||||
messagePriorityMessageQueue.add(message);
|
||||
}
|
||||
|
||||
public Message receiveMessage() {
|
||||
if (messagePriorityMessageQueue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return messagePriorityMessageQueue.remove();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
El `Worker` sondea constantemente el `QueueManager` en busca del mensaje de mayor prioridad y lo procesa.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class Worker {
|
||||
|
||||
private final QueueManager queueManager;
|
||||
|
||||
public Worker(QueueManager queueManager) {
|
||||
this.queueManager = queueManager;
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
while (true) {
|
||||
var message = queueManager.receiveMessage();
|
||||
if (message == null) {
|
||||
LOGGER.info("No Message ... waiting");
|
||||
Thread.sleep(200);
|
||||
} else {
|
||||
processMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processMessage(Message message) {
|
||||
LOGGER.info(message.toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Aquí está el ejemplo completo de cómo creamos una instancia de `QueueManager` y procesamos mensajes usando `Worker`.
|
||||
|
||||
```java
|
||||
var queueManager = new QueueManager(100);
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
queueManager.publishMessage(new Message("Low Message Priority", 0));
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
queueManager.publishMessage(new Message("High Message Priority", 1));
|
||||
}
|
||||
|
||||
var worker = new Worker(queueManager);
|
||||
worker.run();
|
||||
```
|
||||
|
||||
Salida del programa:
|
||||
|
||||
```
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='High Message Priority', priority=1}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
Message{message='Low Message Priority', priority=0}
|
||||
No Message ... waiting
|
||||
No Message ... waiting
|
||||
No Message ... waiting
|
||||
```
|
||||
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Utilice el patrón de Cola Prioritaria cuando:
|
||||
|
||||
* El sistema debe manejar múltiples tareas que pueden tener diferentes prioridades.
|
||||
* Diferentes usuarios o inquilinos deben ser atendidos con diferente prioridad.
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Priority Queue pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/priority-queue)
|
||||
|
After Width: | Height: | Size: 52 KiB |
@@ -21,7 +21,7 @@ Ejemplo real
|
||||
|
||||
> Imagina una torre donde los magos locales van a estudiar sus hechizos. A la torre de marfil sólo se puede acceder a través de un proxy que asegura que sólo los tres primeros magos pueden entrar. Aquí el proxy representa la funcionalidad de la torre y le añade control de acceso.
|
||||
|
||||
En palabras llanas
|
||||
En palabras sencillas
|
||||
|
||||
> Usando el patrón proxy, una clase representa la funcionalidad de otra clase.
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Retry
|
||||
category: Behavioral
|
||||
language: es
|
||||
tag:
|
||||
- Performance
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Reintentar de forma transparente determinadas operaciones que implican la comunicación con recursos externos, en particular a través de la red, aislando el código de llamada de los detalles de implementación del reintento.
|
||||
|
||||
## Explicación
|
||||
|
||||
El patrón de reintento consiste en reintentar operaciones sobre recursos remotos a través de la red un número determinado de veces. Depende estrechamente de los requisitos empresariales y técnicos: ¿Cuánto tiempo permitirá la empresa que espere el usuario final hasta que finalice la operación? ¿Cuáles son las características de rendimiento del recurso remoto durante los picos de carga, así como de nuestra aplicación a medida que más hilos esperan la disponibilidad del recurso remoto? Entre los errores devueltos por el servicio remoto, ¿cuáles pueden ignorarse con seguridad para volver a intentarlo? ¿Es la operación [idempotent](https://en.wikipedia.org/wiki/Idempotence)?
|
||||
|
||||
Otra preocupación es el impacto en el código de llamada al implementar el mecanismo de reintento. Idealmente, la mecánica de reintento debería ser completamente transparente para el código de llamada (la interfaz del servicio permanece inalterada). Existen dos enfoques generales para este problema: desde el punto de vista de la arquitectura empresarial (estratégico) y desde el punto de vista de la biblioteca compartida (táctico).
|
||||
|
||||
Desde un punto de vista estratégico, esto se resolvería redirigiendo las peticiones a un sistema intermediario separado, tradicionalmente un [ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), pero más recientemente un [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a).
|
||||
|
||||
Desde un punto de vista táctico, esto se resolvería reutilizando bibliotecas compartidas como [Hystrix](https://github.com/Netflix/Hystrix) (nótese que Hystrix es una implementación completa del patrón [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/), del que el patrón Retry puede considerarse un subconjunto). Este es el tipo de solución que se muestra en el sencillo ejemplo que acompaña a este `README.md`.
|
||||
|
||||
Ejemplo real
|
||||
|
||||
> Nuestra aplicación utiliza un servicio que proporciona información sobre clientes. De vez en cuando el servicio parece fallar y puede devolver errores o a veces simplemente se desconecta. Para evitar estos problemas aplicamos el patrón retry.
|
||||
|
||||
En palabras simples
|
||||
|
||||
> El patrón de reintento reintenta de forma transparente las operaciones fallidas a través de la red.
|
||||
|
||||
[Documentación de Microsoft](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry) dice
|
||||
|
||||
> Permite a una aplicación manejar fallos transitorios cuando intenta conectarse a un servicio o recurso de red, reintentando de forma transparente una operación fallida. Esto puede mejorar la estabilidad de la aplicación.
|
||||
|
||||
**Ejemplo programático**
|
||||
|
||||
En nuestra aplicación hipotética, tenemos una interfaz genérica para todas las operaciones sobre interfaces remotas.
|
||||
|
||||
```java
|
||||
public interface BusinessOperation<T> {
|
||||
T perform() throws BusinessException;
|
||||
}
|
||||
```
|
||||
|
||||
Y tenemos una implementación de esta interfaz que encuentra a nuestros clientes buscando en una base de datos.
|
||||
|
||||
```java
|
||||
public final class FindCustomer implements BusinessOperation<String> {
|
||||
@Override
|
||||
public String perform() throws BusinessException {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nuestra implementación de `FindCustomer` puede configurarse para lanzar `BusinessException`s antes de devolver el ID del cliente, simulando así un servicio defectuoso que falla intermitentemente. Algunas excepciones, como la `CustomerNotFoundException`, se consideran recuperables tras un hipotético análisis porque la causa raíz del error proviene de "algún problema de bloqueo de la base de datos". Sin embargo, la `DatabaseNotAvailableException` se considera definitivamente un showtopper - la aplicación no debe intentar recuperarse de este error.
|
||||
|
||||
Podemos modelar un escenario recuperable instanciando `FindCustomer` así:
|
||||
|
||||
```java
|
||||
final var op = new FindCustomer(
|
||||
"12345",
|
||||
new CustomerNotFoundException("not found"),
|
||||
new CustomerNotFoundException("still not found"),
|
||||
new CustomerNotFoundException("don't give up yet!")
|
||||
);
|
||||
```
|
||||
|
||||
En esta configuración, `FindCustomer` lanzará `CustomerNotFoundException` tres veces, tras lo cual devolverá sistemáticamente el ID del cliente (`12345`).
|
||||
|
||||
En nuestro escenario hipotético, nuestros analistas indican que esta operación suele fallar entre 2 y 4 veces para una entrada determinada durante las horas punta, y que cada hilo de trabajo del subsistema de base de datos suele necesitar 50 ms para "recuperarse de un error". Aplicando estas políticas se obtendría algo así:
|
||||
|
||||
```java
|
||||
final var op = new Retry<>(
|
||||
new FindCustomer(
|
||||
"1235",
|
||||
new CustomerNotFoundException("not found"),
|
||||
new CustomerNotFoundException("still not found"),
|
||||
new CustomerNotFoundException("don't give up yet!")
|
||||
),
|
||||
5,
|
||||
100,
|
||||
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
|
||||
);
|
||||
```
|
||||
|
||||
Ejecutando `op` una vez se lanzarían automáticamente como máximo 5 intentos de reintento, con un retardo de 100 milisegundos entre intentos, ignorando cualquier `CustomerNotFoundException` lanzada durante el intento. En este escenario en particular, debido a la configuración de `FindCustomer`, habrá 1 intento inicial y 3 reintentos adicionales antes de devolver finalmente el resultado deseado `12345`.
|
||||
|
||||
Si nuestra operación `FindCustomer` lanzara una fatal `DatabaseNotFoundException`, la cual se nos instruyó no ignorar, pero más importante aún, no instruimos a nuestro `Retry` ignorar, entonces la operación habría fallado inmediatamente al recibir el error, sin importar cuantos intentos quedaran.
|
||||
|
||||
## Diagrama de clases
|
||||
|
||||

|
||||
|
||||
## Aplicabilidad
|
||||
|
||||
Siempre que una aplicación necesite comunicarse con un recurso externo, especialmente en un entorno de nube, y si los requisitos empresariales lo permiten.
|
||||
|
||||
## Consecuencias
|
||||
|
||||
**Pros:**
|
||||
|
||||
* Resistencia
|
||||
* Proporciona datos concretos sobre fallos externos
|
||||
|
||||
**Desventajas**
|
||||
|
||||
* Complejidad
|
||||
* Mantenimiento de operaciones
|
||||
|
||||
## Patrones relacionados
|
||||
|
||||
|
||||
* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/)
|
||||
|
||||
## Créditos
|
||||
|
||||
* [Retry pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry)
|
||||
* [Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://www.amazon.com/gp/product/1621140369/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1621140369&linkId=3e3f686af5e60a7a453b48adb286797b)
|
||||
|
After Width: | Height: | Size: 51 KiB |
@@ -18,7 +18,7 @@ Un ejemplo real
|
||||
|
||||
> Puede haber muchos tipos diferentes de vehículos en este mundo pero todos ellos se engloban bajo el único paraguas de Vehículo
|
||||
|
||||
En palabras llanas
|
||||
En palabras sencillas
|
||||
|
||||
> Mapea cada instancia de clase de un árbol de herencia en una única tabla.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Ejemplo real
|
||||
|
||||
> Consideremos un juego con una pelota que necesita características de dos tipos, Game Item, e hilos para funcionar sin problemas en el juego. Podemos utilizar dos objetos, con un objeto compatible con el primer tipo y el otro compatible con el segundo tipo. El par de objetos juntos pueden funcionar como una pelota en el juego.
|
||||
|
||||
En palabras llanas
|
||||
En palabras sencillas
|
||||
|
||||
> Proporciona una forma de formar dos subclases estrechamente acopladas que pueden actuar como una clase gemela que tiene dos extremos.
|
||||
|
||||
|
||||