Mere om problemet
Først vil vi simulere adfærden af det gamle system. Antag, at det genererer undskyldninger for at komme for sent på arbejde eller i skole. For at gøre dette har den enExcuse
grænseflade, der har generateExcuse()
, likeExcuse()
og dislikeExcuse()
metoder.
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
Klassen WorkExcuse
implementerer denne grænseflade:
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
}
}
Lad os teste vores eksempel:
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.
Forestil dig nu, at du har lanceret en undskyldningsgenererende tjeneste, indsamlet statistik og bemærket, at de fleste af dine brugere er universitetsstuderende. For bedre at kunne betjene denne gruppe bad du en anden udvikler om at skabe et system, der genererer undskyldninger specifikt til universitetsstuderende. Udviklingsteamet foretog markedsundersøgelser, rangerede undskyldninger, tilsluttede noget kunstig intelligens og integrerede tjenesten med trafikrapporter, vejrmeldinger og så videre. Nu har du et bibliotek til at generere undskyldninger for universitetsstuderende, men det har en anden grænseflade: StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Denne grænseflade har to metoder: generateExcuse
, som genererer en undskyldning, og dislikeExcuse
, som forhindrer undskyldningen i at dukke op igen i fremtiden. Tredjepartsbiblioteket kan ikke redigeres, dvs. du kan ikke ændre dets kildekode. Det, vi har nu, er et system med to klasser, der implementerer grænsefladen Excuse
, og et bibliotek med en SuperStudentExcuse
klasse, der implementerer StudentExcuse
grænsefladen:
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 ikke ændres. Det nuværende klassehierarki ser således ud: Denne version af systemet fungerer kun med Excuse-grænsefladen. Du kan ikke omskrive koden: I en stor applikation kan det blive en langvarig proces at foretage sådanne ændringer eller bryde applikationens logik. Vi kunne introducere en basisgrænseflade og udvide hierarkiet: For at gøre dette skal vi omdøbe grænsefladen Excuse
. Men det ekstra hierarki er uønsket i seriøse applikationer: at indføre et fælles rodelement bryder arkitekturen. Du bør implementere en mellemklasse, der giver os mulighed for at bruge både den nye og gamle funktionalitet med minimale tab. Kort sagt, du skal bruge en adapter .
Princippet bag adaptermønsteret
En adapter er et mellemobjekt, der gør det muligt at forstå et objekts metodekald af et andet. Lad os implementere en adapter til vores eksempel og kalde detMiddleware
. Vores adapter skal implementere en grænseflade, der er kompatibel med et af objekterne. Lad det være Excuse
. Dette gør det muligt Middleware
at kalde det første objekts metoder. Middleware
modtager opkald og viderestiller dem på en kompatibel måde til det andet objekt. Her er implementeringen Middleware
med generateExcuse
og dislikeExcuse
metoderne:
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
}
Test (i klientkode):
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 utrolig undskyldning tilpasset de aktuelle vejrforhold, trafikpropper eller forsinkelser i offentlig transport tidsplaner. Metoden generateExcuse
sender blot opkaldet til et andet objekt uden yderligere ændringer. Metoden dislikeExcuse
krævede, at vi først sortlistede undskyldningen. Evnen til at udføre mellemliggende databehandling er en grund til, at folk elsker adaptermønsteret. Men hvad med likeExcuse
metoden, som er en del af grænsefladen, Excuse
men ikke en del af grænsefladen StudentExcuse
? Den nye funktionalitet understøtter ikke denne handling. Den UnsupportedOperationException
blev opfundet til denne situation. Den kastes, hvis den anmodede handling ikke understøttes. Lad os bruge det. Sådan Middleware
ser klassens nye implementering ud:
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.
}
}
Ved første øjekast virker denne løsning ikke særlig god, men efterligning af funktionaliteten kan komplicere situationen. Hvis klienten er opmærksom, og adapteren er veldokumenteret, er en sådan løsning acceptabel.
Hvornår skal man bruge en adapter
-
Når du skal bruge en tredjepartsklasse, men dens grænseflade er inkompatibel med hovedapplikationen. Eksemplet ovenfor viser, hvordan man opretter et adapterobjekt, der ombryder opkald i et format, som et målobjekt kan forstå.
-
Når flere eksisterende underklasser har brug for noget fælles funktionalitet. I stedet for at oprette yderligere underklasser (hvilket vil føre til duplikering af kode), er det bedre at bruge en adapter.
Fordele og ulemper
Fordel: Adapteren skjuler detaljerne for behandling af anmodninger fra et objekt til et andet for klienten. Klientkoden tænker ikke på formatering af data eller håndtering af opkald til målmetoden. Det er for kompliceret, og programmører er dovne :) Ulempe: Projektets kodebase er kompliceret af yderligere klasser. Hvis du har mange inkompatible grænseflader, kan antallet af ekstra klasser blive uoverskueligt.Forveksle ikke en adapter med en facade eller dekoratør
Med kun en overfladisk inspektion kunne en adapter forveksles med facade- og dekorationsmønstre. Forskellen mellem en adapter og en facade er, at en facade introducerer en ny grænseflade og omslutter hele delsystemet. Og en dekoratør ændrer i modsætning til en adapter selve objektet i stedet for grænsefladen.Trin-for-trin algoritme
-
Først skal du være sikker på, at du har et problem, som dette mønster kan løse.
-
Definer klientgrænsefladen, der skal bruges til indirekte at interagere med inkompatible objekter.
-
Få adapterklassen til at arve den grænseflade, der er defineret i det foregående trin.
-
I adapterklassen skal du oprette et felt til at gemme en reference til det tilpassede objekt. Denne reference videregives til konstruktøren.
-
Implementer alle klientgrænseflademetoderne i adapteren. En metode kan:
-
Videregiv opkald uden at foretage ændringer
-
Ændre eller supplere data, øge/mindske antallet af opkald til målmetoden osv.
-
I ekstreme tilfælde, hvis en bestemt metode forbliver inkompatibel, smid en UnsupportedOperationException. Ikke-understøttede operationer skal være strengt dokumenteret.
-
-
Hvis applikationen kun bruger adapterklassen gennem klientgrænsefladen (som i eksemplet ovenfor), så kan adapteren udvides smertefrit i fremtiden.
GO TO FULL VERSION