"Olá amigo!"

"Oi, Bilaabo!"

"Ainda temos algum tempo, então vou falar sobre mais três padrões."

"Mais três? Quantos são ao todo?"

"Atualmente, existem dezenas de padrões populares, mas o número de «soluções bem-sucedidas» é ilimitado."

"Entendo. Então eu tenho que aprender várias dezenas de padrões?"

"Até que você tenha experiência real em programação, eles não lhe darão muito."

"É melhor você adquirir um pouco mais de experiência e, em um ano, retornar a esse tópico e tentar entendê-los mais profundamente. Pelo menos algumas dezenas dos padrões de design mais populares."

"É um pecado não usar a experiência de outra pessoa e, em vez disso, inventar algo pela 110ª vez."

"Concordo."

"Então vamos começar."

Padrão de adaptador (ou wrapper)

Padrões: adaptador, proxy, ponte - 1

"Imagine que você vem para a China e descobre que as tomadas seguem um padrão diferente. Os furos não são redondos, mas planos. Nesse caso, você vai precisar de um adaptador."

"Algo semelhante também pode acontecer na programação. As classes operam em interfaces semelhantes, mas diferentes. Por isso, precisamos fazer um adaptador entre elas."

"Isto é o que parece:"

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

interface TotalTime
{
 int getTotalSeconds();
}

"Suponha que temos duas interfaces: Time  e  TotalTime ."

"A interface Time permite obter a hora atual usando os métodos getSeconds (),  getMinutes () e  getHours ()."

"A interface TotalTime permite obter o número de segundos que se passaram desde a meia-noite até o momento atual."

"O que devemos fazer se tivermos um objeto TotalTime , mas precisamos de um objeto Time ou vice-versa?"

"Podemos escrever classes adaptadoras para isso. Por exemplo:"

Exemplo
 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());

"E um adaptador na outra direção:"

Exemplo
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. Eu gosto. Mas há algum exemplo?"

"Claro! Por exemplo, InputStreamReader é um adaptador clássico. Ele converte um InputStream em um Reader."

"Às vezes, esse padrão também é chamado de wrapper, porque a nova classe 'envolve' outro objeto."

"Você pode ler algumas outras coisas interessantes aqui ."

padrão de proxy

"O padrão proxy é um pouco semelhante ao padrão wrapper. Mas seu objetivo não é converter interfaces, mas controlar o acesso ao objeto original armazenado dentro da classe proxy. Além disso, tanto a classe original quanto o proxy geralmente têm a mesma interface, o que torna mais fácil substituir um objeto da classe original por um objeto proxy."

"Por exemplo:"

Interface da classe real
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementação da classe 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);
 }
}
Implementação da classe 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);
 }
}

"No exemplo acima, descrevemos a interface Bank e a classe CitiBank , uma implementação dessa interface."

"A interface permite obter ou alterar o saldo da conta de um usuário."

E então criamos o BankSecurityProxy , que também implementa a interface Bank e armazena uma referência a uma interface Bank diferente. Os métodos desta classe verificam se o usuário é o dono da conta ou o gerente do banco. Se não for, uma SecurityException será lançada."

"Veja como funciona na prática:"

Código sem verificações de segurança:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Código com verificações de segurança:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"No primeiro exemplo, criamos um objeto de banco e chamamos seu método setUserMoney .

"No segundo exemplo, envolvemos o objeto banco original em um objeto BankSecurityProxy . Eles têm a mesma interface, então o código subsequente continua a funcionar como antes. Mas agora uma verificação de segurança será realizada sempre que um método for chamado."

"Legal!"

"Sim. Você pode ter muitos desses proxies. Por exemplo, você pode adicionar outro proxy que verifique se o saldo da conta é muito grande. O gerente do banco pode decidir colocar muito dinheiro em sua própria conta e fugir para Cuba com os fundos ."

"Além disso... A criação de todas essas cadeias de objetos pode ser colocada em uma classe BankFactory , onde você pode habilitar/desabilitar os que você precisa."

" BufferedReader funciona usando um princípio semelhante. É um Reader , mas faz um trabalho adicional."

"Esta abordagem permite «montar» um objecto com a funcionalidade necessária a partir de várias «peças»."

"Ah, quase esqueci. Proxies são usados ​​muito mais amplamente do que acabei de mostrar. Você pode ler sobre outros usos aqui ."

padrão de ponte

Padrões: adaptador, proxy, ponte - 2

"Às vezes, durante a execução de um programa, é necessário alterar significativamente a funcionalidade de um objeto. Por exemplo, suponha que você tenha um jogo com um personagem burro que mais tarde é transformado em dragão por um mago. O dragão tem comportamento e propriedades completamente diferentes, mas é o mesmo objeto!"

"Não podemos simplesmente criar um novo objeto e acabar com ele?"

"Nem sempre. Suponha que seu burro seja amigo de vários personagens, ou talvez esteja sob a influência de vários feitiços, ou esteja envolvido em certas missões. Em outras palavras, o objeto pode já estar em uso em muitos lugares - e vinculado a muitos outros objetos. Portanto, neste caso, não é uma opção simplesmente criar um novo objeto."

"Bem, o que pode ser feito então?"

"O padrão Bridge é uma das soluções mais bem-sucedidas."

"Este padrão envolve a divisão de um objeto em dois objetos: um «objeto de interface» e um «objeto de implementação»."

"Qual é a diferença entre a interface e a classe que a implementa?"

"Com uma interface e uma classe, acabamos com um objeto. Mas aqui — temos dois. Veja este exemplo:"

Exemplo
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()
 {
 }
}

"E então você pode declarar várias subclasses de UserImpl, por exemplo, UserDonkey (burro) e UserDragon (dragão)."

"Ainda assim, eu realmente não entendo como isso vai funcionar."

"Bem, algo assim:"

Exemplo
class User
{
 private UserImpl realUser;

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

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

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

"Então é algo como um proxy."

"Sim, mas em um proxy o objeto principal pode ser armazenado em algum lugar separado, e o código funciona com proxies. Aqui estamos dizendo que todo mundo trabalha com o objeto principal, mas suas partes mudam internamente."

"Ah. Obrigado. Você pode me dar um link para ler mais sobre isso?"

"Claro, amigo, meu amigo. Aqui está: padrão de ponte ."