Patrón Adaptador

El patrón adaptador (Adapter Pattern) actúa como un puente entre dos interfaces incompatibles y pertenece a los patrones de diseño estructurales. A través de un middleware (adaptador), convierte la interfaz de una clase en otra interfaz que el cliente espera, permitiendo que clases que originalmente no podían trabajar juntas cooperen.

Este patrón involucra una única clase que se encarga de añadir funcionalidades de interfaces independientes o incompatibles. Un ejemplo real: el lector de tarjetas actúa como adaptador entre la tarjeta de memoria y el portátil. Se inserta la tarjeta de memoria en el lector y este en el portátil, de modo que se puede leer la tarjeta de memoria a través del portátil.

Supongamos que hay un reproductor de audio que solo puede reproducir archivos MP3. Ahora necesitamos reproducir archivos VLC y MP4, lo cual se puede lograr creando un adaptador:

  • Interfaz objetivo: Define una interfaz de reproductor de audio que pueda reproducir archivos en múltiples formatos.
  • Clase adaptada (Adaptee): El reproductor de audio existente, que solo puede reproducir archivos MP3.
  • Clase adaptadora (Adapter): Crea una nueva clase que implemente la interfaz objetivo y, internamente, utilice la clase adaptada para reproducir archivos MP3, agregando también soporte para archivos VLC y MP4.

Resumen

El patrón adaptador es un patrón de diseño de software que tiene como objetivo resolver problemas de compatibilidad entre diferentes interfaces.

Propósito: Convertir la interfaz de una clase en otra interfaz, de modo que las clases originalmente incompatibles puedan trabajar juntas.

Problema principal que resuelve: En un sistema de software, se necesita colocar objetos existentes en un nuevo entorno, pero la interfaz requerida por el nuevo entorno no coincide con la de los objetos existentes.

Escenarios de uso

  • Se necesita usar una clase existente, pero su interfaz no cumple con los requisitos del sistema.
  • Se desea crear una clase reutilizable que trabaje con varias clases no relacionadas (incluidas las que puedan introducirse en el futuro), las cuales pueden no tener una interfaz uniforme.
  • Integrar una clase en otro sistema de clases mediante la conversión de interfaz.

Método de implementación

  • Herencia o dependencia: Se recomienda usar la relación de dependencia en lugar de la herencia para mantener la flexibilidad.

Código clave

El adaptador hereda o depende de un objeto existente e implementa la interfaz objetivo requerida.

Ejemplos de aplicación

  • Adaptador de voltaje: Convierte 110V a 220V para adaptarse a los estándares eléctricos de diferentes países.
  • Conversión de interfaz: Por ejemplo, convertir la interfaz Enumeration de Java JDK 1.1 a la interfaz Iterator de JDK 1.2.
  • Ejecución multiplataforma: Ejecutar programas de Windows en Linux.
  • Conexión de base de datos: JDBC en Java interactúa con diferentes tipos de bases de datos mediante el patrón adaptador.

Ventajas

  • Promueve la cooperación entre clases, incluso si no tienen una relación directa.
  • Mejora la reutilización de las clases.
  • Aumenta la transparencia de las clases.
  • Proporciona una buena flexibilidad.

Desventajas

  • El uso excesivo de adaptadores puede provocar una estructura del sistema desordenada, difícil de entender y mantener.
  • En Java, dado que solo se puede heredar de una clase, solo se puede adaptar una clase, y la clase objetivo debe ser abstracta.

Recomendaciones de uso

  • El patrón adaptador debe usarse con cautela, especialmente en la fase de diseño detallado; se utiliza más para resolver problemas en sistemas existentes.
  • Cuando se considera modificar la interfaz de un sistema que funciona correctamente, el patrón adaptador es una opción adecuada.

De esta manera, el patrón adaptador puede expresar claramente su concepto central y su aplicación, evitando complejidades innecesarias.

Estructura

El patrón adaptador incluye los siguientes roles principales:

  • Interfaz objetivo (Target): Define la interfaz que necesita el cliente.
  • Clase adaptada (Adaptee): Define una interfaz ya existente que necesita ser adaptada.
  • Clase adaptadora (Adapter): Implementa la interfaz objetivo y, mediante composición o herencia, invoca los métodos de la clase adaptada para lograr la interfaz objetivo.

Implementación

Tenemos unaMediaPlayerinterfaz y una clase de entidad que implementa laMediaPlayerinterfaz.AudioPlayer. De forma predeterminada,AudioPlayerpuede reproducir archivos de audio en formato mp3.

También tenemos otra interfazAdvancedMediaPlayery una clase de entidad que implementa laAdvancedMediaPlayerinterfaz. Esta clase puede reproducir archivos en formato vlc y mp4.

Queremos queAudioPlayerreproduzca archivos de audio en otros formatos. Para lograr esta funcionalidad, necesitamos crear una clase adaptadora que implemente laMediaPlayerinterfaz.MediaAdapter, y usarAdvancedMediaPlayerel objeto para reproducir el formato deseado.

AudioPlayerUsando la clase adaptadoraMediaAdapterse pasa el tipo de audio requerido, sin necesidad de conocer la clase real que puede reproducir el formato de audio requerido.AdapterPatternDemoLa clase usaAudioPlayerla clase para reproducir varios formatos.

适配器模式的 UML 图

Paso 1

Crear interfaces para el reproductor de medios y el reproductor de medios avanzado.

MediaPlayer.java

public interface MediaPlayer { public void play(String audioType, String fileName); }

AdvancedMediaPlayer.java

public interface AdvancedMediaPlayer { public void playVlc(String fileName); public void playMp4(String fileName); }

Paso 2

Crear clases de entidad que implementan laAdvancedMediaPlayerinterfaz.

VlcPlayer.java

public class VlcPlayer implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: "+ fileName); } @Override public void playMp4(String fileName) { //No hacer nada } }

Mp4Player.java

public class Mp4Player implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { //No hacer nada } @Override public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: "+ fileName); } }

Paso 3

Crear una clase adaptadora que implementa laMediaPlayerinterfaz.

MediaAdapter.java

public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType){ if(audioType.equalsIgnoreCase("vlc") ){ advancedMusicPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")){ advancedMusicPlayer = new Mp4Player(); } } @Override public void play(String audioType, String fileName) { if(audioType.equalsIgnoreCase("vlc")){ advancedMusicPlayer.playVlc(fileName); }else if(audioType.equalsIgnoreCase("mp4")){ advancedMusicPlayer.playMp4(fileName); } } }

Paso 4

Crear una clase de entidad que implementa laMediaPlayerinterfaz.

AudioPlayer.java

public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; @Override public void play(String audioType, String fileName) { //Soporte integrado para reproducir archivos de música mp3 if(audioType.equalsIgnoreCase("mp3")){ System.out.println("Playing mp3 file. Name: "+ fileName); } //mediaAdapter proporciona soporte para reproducir otros formatos de archivo else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){ mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else{ System.out.println("Invalid media. "+ audioType + " format not supported"); } } }

Paso 5

Usar AudioPlayer para reproducir diferentes tipos de formatos de audio.

AdapterPatternDemo.java

public class AdapterPatternDemo { public static void main(String[] args) { AudioPlayer audioPlayer = new AudioPlayer(); audioPlayer.play("mp3", "beyond the horizon.mp3"); audioPlayer.play("mp4", "alone.mp4"); audioPlayer.play("vlc", "far far away.vlc"); audioPlayer.play("avi", "mind me.avi"); } }

Paso 6

Ejecutar el programa y mostrar el resultado:

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported
Otras extensiones