"¡Hola, amigo!"

"¡Hola, Bilaabo!"

"Todavía nos queda algo de tiempo, así que te contaré sobre tres patrones más".

"¿Tres más? ¿Cuántos hay en total?"

"Actualmente hay docenas de patrones populares, pero el número de 'soluciones exitosas' es ilimitado".

"Ya veo. ¿Así que tengo que aprender varias docenas de patrones?"

"Hasta que no tengas experiencia real en programación, no te darán mucho".

"Será mejor que obtengas un poco más de experiencia y luego, en un año, vuelvas a este tema y trates de comprenderlo más profundamente. Al menos un par de docenas de los patrones de diseño más populares".

"Es un pecado no usar la experiencia de otra persona y en su lugar inventar algo por 110ª vez".

"Estoy de acuerdo."

"Entonces comencemos".

Patrón de adaptador (o envoltura)

Patrones: Adaptador, Proxy, Puente - 1

"Imagina que vienes a China y encuentras que los enchufes eléctricos siguen un estándar diferente. Los agujeros no son redondos, sino planos. En este caso, necesitarás un adaptador".

"Algo similar también puede suceder en la programación. Las clases operan en interfaces similares pero diferentes. Por lo tanto, debemos hacer un adaptador entre ellas".

"Así es como se ve:"

Ejemplo
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

"Supongamos que tenemos dos interfaces: Time  y  TotalTime ".

"La interfaz de tiempo le permite obtener la hora actual utilizando los métodos getSeconds (),  getMinutes () y  getHours ()".

"La interfaz TotalTime le permite obtener la cantidad de segundos que han pasado desde la medianoche hasta el momento actual".

"¿Qué debemos hacer si tenemos un objeto TotalTime , pero necesitamos un objeto Time o viceversa?"

"Podemos escribir clases de adaptadores para esto. Por ejemplo:"

Ejemplo
 class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
 
Uso
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"Y un adaptador en la otra dirección:"

Ejemplo
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Uso
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. Me gusta. Pero, ¿hay algún ejemplo?"

"¡Por supuesto! Como ejemplo, InputStreamReader es un adaptador clásico. Convierte un InputStream en un Reader".

"A veces, este patrón también se denomina contenedor, porque la nueva clase 'envuelve' otro objeto".

"Puedes leer algunas otras cosas interesantes aquí ".

patrón de representación

"El patrón de proxy es algo similar al patrón de envoltura. Pero su propósito no es convertir interfaces, sino controlar el acceso al objeto original almacenado dentro de la clase de proxy. Además, tanto la clase original como el proxy suelen tener la misma interfaz, lo que hace que sea más fácil reemplazar un objeto de la clase original con un objeto proxy".

"Por ejemplo:"

Interfaz de la clase real
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementación de la clase original.
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementación de la clase proxy
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

"En el ejemplo anterior, describimos la interfaz Bank y la clase CitiBank , una implementación de esta interfaz".

"La interfaz le permite obtener o cambiar el saldo de la cuenta de un usuario".

Y luego creamos BankSecurityProxy , que también implementa la interfaz Bank y almacena una referencia a una interfaz Bank diferente. Los métodos de esta clase comprueban si el usuario es el propietario de la cuenta o el administrador del banco. Si no es así, se lanza una SecurityException".

"Así es como funciona en la práctica:"

Código sin controles de seguridad:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Código con controles de seguridad:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"En el primer ejemplo, creamos un objeto de banco y llamamos a su método setUserMoney .

"En el segundo ejemplo, envolvemos el objeto del banco original en un objeto BankSecurityProxy . Tienen la misma interfaz, por lo que el código posterior continúa funcionando como lo hacía. Pero ahora se realizará una verificación de seguridad cada vez que se llame a un método".

"¡Fresco!"

"Sí. Puede tener muchos proxies de este tipo. Por ejemplo, podría agregar otro proxy que verifique si el saldo de la cuenta es demasiado grande. El gerente del banco podría decidir poner una gran cantidad de dinero en su propia cuenta y fugarse a Cuba con los fondos ."

"Además... La creación de todas estas cadenas de objetos se puede poner en una clase BankFactory , donde puedes habilitar/deshabilitar los que necesites".

" BufferedReader funciona con un principio similar. Es un Reader , pero realiza un trabajo adicional".

"Este enfoque le permite "ensamblar" un objeto con la funcionalidad necesaria a partir de varias "piezas"."

"Oh, casi lo olvido. Los proxies se usan mucho más ampliamente de lo que te acabo de mostrar. Puedes leer sobre otros usos aquí ".

Patrón de puente

Patrones: Adaptador, Proxy, Puente - 2

"A veces, mientras se ejecuta un programa, es necesario cambiar significativamente la funcionalidad de un objeto. Por ejemplo, suponga que tiene un juego con un burro que luego un mago convierte en dragón. El dragón tiene un comportamiento y propiedades completamente diferentes, pero es el mismo objeto!"

"¿No podemos simplemente crear un nuevo objeto y terminar con él?"

"No siempre. Supongamos que tu burro es amigo de un grupo de personajes, o tal vez estuvo bajo la influencia de varios hechizos, o estuvo involucrado en ciertas misiones. En otras palabras, el objeto ya puede estar en uso en muchos lugares: y vinculado a muchos otros objetos. Entonces, en este caso, no es una opción simplemente crear un nuevo objeto".

"Bueno, ¿qué se puede hacer entonces?"

"El patrón Bridge es una de las soluciones más exitosas".

"Este patrón implica dividir un objeto en dos objetos: un «objeto de interfaz» y un «objeto de implementación»."

"¿Cuál es la diferencia entre la interfaz y la clase que la implementa?"

"Con una interfaz y una clase, terminamos con un objeto. Pero aquí, tenemos dos. Mira este ejemplo:"

Ejemplo
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

"Y luego puede declarar varias subclases de UserImpl, por ejemplo, UserDonkey (burro) y UserDragon (dragón)".

"De todos modos, realmente no entiendo cómo funcionará esto".

"Bueno, algo como esto:"

Ejemplo
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Cómo funciona
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"Así que es algo así como un proxy".

"Sí, pero en un proxy, el objeto principal podría almacenarse en algún lugar por separado, y el código funciona con proxies en su lugar. Aquí estamos diciendo que todos trabajan con el objeto principal, pero sus partes cambian internamente".

"Ah. Gracias. ¿Me darías un enlace para leer más al respecto?"

"Por supuesto, Amigo, mi amigo. Aquí tienes: patrón de puente ".