Rediseñar el código existente
Implementar patrones para rediseñar código
El propósito de este capítulo es mostrar cómo implementar los patrones de diseño rediseñando el código existente que se desarrolló al margen de estos modelos. Los nueve patrones que se implementarán forman parte de los veintitrés ya introducidos en los capítulos anteriores.
El código para rediseñar se presenta en forma de ejemplos que se estudiarán a lo largo de este capítulo.
Cada ejemplo está dedicado a un patrón y se presenta en dos partes:
-
Una primera en la que se muestra el ejemplo sin implementar el patrón y que a menudo tiene pocas cualidades de un diseño orientado a objetos.
-
Una segunda que muestra cómo se ha reescrito el ejemplo de la primera parte utilizando el patrón correspondiente, lo que da como resultado una versión más respetuosa de las cualidades de un diseño orientado a objetos (sobre todo en términos de legibilidad, modularidad y capacidad de ampliación).
Vale la pena señalar una excepción para los patrones Composite y Visitor, para los cuales hemos desarrollado una reescritura que implementa ambos modelos simultáneamente.
Composite y Visitor
1. Ejemplo inicial
El código de ejemplo se forma con dos clases: la clase Employee, que representa a los empleados, y la clase Subsidiary, correspondiente a las empresas.
A continuación, encontrará el código de la clase Employee, que precede a la enumeración EmployeeType. La clase introduce los asesores de lectura y escritura para los atributos wage y employeeType (de tipo EmployeeType). También proporciona la función isStaffEmployee, que indica si un empleado forma parte de la plantilla (en el sentido de equipo directivo), así como la función isAdministrativeEmployee, que especifica si es un empleado administrativo.
public enum EmployeeType {
STAFF, ADMINISTRATIVE, TECHNICAL
}
public class Employee{
protected long wage;
protected EmployeeType employeeType;
public Employee(long wage, EmployeeType employeeType) {
this.wage = wage;
this.employeeType = employeeType;
}
public long getWage() {
return wage;
}
public EmployeeType getEmployeeType() {
return employeeType;
}
public void setWage(long wage) {
this.wage = wage;
}
public void setEmployeeType(EmployeeType employeeType) {
this.employeeType = employeeType;
}
public boolean isStaffEmployee() {
return employeeType == EmployeeType.STAFF;
}
public boolean isAdministrativeEmployee() {
return employeeType == EmployeeType.ADMINISTRATIVE;
}
}
Examinaremos ahora la clase Subsidiary, cuyo código se proporciona a continuación. Esta...
Template Method
1. Ejemplo inicial
El ejemplo inicial está constituido por la clase abstracta GraphicalObject y sus dos subclases concretas: Ellipse y TextZone. Representa un objeto gráfico abstracto que factoriza propiedades comunes. En este caso, introduce las propiedades comunes a las elipses y las zonas de texto, a saber, su posición, su dimensión, su inicialización y el método de dibujo drawFrame, que dibuja un marco alrededor del objeto.
Las dos subclases concretas introducen sus propiedades y su método de dibujo específico. En los métodos de dibujo, la acción se simula imprimiendo los datos en la pantalla.
Encontrará, a continuación, el código de las tres clases.
public abstract class GraphicalObject {
protected int xPos,yPos,width,height;
public GraphicalObject(int xPos, int yPos, int width, int height) {
this.xPos = xPos;
this.yPos = yPos;
this.width = width;
this.height = height;
}
protected void drawFrame() {
System.out.println("Drawing rectangle xPos= " + xPos + " yPos= " +
yPos + " Width= " + width + " Height = " + height);
}
public abstract void drawFramedObject();
}
public class Ellipse extends GraphicalObject {
protected int lineThickness; ...
Iterator
1. Ejemplo inicial
El ejemplo elegido para ilustrar la implementación del patrón Iterator consiste en una lista encadenada de cadenas de caracteres cuyos nodos están descritos por la clase Node que contiene dos atributos:
-
su valor (atributo value);
-
el siguiente nodo, si existe (atributo next).
El código de la clase Node es el siguiente:
class Node {
protected String value;
protected Node next;
public Node(String value) {
this.value = value;
this.next = null;
}
public String getValue() {
return value;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public boolean hasNext() {
return next != null;
}
}
La clase LinkedList introduce el encabezado de la lista enlazada (atributo head) y los siguientes métodos:
-
addNewNode, que agrega un nodo a la lista.
-
getNumberOfNodes, que devuelve el número de nodos presentes en la lista.
-
getValue, que devuelve el valor de un nodo según su índice.
La clase LinkedList queda descrita por el siguiente código:
class LinkedList {
protected Node head = null;
public LinkedList() {
}
public void addNewNode(String value) {
if (head == null) head = new Node(value);
else {
Node lastNode = head;
while (lastNode.hasNext()) lastNode...
Chain of Responsibility
1. Ejemplo inicial
Para el ejemplo inicial, elegimos la tramitación de solicitudes de crédito al consumo o de crédito inmobiliario en una entidad bancaria.
Cada solicitud de crédito se presenta al asesor bancario del cliente. El asesor tramita la solicitud del siguiente modo:
-
Concede el crédito al consumidor si su importe no supera el límite máximo que se le ha asignado. Si no es así, transmite la solicitud al director de su sucursal.
-
No puede conceder créditos inmobiliarios, debe remitir la solicitud al director de la sucursal.
El director de la sucursal tramita las solicitudes de la siguiente forma:
-
El crédito al consumo se concede si su importe no supera el límite máximo que se le ha asignado; en caso contrario, remite la solicitud a la dirección del banco.
-
El crédito inmobiliario se concede con la doble condición de que su importe no supere el límite máximo y su duración no supere el plazo límite, siendo ambos valores asignados por el director. Si no es el caso, el director remite la solicitud a la dirección del banco.
Por último, las solicitudes son tramitadas por la dirección del banco del siguiente modo: sea cual sea el tipo de crédito, solo se concede si su importe no supera el umbral fijado para este tipo de crédito por el consejo de administración del banco.
Estudiamos el código que implementa todos estos mecanismos.
Las solicitudes de créditos al consumo se describen mediante la clase ConsumerLoanRequest; las solicitudes de préstamos hipotecarios, mediante la clase HomeLoanRequest. Encontrará el código de estas dos clases a continuación. La clase ConsumerLoanRequest introduce el importe del préstamo. La clase HomeLoanRequest introduce el importe y la duración del préstamo.
public class ConsumerLoanRequest {
protected int amount;
public ConsumerLoanRequest(int amount) {
super();
this.amount = amount;
}
public int getAmount() {
return amount;
} ...
State
1. Ejemplo inicial
El siguiente código es para la enumeración StateEnum y la clase Queue. Esta última gestiona una estructura de datos de tipo file, es decir, que añade un elemento a la parte superior de la estructura y elimina un elemento de la parte inferior de ella. Su atributo de estado, tipificado por la enumeración StateEnum, contiene el valor del estado actual de la cola: vacía (EMPTYSTATE), llena (FULLSTATE) o ni vacía ni llena (NORMALSTATE).
El método enqueue añade un elemento si la cola no está llena. En este caso, actualiza el estado después de agregar el elemento. Si la variable top es igual a MAXSIZE, entonces la cola está llena.
El método dequeue elimina un elemento si la cola no está vacía. Este método provoca un desplazamiento de los valores presentes en la cola. A continuación, actualiza el estado tras la eliminación. Si la variable top es igual a 0, entonces la cola está vacía.
enum StateEnum {
EMPTYSTATE, NORMALSTATE, FULLSTATE
}
public class Queue {
protected final static int MAXSIZE = 10;
protected int buffer[] = new int[MAXSIZE];
protected int top = 0;
protected StateEnum state = StateEnum.EMPTYSTATE;
public void enqueue(int value) {
if (state != StateEnum.FULLSTATE) {
buffer[top] = value;
top++;
if (top == MAXSIZE) state = StateEnum.FULLSTATE;
else state = StateEnum.NORMALSTATE;
}
}
public Integer dequeue() {
if (state != StateEnum.EMPTYSTATE) {
int returnValue = buffer[0];
top--; ...
Observer
1. Ejemplo inicial
El ejemplo es un objeto gráfico que, cuando se modifica una de sus dimensiones, invoca un conjunto de métodos a través de un mecanismo de devolución de llamada (callback). Este objeto gráfico es un rectángulo descrito por la clase epónima.
El mecanismo de callback se implementa de la siguiente manera:
-
La interfaz Callback introduce la firma del método update que se invocará cuando se realice cualquier cambio.
-
Se transmite la lista de objetos que realizan esta interfaz como parámetro a los métodos setHeight y setWidth. Estos dos métodos invocan el método de actualización de los objetos de estas listas mediante una llamada al método notifyCallBack.
A continuación, encontrará todo el código que presenta la interfaz Callback y la clase Rectangle. Va precedido del código de la enumeración DimensionEnum, que se utiliza para indicar una dimensión.
enum DimensionEnum {
HEIGHT, WIDTH
}
public interface Callback {
void update(DimensionEnum dimension, Integer newValue);
}
import java.util.ArrayList;
import java.util.List;
public class Rectangle {
protected int height, width;
public Rectangle(int height, int width) {
super();
this.height = height;
this.width = width;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public void notifyCallBack(DimensionEnum dimension, Integer newValue,
List<Callback> dimensionCallbacks) {
for (Callback dimensionCallback: dimensionCallbacks) {
dimensionCallback.update(dimension...
Decorator
1. Ejemplo inicial
El ejemplo inicial se basa en una alternativa a la implementación clásica del patrón Observer. Es una realización de este patrón sobre una clase existente cuyas sobreclases no se desean o son imposibles de modificar. La gestión de los observadores se introduce en una subclase de la clase observada.
En nuestro ejemplo, la clase cuyos cambios de estado deseamos observar es la clase Rectangle, cuyo código encontrará a continuación.
public class Rectangle {
protected int height, width;
public Rectangle(int height, int width) {
super();
this.height = height;
this.width = width;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public void setHeight(int height) {
this.height = height;
}
public void setWidth(int width) {
this.width = width;
}
}
La gestión de observadores se introduce en la subclase ObservableRectangle de la clase Rectangle mediante mecanismos de registro e invocación de estos observadores cuando se modifica el estado del objeto. Además, ObservableRectangle redefine los métodos setHeight y setWidth de la clase Rectangle para que estos dos métodos llamen al método notifyObserver que invoca al método update de los observadores. El código de ObservableRectangle va precedido por el de la enumeración DimensionEnum, que siempre se utiliza para especificar la dimensión.
enum DimensionEnum {
HEIGHT, WIDTH
}
import java.util.ArrayList;
import java.util.List;
public class ObservableRectangle...
Command
1. Ejemplo inicial
El ejemplo elegido para ilustrar el patrón Command se basa en un editor de objetos gráficos que son objetos UML, en este caso clases y métodos. Admite la posibilidad de añadir argumentos (parámetros) a un método o añadir propiedades a una clase.
La clase UML está representada por la clase ClassGraphicalObject. El método UML se representa mediante la clase MethodGraphicalObject, que introduce el número de argumentos.
El código de estas dos clases se muestra a continuación:
public class MethodGraphicalObject {
protected int numberArguments = 0;
public static final int MAXARGUMENTS = 10;
public MethodGraphicalObject() {
}
public int getNumberArguments() {
return numberArguments;
}
public void setNumberArguments(int numberArguments) {
this.numberArguments = numberArguments;
}
}
public class ClassGraphicalObject {
protected int numberProperties = 0;
public static final int MAXPROPERTIES = 20;
public ClassGraphicalObject() {
}
public int getNumberProperties() {
return numberProperties;
}
public void setNumberProperties(int numberProperties) {
this.numberProperties = numberProperties;
}
}
Aumentar el número de argumentos o propiedades en una unidad se hace pulsando la tecla flecha arriba del teclado, disminuirlo en una unidad se hace pulsando la tecla flecha abajo. Los objetos encargados de la interfaz de usuario invocan la gestión centralizada de comandos en la clase Invoker.
Para ejecutar un comando, un cliente de esta clase debe invocar primero uno de los siguientes métodos:
-
El método...