
Mais sobre o problema
Primeiro, vamos simular o comportamento do sistema antigo. Suponha que isso gere desculpas para chegar atrasado ao trabalho ou à escola. Para fazer isso, ele possui umaExcuse
interface que possui generateExcuse()
, likeExcuse()
e dislikeExcuse()
métodos.
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
A WorkExcuse
classe implementa esta interface:
public class WorkExcuse implements Excuse {
private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
"the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
"my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};
@Override
public String generateExcuse() { // Randomly select an excuse from the array
String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
apologies[(int) Math.round(Math.random() + 1)];
return result;
}
@Override
public void likeExcuse(String excuse) {
// Duplicate the element in the array so that its chances of being chosen are higher
}
@Override
public void dislikeExcuse(String excuse) {
// Remove the item from the array
}
}
Vamos testar nosso exemplo:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Saída:
"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Agora imagine que você lançou um serviço gerador de desculpas, coletou estatísticas e percebeu que a maioria de seus usuários são estudantes universitários. Para atender melhor esse grupo, você pediu a outro desenvolvedor que criasse um sistema que gerasse desculpas especificamente para universitários. A equipe de desenvolvimento realizou pesquisas de mercado, classificou as desculpas, conectou alguma inteligência artificial e integrou o serviço a relatórios de tráfego, boletins meteorológicos e assim por diante. Agora você tem uma biblioteca para gerar desculpas para universitários, mas com uma interface diferente: StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Essa interface possui dois métodos: generateExcuse
, que gera uma desculpa, e dislikeExcuse
, que evita que a desculpa apareça novamente no futuro. A biblioteca de terceiros não pode ser editada, ou seja, você não pode alterar seu código-fonte. O que temos agora é um sistema com duas classes que implementam a Excuse
interface e uma biblioteca com uma SuperStudentExcuse
classe que implementa a StudentExcuse
interface:
public class SuperStudentExcuse implements StudentExcuse {
@Override
public String generateExcuse() {
// Logic for the new functionality
return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
}
@Override
public void dislikeExcuse(String excuse) {
// Adds the reason to a blacklist
}
}
O código não pode ser alterado. A hierarquia de classes atual se parece com isso: 

Excuse
interface. Mas a hierarquia extra é indesejável em aplicações sérias: a introdução de um elemento raiz comum quebra a arquitetura. Você deve implementar uma classe intermediária que nos permitirá usar a funcionalidade nova e antiga com perdas mínimas. Resumindo, você precisa de um adaptador .
O princípio por trás do padrão adaptador
Um adaptador é um objeto intermediário que permite que as chamadas de método de um objeto sejam compreendidas por outro. Vamos implementar um adaptador para nosso exemplo e chamá-lo deMiddleware
. Nosso adaptador deve implementar uma interface compatível com um dos objetos. Deixe estar Excuse
. Isso permite Middleware
chamar os métodos do primeiro objeto. Middleware
recebe chamadas e as encaminha de forma compatível para o segundo objeto. Aqui está a Middleware
implementação com os métodos generateExcuse
e dislikeExcuse
:
public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
}
@Override
public void dislikeExcuse(String excuse) {
// The method first adds the excuse to the blacklist,
// Then passes it to the dislikeExcuse method of the superStudentExcuse object.
}
// The likeExcuse method will appear later
}
Teste (no código do cliente):
public class Test {
public static void main(String[] args) {
Excuse excuse = new WorkExcuse(); // We create objects of the classes
StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
System.out.println("An ordinary excuse for an employee:");
System.out.println(excuse.generateExcuse());
System.out.println("\n");
Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
System.out.println("Using new functionality with the adapter:");
System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
}
}
Saída:
An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position.
Using new functionality with the adapter:
Uma desculpa incrível adaptada às atuais condições meteorológicas, engarrafamentos ou atrasos nos horários dos transportes públicos. O generateExcuse
método simplesmente passa a chamada para outro objeto, sem nenhuma alteração adicional. O dislikeExcuse
método exigia que primeiro colocássemos a desculpa na lista negra. A capacidade de executar o processamento intermediário de dados é uma razão pela qual as pessoas adoram o padrão do adaptador. Mas e quanto ao likeExcuse
método, que faz parte da Excuse
interface, mas não faz parte da StudentExcuse
interface? A nova funcionalidade não suporta esta operação. O UnsupportedOperationException
foi inventado para esta situação. Ele é lançado se a operação solicitada não for suportada. Vamos usá-lo. Esta é a Middleware
aparência da nova implementação da classe:
public class Middleware implements Excuse {
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) {
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse();
}
@Override
public void likeExcuse(String excuse) {
throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
}
@Override
public void dislikeExcuse(String excuse) {
// The method accesses a database to fetch additional information,
// and then passes it to the superStudentExcuse object's dislikeExcuse method.
}
}
À primeira vista, esta solução não parece muito boa, mas imitar a funcionalidade pode complicar a situação. Se o cliente prestar atenção e o adaptador estiver bem documentado, tal solução é aceitável.
Quando usar um adaptador
-
Quando você precisa usar uma classe de terceiros, mas sua interface é incompatível com o aplicativo principal. O exemplo acima mostra como criar um objeto adaptador que agrupa chamadas em um formato que um objeto de destino possa entender.
-
Quando várias subclasses existentes precisam de alguma funcionalidade comum. Em vez de criar subclasses adicionais (o que levará à duplicação de código), é melhor usar um adaptador.
Vantagens e desvantagens
Vantagem: O adaptador oculta do cliente os detalhes do processamento de solicitações de um objeto para outro. O código do cliente não pensa em formatar dados ou manipular chamadas para o método de destino. É muito complicado e os programadores são preguiçosos :) Desvantagem: A base de código do projeto é complicada por classes adicionais. Se você tiver muitas interfaces incompatíveis, o número de classes adicionais pode se tornar incontrolável.Não confunda adaptador com fachada ou decorador
Com apenas uma inspeção superficial, um adaptador pode ser confundido com os padrões da fachada e do decorador. A diferença entre um adaptador e uma fachada é que uma fachada introduz uma nova interface e envolve todo o subsistema. E um decorador, ao contrário de um adaptador, altera o próprio objeto em vez da interface.Algoritmo passo a passo
-
Primeiro, certifique-se de ter um problema que esse padrão possa resolver.
-
Defina a interface do cliente que será usada para interagir indiretamente com objetos incompatíveis.
-
Faça com que a classe do adaptador herde a interface definida na etapa anterior.
-
Na classe do adaptador, crie um campo para armazenar uma referência ao objeto adaptee. Essa referência é passada para o construtor.
-
Implemente todos os métodos de interface do cliente no adaptador. Um método pode:
-
Repasse as chamadas sem fazer nenhuma alteração
-
Modifique ou complemente dados, aumente/diminua o número de chamadas para o método de destino, etc.
-
Em casos extremos, se um determinado método permanecer incompatível, lance um UnsupportedOperationException. As operações sem suporte devem ser estritamente documentadas.
-
-
Se o aplicativo usar apenas a classe do adaptador por meio da interface do cliente (como no exemplo acima), o adaptador poderá ser expandido facilmente no futuro.
GO TO FULL VERSION