"¡Hola, Amigo! Hoy te contaré algunas cosas interesantes sobre la clase BufferedInputStream, pero empecemos con « envoltorios » y una « bolsa de azúcar »."

"¿Qué quieres decir con «envoltura» y «bolsa de azúcar»?"

"Estas son metáforas. Escucha. Entonces..."

El patrón de diseño «envoltorio» (o «decorador») es un mecanismo bastante simple y conveniente para extender la funcionalidad de un objeto sin usar la herencia.

BufferedInputStream - 1

Supongamos que tenemos una clase Cat con dos métodos: getName y setName:

codigo Java Descripción
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
La clase Cat tiene dos métodos: getName y setName
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");

 printName(cat);
}

public static void printName(Cat cat)
{
 System.out.println(cat.getName());
}
Un ejemplo de cómo se podría utilizar.

«Oscar» se mostrará en la consola.

Supongamos que necesitamos interceptar una llamada de método en un objeto gato y tal vez hacer algunos pequeños cambios. Para esto, necesitamos envolverlo en su propia clase contenedora.

Si queremos "envolver" nuestro propio código alrededor de las llamadas al método en algún objeto, entonces necesitamos:

1) Crear nuestra propia clase contenedora y heredar de la misma clase/interfaz que el objeto a envolver.

2) Pasar el objeto a envolver al constructor de nuestra clase.

3) Anular todos los métodos en nuestra nueva clase. Invoque los métodos del objeto envuelto dentro de cada uno de los métodos anulados.

4) Realice los cambios que desee: cambie lo que hacen las llamadas al método, cambie sus parámetros y/o haga otra cosa.

En el siguiente ejemplo, interceptamos llamadas al método getName de un objeto Cat y cambiamos ligeramente su valor de retorno.

codigo Java Descripción
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
La clase Cat contiene dos métodos: getName y setName.
class CatWrapper extends Cat
{
 private Cat original;
 public CatWrapper (Cat cat)
 {
  super(cat.getName());
  this.original = cat;
 }

 public String getName()
 {
  return "A cat named " + original.getName();
 }

 public void setName(String name)
 {
  original.setName(name);
 }
}
La clase contenedora. La clase no almacena ningún dato excepto una referencia al objeto original.
La clase puede "lanzar" llamadas al objeto original (setName) pasado al constructor. También puede "capturar" estas llamadas y modificar sus parámetros y/o resultados .
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");
 Cat catWrap = new CatWrapper (cat);
 printName(catWrap);
}

public static void printName(Cat named)
{
 System.out.println(named.getName());
}
Un ejemplo de cómo se podría utilizar.

«Un gato llamado Oscar».
se mostrará en la consola

En otras palabras, reemplazamos silenciosamente cada objeto original con un objeto contenedor, que recibe un enlace al objeto original. Todas las llamadas a métodos en el contenedor se reenvían al objeto original y todo funciona como un reloj.

"Me gusta. La solución es simple y funcional".

"También te contaré sobre una 'bolsa de azúcar'. Esta es una metáfora más que un patrón de diseño. Una metáfora de la palabra amortiguación y amortiguación. ¿Qué es la amortiguación y por qué la necesitamos?"

BufferedInputStream - 2

Digamos que hoy es el turno de cocinar de Rishi y lo estás ayudando. Rishi no ha llegado todavía, pero quiero beber té. Te pido que me traigas una cucharada de azúcar. Vas al sótano y encuentras una bolsa de azúcar. Puedes traerme la bolsa entera, pero no necesito la bolsa. Solo necesito una cucharada. Luego, como buen robot, tomas una cucharada y me la traes. Lo agrego al té, pero todavía no es lo suficientemente dulce. Y te pido que me traigas uno más. Vas de nuevo al sótano y traes otra cucharada. Luego viene Ellie y te pido que le lleves azúcar... Todo esto lleva demasiado tiempo y es ineficiente.

Rishi llega, ve todo esto y te pide que le traigas un azucarero lleno de azúcar. Entonces Ellie y yo comenzamos a pedirle azúcar a Rishi. Simplemente nos lo sirve del azucarero, y eso es todo.

Lo que sucedió después de que apareciera Rishi se llama amortiguamiento : el azucarero es un amortiguador. Gracias al almacenamiento en búfer, los "clientes" pueden leer datos de un búfer en pequeñas porciones , mientras que el búfer, para ahorrar tiempo y esfuerzo, los lee desde la fuente en grandes porciones .

"Ese es un buen ejemplo, Kim. Lo entiendo perfectamente. La solicitud de una cucharada de azúcar es como leer un byte de un flujo".

"Exactamente. La clase BufferedInputStream es un ejemplo clásico de contenedor con búfer. Envuelve la clase InputStream. Lee datos del InputStream original en bloques grandes en un búfer y luego los extrae del búfer pieza por pieza a medida que leer de él".

"Muy bien. Está todo claro. ¿Hay amortiguadores para escribir?"

"Oh, por supuesto."

"¿Tal vez un ejemplo?"

"Imagínese un bote de basura. En lugar de salir a poner la basura en un incinerador cada vez, simplemente la arroja al bote de basura. Luego, Bubba saca el bote una vez cada dos semanas. Un amortiguador clásico".

"¡Qué interesante! Y mucho más claro que una bolsa de azúcar, por cierto".

"Y el método flush() es como sacar la basura de inmediato. Puede usarlo antes de que lleguen los invitados".