¡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.
En 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.
Supongamos 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:
Un conjunto de métodos es precisamente lo que es una interfaz. Como puede ver, esta clase tiene un
Sin embargo, las interfaces
Y aunque nuestro


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 elCardReader
class 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 clasesReader
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
. Reader
es 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 BufferedReader
y InputStreamReader
, que son sus descendientes :)
public class BufferedReader extends Reader {
…
}
public class InputStreamReader extends Reader {
…
}
La InputStreamReader
clase es un adaptador clásico. Como probablemente recuerde, podemos pasar un InputStream
objeto a su constructor. Para ello, solemos utilizar la System.in
variable:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Pero que InputStreamReader
hace? Como todo adaptador, convierte una interfaz en otra. En este caso, la InputStream
interfaz a la Reader
interfaz. Inicialmente, tenemos la InputStream
clase. Funciona bien, pero solo puede usarlo para leer bytes individuales. Además, tenemos una Reader
clase 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 InputStream
clase. 
read()
(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 Reader
clase abstracta . También podemos ver esto en la documentación. 
InputStream
y Reader
son 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 MemoryCard
objeto dentro CardReader
. ¡Ahora estamos pasando un InputStream
objeto al InputStreamReader
constructor! Usamos nuestra System.in
variable 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. 
System.in
objeto (el flujo vinculado al teclado) inicialmente no permitía esto, los creadores del lenguaje resolvieron este problema implementando el patrón de adaptador. La Reader
clase 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 OutputStream
clase que solo puede escribir bytes, hay unaWriter
clase 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 OutputStreamWriter
clase para adaptar fácilmente las dos interfaces de las clases Writer
y OutputStream
entre sí. Después de pasar un OutputStream
flujo de bytes al constructor, ¡podemos usar an OutputStreamWriter
para 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! :)
GO TO FULL VERSION