java - patrones - Casos de uso y ejemplos de patrón decorador GoF para IO
patrones de diseño orientado a objetos (8)
A - Patrón decorador
A.1 - Caso de uso del patrón decorador
El patrón decorador se usa para extender una funcionalidad heredada sin cambiar la clase heredada. Digamos que tenemos una clase concreta que implementa una interfaz. Sin embargo, necesitamos extender la funcionalidad del método existente porque la clase existente y sus métodos ya están siendo utilizados por otras clases, por lo que no queremos hacer un cambio en las clases existentes. Pero también necesitamos funcionalidad extendida en la clase más nueva, entonces, ¿cómo resolvemos este problema?
1- We can''t change the existing legacy code
2- We want to extend the functionality
Entonces usamos un patrón de decorador, envolvemos la clase existente dentro de los decoradores.
B - Ejemplo de patrón de decorador básico de GoF
Aquí tenemos una interfaz simple y una implementación / clase concreta. La interfaz tiene un método simple, que es getMessageOfTheDay
y devuelve un String
. Supongamos que hay muchas otras clases que usan este método. Entonces, si queremos hacer un cambio en la implementación / clase concreta, afectará el viejo código heredado. Queremos cambiarlo solo por las nuevas clases, así que usamos el patrón de decorador.
Aquí hay un ejemplo trivial del patrón de diseño de Gang Of Four Decorator;
B.1 - Greeter.java
public interface Greeter {
String getMessageOfTheDay();
}
B.2 - BasicGreeter.java
public class BasicGreeter implements Greeter {
@Override
public String getMessageOfTheDay() {
return "Welcome to my server";
}
}
B.3 - Clase de decorador abstracto: GreeterDecorator.java
public abstract class GreeterDecorator implements Greeter {
protected Greeter greeter;
public GreeterDecorator(Greeter greeter) {
this.greeter = greeter;
}
public String getMessageOfTheDay() {
return greeter.getMessageOfTheDay();
}
}
B.4 - Clase de decorador de hormigón: StrangerDecorator.java
public class StrangerDecorator extends GreeterDecorator {
public StrangerDecorator(Greeter greeter) {
super(greeter);
}
@Override
public String getMessageOfTheDay() {
return "Hello Stranger " + super.getMessageOfTheDay();
}
}
B.5 - Código de demostración: DecoratorDemo .java
public class DecoratorDemo {
public static void main(String[] args) {
Greeter greeter = new BasicGreeter();
String motd = greeter.getMessageOfTheDay();
System.out.println(motd);
Greeter newGreeter = new StrangerDecorator(greeter);
String newMotd = newGreeter.getMessageOfTheDay();
System.out.println(newMotd);
Greeter muchNewGreeter = new StrangerDecorator(new StrangerDecorator(greeter));
String newestMotd = muchNewGreeter.getMessageOfTheDay();
System.out.println(newestMotd);
}
}
Echa un vistazo a esos ejemplos. La clase de decorador abstracto es necesaria para ajustar el contrato original y la implementación. Usando el decorador abstracto, puede crear decoradores múltiples más nuevos, pero en este ejemplo, BasicGreeter está envuelto dentro del decorador abstracto y solo hemos creado en la nueva clase de decorador que es StrangeGreeter . Notifique que las clases de decorador se pueden usar como un tren, podemos envolver un decorador dentro de otro decorador o lo mismo. La funcionalidad es extensible pero la clase original se conserva sin ninguna modificación.
C - Demostración de OutputStream
Echemos un vistazo a este ejemplo. Queremos escribir una cadena al archivo con OutputStream. Aquí está el código de demostración;
C.1 - Ejemplo de demostración de OutputStream para escribir un archivo
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
File file = new File("./normal.txt");
file.createNewFile();
OutputStream oStream = new FileOutputStream(file);
String content = "I love Commodore 64";
oStream.write(content.getBytes());
oStream.close();
}
}
C.2 - Salida decorador JSON: normal.txt
Habrá un nuevo archivo con el nombre "normal.txt" creado en la carpeta del proyecto y el contenido será;
I love Commodore 64
D - JSON OutputStream Decorator Demo
Ahora, quiero crear un formato de contenedor JSON, que es el siguiente;
{
data: <data here>
}
Lo que quiero es escribir el contenido dentro de un formato JSON de campo simple. ¿Cómo podemos lograr este objetivo? Hay muchas formas triviales. Sin embargo, usaré el Patrón Decorador GoF escribiendo un JSONDecorator que extienda la clase OutputStream de Java;
D.1 - Decorador JSON para OutputStream: JSONStream.java
public class JSONStream extends OutputStream {
protected OutputStream outputStream;
public JSONStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
@Override
public void write(byte[] b) throws IOException {
String content = new String(b);
content = "{/r/n/tdata:/"" + content + "/"/r/n}";
outputStream.write(content.getBytes());
}
}
D.2 - Demostración de JSON Decorator: JSONDecoratorDemo.java
public class JSONDecoratorDemo {
public static void main(String[] args) throws IOException {
File file = new File("./json.txt");
file.createNewFile();
OutputStream oStream = new FileOutputStream(file);
JSONStream js = new JSONStream(oStream);
String content = "I love Commodore 64";
js.write(content.getBytes());
js.close();
oStream.close();
}
}
D.3 - Salida del decorador JSON: json.txt
{
data:"I love Commodore 64"
}
En realidad, OutputStream es un Decorator Pattern, es el decorador abstracto y el decorador de concreto aquí es la clase JSONStream .
He leído en wikipedia que el patrón Decorator se usa para las clases de IO de .Net y Java .
¿Alguien puede explicar cómo se usa esto? ¿Y cuál es el beneficio de esto con un posible ejemplo?
Hay un ejemplo de formularios de Windows en wikipedia, pero quiero saber cómo sucede con las clases IO de Java .
Bueno, puedo llegar tarde a la fiesta, pero esta pregunta nunca pasa de moda. El punto clave para entender Decorator es que le da la capacidad de conectar un objeto a un objeto existente a otro objeto existente, y así sucesivamente. Es popular implementar este patrón en un constructor. Por ejemplo,
Icecream ic = new RainbowTopUp(new ChocoTopUp(new Vanilla()));
Si observa el diagrama en la wikipedia, verá ConcreteComponent y Decorator heredados de la misma superclase / interfaz, Component . Es decir, estas dos clases tienen los mismos métodos de implementación.
Sin embargo, en la clase Decorador , vería una flecha en el Componente , lo que significa que usa Componente en algún lugar de la clase Decorador . En este caso, usa el Componente como un tipo de datos de un constructor en el Decorador . Ese es el gran truco. Sin este truco, no podrás conectar un nuevo objeto a un objeto existente.
Después de eso, puede crear subclases heredadas de la clase Decorator . Debido a que todas las clases tienen la misma raíz, todas las clases pueden completarse libremente sin ningún orden.
Comprendamos los componentes del patrón Decorator antes de pasar por las clases Java IO.
Decorator patrón del Decorator tiene cuatro componentes
- Componente: el Componente define la interfaz para los objetos que pueden tener responsabilidades añadidas dinámicamente
- ConcreteComponent: es simplemente una implementación de la interfaz del componente
- Decorador: el Decorador tiene una referencia a un Componente , y también se ajusta a la interfaz del Componente . Decorator es esencialmente envolviendo el Componente
- ConcreteDecorator: ConcreteDecorator solo agrega responsabilidades al Componente original.
El patrón de decorador se puede usar para ampliar (decorar) la funcionalidad de un determinado objeto de forma estática o, en algunos casos, en tiempo de ejecución, independientemente de otras instancias de la misma clase, siempre que se realicen algunos preparativos en tiempo de diseño. Esto se logra mediante el diseño de una nueva clase Decorator que envuelve la clase original.
Ahora vamos a asignar estos conceptos a las clases java.io pacakge.
Componente:
Esta clase abstracta es la superclase de todas las clases que representa una secuencia de entrada de bytes.
Las aplicaciones que necesitan definir una subclase de InputStream siempre deben proporcionar un método que devuelva el siguiente byte de entrada.
public abstract int read()
es un método abstracto.
ConcreteComponent:
Un FileInputStream obtiene bytes de entrada de un archivo en un sistema de archivos. Los archivos disponibles dependen del entorno del host.
FileInputStream está diseñado para leer flujos de bytes sin formato, como datos de imágenes. Para leer secuencias de caracteres, considere usar FileReader.
Ejemplos de todos los ConcreteComponents de InputStream:
AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream,
InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream,
StringBufferInputStream
Decorador:
Un FilterInputStream contiene alguna otra corriente de entrada, que utiliza como su fuente de datos básica, posiblemente transformando los datos a lo largo del camino o proporcionando funcionalidad adicional.
Tenga en cuenta que FilterInputStream
implementa InputStream
=> Decorator implementa Component como se muestra en el diagrama UML .
public class FilterInputStream
extends InputStream
ConcreteDecorator:
Un BufferedInputStream agrega funcionalidad a otro flujo de entrada, es decir, la capacidad de almacenar en búfer la entrada y apoyar los métodos de marca y restablecimiento.
Ejemplos de todos los ConcreteDecorators :
BufferedInputStream, CheckedInputStream, CipherInputStream, DataInputStream,
DeflaterInputStream, DigestInputStream, InflaterInputStream,
LineNumberInputStream, ProgressMonitorInputStream, PushbackInputStream
Código de ejemplo de trabajo:
He usado BufferedInputStream
para leer cada carácter de una palabra, que se ha almacenado en un archivo de texto a.txt
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
char c = (char)bis.read();
System.out.println("Char: "+c);;
}
Cuándo usar este patrón:
- Las responsabilidades y comportamientos del objeto se deben agregar / eliminar dinámicamente
- Implementaciones concretas deben estar desacopladas de las responsabilidades y comportamientos
- Cuando la subclasificación es demasiado costosa para agregar / eliminar dinámicamente responsabilidades
El patrón de decorador se usa en las clases de java.io cuando se manipulan flujos de entrada / salida (y lo mismo se aplica a los lectores y escritores).
inputstream, bytearrayinputstream, stringbuilderinputstreams, etc. son elementos basados. Filterinputstream es la clase base para las clases de decorador. Las secuencias de entrada de filtro (como la secuencia de entrada en búfer) pueden hacer cosas adicionales cuando leen secuencias o escriben en ellas.
Se crean al encapsular una secuencia, y son secuencias en sí mismas.
new BufferedReader( new FileInputStream() ).readLine();
No puedo pensar en ninguna clase que implemente este patrón en java.net, pero creo que le contaron sobre este paquete, ya que está fuertemente relacionado con java.io (socket.getInputStream, por ejemplo).
En realidad, aquí hay un curso de O''Relly que explica cómo se implementa el decorador en java.io.
Saludos, Stéphane
El patrón de decorador se usa para agregar funcionalidad a objetos existentes como una clase definida en una biblioteca. Luego puede "decorarlo" para satisfacer sus necesidades. Si está interesado en aprender más acerca de los patrones, recomiendo "Patrones de diseño" de The Gang of Four.
En .NET, hay muchos decoradores de flujo, como BufferedStream, CryptoStream, GzipStream, etc. Todos decoran la clase Stream
.
Una forma de decorar una secuencia de entrada / salida es aplicarle compresión / descompresión. Vea las clases en java.util.zip
, por ejemplo. Tal flujo decorado se puede usar exactamente de la misma manera que un flujo de entrada / salida "regular", con compresión / descompresión realizada de forma totalmente transparente.
InputStream
es una clase abstracta. La mayoría de las implementaciones concretas como BufferedInputStream
, GzipInputStream
, ObjectInputStream
, etc. tienen un constructor que toma una instancia de la misma clase abstracta. Esa es la clave de reconocimiento del patrón de decorador (esto también se aplica a los constructores que toman una instancia de la misma interfaz).
Cuando se utiliza dicho constructor, todos los métodos se delegarán en la instancia envuelta, con cambios en el comportamiento de los métodos. Por ejemplo, almacenar en memoria intermedia la secuencia en la memoria de antemano, descomprimir la secuencia de antemano o interpretar la secuencia de forma diferente. Algunos incluso tienen métodos adicionales que finalmente también delegan más a la instancia envuelta. Esos métodos decoran la instancia envuelta con un comportamiento extra.
Digamos que tenemos un grupo de objetos Java serializados en un archivo Gzipped y que queremos leerlos rápidamente.
Primero abre un flujo de entrada de él:
FileInputStream fis = new FileInputStream("/objects.gz");
Queremos velocidad, así que vamos a almacenarla en la memoria:
BufferedInputStream bis = new BufferedInputStream(fis);
El archivo tiene gzip, por lo que debemos descomprimirlo:
GzipInputStream gis = new GzipInputStream(bis);
Necesitamos deserializar esos objetos Java:
ObjectInputStream ois = new ObjectInputStream(gis);
Ahora finalmente podemos usarlo:
SomeObject someObject = (SomeObject) ois.readObject();
// ...
El beneficio es que tiene mucha libertad para decorar el flujo utilizando uno o más decoradores diferentes para satisfacer sus necesidades. Eso es mucho mejor que tener una única clase para cada combinación posible, como ObjectGzipBufferedFileInputStream
, ObjectBufferedFileInputStream
, GzipBufferedFileInputStream
, ObjectGzipFileInputStream
, ObjectFileInputStream
, GzipFileInputStream
, BufferedFileInputStream
, etc.
Tenga en cuenta que cuando esté a punto de cerrar la transmisión, basta con cerrar el decorador externo . Delegará la llamada cercana hasta el final.
ois.close();