CodeGym /Blog Java /Random-ES /Patrón de diseño del adaptador
Autor
Pavlo Plynko
Java Developer at CodeGym

Patrón de diseño del adaptador

Publicado en el grupo Random-ES
¡Hola! Hoy tocaremos un nuevo tema importante: los patrones de diseño . ¿Cuáles son estos patrones? Creo que debes conocer la expresión " no reinventar la rueda ". En programación, como en muchas otras áreas, hay una gran cantidad de situaciones comunes. A medida que el desarrollo de software ha evolucionado, se han creado soluciones listas para usar que funcionan para cada uno de ellos. Estas soluciones se denominan patrones de diseño. Por convención, un patrón es una solución formulada así: "si necesita hacer X en su programa, esta es la mejor manera de hacerlo". Hay un montón de patrones. El excelente libro "Head First Design Patterns", con el que definitivamente debería familiarizarse, está dedicado a ellos. Patrón de diseño del adaptador - 2En pocas palabras, un patrón consta de un problema común y una solución correspondiente que puede considerarse una especie de estándar. En la lección de hoy, conoceremos uno de estos patrones: Adaptador. Su nombre lo dice todo, y te has encontrado con adaptadores muchas veces en la vida real. Algunos de los adaptadores más comunes son los lectores de tarjetas que tienen muchas computadoras y portátiles. Patrón de diseño del adaptador - 3Supongamos que tenemos algún tipo de tarjeta de memoria. ¿Entonces, cuál es el problema? No sabe cómo interactuar con la computadora. No comparten una interfaz común. La computadora tiene un puerto USB, pero no podemos insertar la tarjeta de memoria en él. La tarjeta no se puede conectar a la computadora, por lo que no podemos guardar nuestras fotos, videos y otros datos. Un lector de tarjetas es un adaptador que soluciona este problema. ¡Después de todo, tiene un cable USB! A diferencia de la tarjeta en sí, el lector de tarjetas se puede conectar a la computadora. Comparten una interfaz común con la computadora: USB. Veamos cómo se ve esto en la práctica:

public interface USB { 

   void connectWithUsbCable(); 
}
Esta es nuestra interfaz USB con un solo método para conectarse a través de USB.

public class MemoryCard { 

   public void insert() { 
       System.out.println("Memory card successfully inserted!"); 
   } 

   public void copyData() { 
       System.out.println("The data has been copied to the computer!"); 
   } 
}
Esta es nuestra clase que representa la tarjeta de memoria. Ya tiene los 2 métodos que necesitamos, pero aquí está el problema: no implementa la interfaz USB. La tarjeta no se puede insertar en el puerto USB.

public class CardReader implements USB { 

   private MemoryCard memoryCard; 

   public CardReader(MemoryCard memoryCard) { 
       this.memoryCard = memoryCard; 
   } 

   @Override 
   public void connectWithUsbCable() { 
       this.memoryCard.insert(); 
       this.memoryCard.copyData(); 
   } 
}
¡Y aquí está nuestro adaptador! Lo que hace elCardReaderclass do y qué es exactamente lo que lo convierte en un adaptador? Es todo sencillo. La clase que se está adaptando (MemoryCard) se convierte en uno de los campos del adaptador. Esto tiene sentido. Cuando colocamos una tarjeta de memoria dentro de un lector de tarjetas en la vida real, también se convierte en parte de él. A diferencia de la tarjeta de memoria, el adaptador comparte una interfaz con la computadora. Tiene un cable USB, es decir, se puede conectar a otros dispositivos a través de USB. Es por eso que nuestra clase CardReader implementa la interfaz USB. Pero, ¿qué sucede exactamente dentro de este método? ¡Exactamente lo que necesitamos que suceda! El adaptador delega el trabajo a nuestra tarjeta de memoria. De hecho, el adaptador no hace nada por sí mismo. Un lector de tarjetas no tiene ninguna funcionalidad independiente. Su trabajo es solo conectar la computadora y la tarjeta de memoria para permitir que la tarjeta haga su trabajo: ¡copiar archivos!connectWithUsbCable()método) para satisfacer las "necesidades" de la tarjeta de memoria. Vamos a crear un programa cliente que simule a una persona que quiere copiar datos de una tarjeta de memoria:

public class Main { 

   public static void main(String[] args) { 

       USB cardReader = new CardReader(new MemoryCard()); 
       cardReader.connectWithUsbCable(); 
   } 
}
Entonces que fue lo que recibimos? Salida de la consola:

Memory card successfully inserted! 
The data has been copied to the computer!
Excelente. ¡Logramos nuestro objetivo! Aquí hay un enlace a un video con información sobre el patrón del adaptador:

Clases abstractas de Reader y Writer

Ahora regresaremos a nuestra actividad favorita: conocer un par de clases nuevas para trabajar con entrada y salida :) Me pregunto cuántas ya hemos aprendido. Hoy hablaremos de las clases Reader y Writer. ¿Por qué específicamente esas clases? Porque están relacionados con nuestra sección anterior sobre adaptadores. Vamos a examinarlos con más detalle. Empezaremos con  Reader. Readeres una clase abstracta, por lo que no podremos crear objetos explícitamente.   ¡Pero en realidad ya estás familiarizado con él! Después de todo, conoces bien las clases BufferedReadery InputStreamReader, que son sus descendientes :)

public class BufferedReader extends Reader { 
… 
} 

public class InputStreamReader extends Reader { 
… 
}
La InputStreamReaderclase es un adaptador clásico. Como probablemente recuerde, podemos pasar un InputStreamobjeto a su constructor. Para ello, solemos utilizar la System.invariable:

public static void main(String[] args) { 

   InputStreamReader inputStreamReader = new InputStreamReader(System.in); 
}
Pero que InputStreamReaderhace? Como todo adaptador, convierte una interfaz en otra.  En este caso, la InputStreaminterfaz a la Readerinterfaz. Inicialmente, tenemos la InputStreamclase. Funciona bien, pero solo puede usarlo para leer bytes individuales. Además, tenemos una Readerclase abstracta. Tiene una funcionalidad muy útil: ¡sabe cómo leer caracteres! Ciertamente necesitamos esta habilidad. Pero aquí nos enfrentamos al problema clásico que generalmente resuelven los adaptadores: interfaces incompatibles. ¿Qué significa eso? Echemos un vistazo a la documentación de Oracle. Estos son los métodos de la InputStreamclase. Patrón de diseño del adaptador - 4Un conjunto de métodos es precisamente lo que es una interfaz. Como puede ver, esta clase tiene unread()(algunas variantes, de hecho), pero solo puede leer bytes: ya sea bytes individuales o varios bytes usando un búfer. Pero esta opción no nos conviene, queremos leer caracteres. Necesitamos la funcionalidad que ya está implementada en la Readerclase abstracta . También podemos ver esto en la documentación. Patrón de diseño del adaptador - 5Sin embargo, las interfaces InputStreamy  Readerson incompatibles. Como puede ver, cada implementación del read()método tiene diferentes parámetros y valores de retorno. ¡Y aquí es donde necesitamos InputStreamReader! Actuará como un adaptador entre nuestras clases. Como en el ejemplo con el lector de tarjetas, que consideramos anteriormente, colocamos una instancia de la clase que se está adaptando "dentro" de la clase adaptadora, es decir, le pasamos una a su constructor. En el ejemplo anterior, colocamos un MemoryCardobjeto dentro CardReader. ¡Ahora estamos pasando un InputStream objeto al InputStreamReaderconstructor! Usamos nuestra System.invariable familiar como InputStream:

public static void main(String[] args) { 

   InputStreamReader inputStreamReader = new InputStreamReader(System.in); 
}
Y, de hecho, mirando la documentación de InputStreamReader, podemos ver que la adaptación tuvo éxito :) Ahora tenemos métodos para leer caracteres a nuestra disposición. Patrón de diseño del adaptador - 6Y aunque nuestro System.inobjeto (el flujo vinculado al teclado) inicialmente no permitía esto, los creadores del lenguaje resolvieron este problema implementando el patrón de adaptador. La Readerclase abstracta, como la mayoría de las clases de E/S, tiene un hermano gemelo:  Writer. Tiene la misma gran ventaja que  Reader proporciona una interfaz conveniente para trabajar con personajes. Con los flujos de salida, el problema y su solución tienen el mismo aspecto que con los flujos de entrada. Hay una OutputStreamclase que solo puede escribir bytes, hay unaWriterclase abstracta que sabe cómo trabajar con personajes, y hay dos interfaces incompatibles. Este problema se resuelve una vez más con el patrón adaptador. Usamos la OutputStreamWriterclase para adaptar fácilmente las dos interfaces de las clases Writer y  OutputStream entre sí. Después de pasar un OutputStreamflujo de bytes al constructor, ¡podemos usar an OutputStreamWriterpara escribir caracteres en lugar de bytes!

import java.io.*; 

public class Main { 

   public static void main(String[] args) throws IOException { 

       OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt")); 
       streamWriter.write(32144); 
       streamWriter.close();
   } 
}
Escribimos el carácter con el código 32144 (綐) en nuestro archivo, eliminando la necesidad de trabajar con bytes :) Eso es todo por hoy. ¡Nos vemos en las próximas lecciones! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION