
Mer om problemet
Först kommer vi att simulera beteendet hos det gamla systemet. Anta att det genererar ursäkter för att komma för sent till jobbet eller skolan. För att göra detta har den ettExcuse
gränssnitt som har generateExcuse()
, likeExcuse()
och dislikeExcuse()
metoder.
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
Klassen WorkExcuse
implementerar detta gränssnitt:
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
}
}
Låt oss testa vårt exempel:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Produktion:
"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.
Föreställ dig nu att du har lanserat en ursäktsgenererande tjänst, samlat in statistik och märkt att de flesta av dina användare är universitetsstudenter. För att bättre tjäna den här gruppen bad du en annan utvecklare att skapa ett system som genererar ursäkter specifikt för universitetsstudenter. Utvecklingsteamet genomförde marknadsundersökningar, rangordnade ursäkter, kopplade upp lite artificiell intelligens och integrerade tjänsten med trafikrapporter, väderrapporter och så vidare. Nu har du ett bibliotek för att skapa ursäkter för universitetsstudenter, men det har ett annat gränssnitt: StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Det här gränssnittet har två metoder: , generateExcuse
som genererar en ursäkt och , dislikeExcuse
som förhindrar att ursäkten visas igen i framtiden. Tredjepartsbiblioteket kan inte redigeras, dvs du kan inte ändra dess källkod. Det vi har nu är ett system med två klasser som implementerar gränssnittet Excuse
och ett bibliotek med en SuperStudentExcuse
klass som implementerar StudentExcuse
gränssnittet:
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
}
}
Koden kan inte ändras. Den nuvarande klasshierarkin ser ut så här: 

Excuse
gränssnittet. Men den extra hierarkin är oönskad i seriösa tillämpningar: att införa ett gemensamt rotelement bryter arkitekturen. Du bör implementera en mellanklass som låter oss använda både den nya och gamla funktionen med minimala förluster. Kort sagt, du behöver en adapter .
Principen bakom adaptermönstret
En adapter är ett mellanobjekt som gör att ett objekts metodanrop kan förstås av ett annat. Låt oss implementera en adapter för vårt exempel och kalla detMiddleware
. Vår adapter måste implementera ett gränssnitt som är kompatibelt med ett av objekten. Låt det vara Excuse
. Detta gör det möjligt Middleware
att anropa det första objektets metoder. Middleware
tar emot samtal och vidarekopplar dem på ett kompatibelt sätt till det andra objektet. Här är implementeringen Middleware
med generateExcuse
och dislikeExcuse
metoderna:
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
}
Testning (i klientkod):
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
}
}
Produktion:
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:
En otrolig ursäkt anpassad till rådande väderförhållanden, trafikstockningar eller förseningar i kollektivtrafikens tidtabeller. Metoden generateExcuse
skickar helt enkelt anropet till ett annat objekt, utan några ytterligare ändringar. Metoden dislikeExcuse
krävde att vi först svartlistade ursäkten. Möjligheten att utföra mellanliggande databehandling är en anledning till att människor älskar adaptermönstret. Men hur är det med likeExcuse
metoden, som är en del av gränssnittet Excuse
men inte en del av StudentExcuse
gränssnittet? Den nya funktionen stöder inte denna operation. Den UnsupportedOperationException
uppfanns för denna situation. Den kastas om den begärda operationen inte stöds. Låt oss använda det. Så här Middleware
ser klassens nya implementering ut:
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.
}
}
Vid första anblicken verkar den här lösningen inte särskilt bra, men att imitera funktionaliteten kan komplicera situationen. Om kunden är uppmärksam och adaptern är väl dokumenterad är en sådan lösning acceptabel.
När ska man använda en adapter
-
När du behöver använda en tredje parts klass, men dess gränssnitt är inkompatibelt med huvudapplikationen. Exemplet ovan visar hur man skapar ett adapterobjekt som radbryter anrop i ett format som ett målobjekt kan förstå.
-
När flera befintliga underklasser behöver någon gemensam funktionalitet. Istället för att skapa ytterligare underklasser (vilket kommer att leda till duplicering av kod) är det bättre att använda en adapter.
Fördelar och nackdelar
Fördel: Adaptern döljer detaljerna för bearbetning av förfrågningar från ett objekt till ett annat för klienten. Klientkoden tänker inte på att formatera data eller hantera anrop till målmetoden. Det är för komplicerat, och programmerare är lata :) Nackdel: Projektets kodbas kompliceras av ytterligare klasser. Om du har många inkompatibla gränssnitt kan antalet ytterligare klasser bli ohanterligt.Blanda inte ihop en adapter med en fasad eller dekoratör
Med endast en ytlig inspektion kan en adapter förväxlas med fasad- och dekorationsmönster. Skillnaden mellan en adapter och en fasad är att en fasad introducerar ett nytt gränssnitt och omsluter hela delsystemet. Och en dekoratör, till skillnad från en adapter, ändrar själva objektet snarare än gränssnittet.Steg-för-steg-algoritm
-
Se först till att du har ett problem som det här mönstret kan lösa.
-
Definiera klientgränssnittet som ska användas för att indirekt interagera med inkompatibla objekt.
-
Få adapterklassen att ärva det gränssnitt som definierades i föregående steg.
-
Skapa ett fält i adapterklassen för att lagra en referens till adaptee-objektet. Denna referens skickas till konstruktören.
-
Implementera alla klientgränssnittsmetoder i adaptern. En metod kan:
-
Skicka vidare samtal utan att göra några ändringar
-
Ändra eller komplettera data, öka/minska antalet samtal till målmetoden etc.
-
I extrema fall, om en viss metod förblir inkompatibel, kasta ett UnsupportedOperationException. Ostödda verksamheter måste vara strikt dokumenterade.
-
-
Om applikationen endast använder adapterklassen genom klientgränssnittet (som i exemplet ovan), så kan adaptern utökas smärtfritt i framtiden.
GO TO FULL VERSION