CodeGym /Java blog /Tilfældig /Hvilke problemer løser adapterdesignmønsteret?
John Squirrels
Niveau
San Francisco

Hvilke problemer løser adapterdesignmønsteret?

Udgivet i gruppen
Softwareudvikling vanskeliggøres af inkompatible komponenter, der skal arbejde sammen. For eksempel, hvis du har brug for at integrere et nyt bibliotek med en gammel platform skrevet i tidligere versioner af Java, kan du støde på inkompatible objekter eller rettere inkompatible grænseflader. Hvilke problemer løser adapterdesignmønsteret?  - 1Hvad skal man gøre i dette tilfælde? Omskrive koden? Det kan vi ikke, fordi det vil tage meget tid at analysere systemet, eller applikationens interne logik vil blive krænket. For at løse dette problem blev adaptermønsteret oprettet. Det hjælper objekter med inkompatible grænseflader til at arbejde sammen. Lad os se, hvordan du bruger det!

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 en Excusegrænseflade, der har generateExcuse(), likeExcuse()og dislikeExcuse()metoder.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Klassen WorkExcuseimplementerer 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 SuperStudentExcuseklasse, der implementerer StudentExcusegræ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: Hvilke problemer løser adapterdesignmønsteret?  - 2Denne 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: Hvilke problemer løser adapterdesignmønsteret?  - 3For 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 det Middleware. Vores adapter skal implementere en grænseflade, der er kompatibel med et af objekterne. Lad det være Excuse. Dette gør det muligt Middlewareat kalde det første objekts metoder. Middlewaremodtager opkald og viderestiller dem på en kompatibel måde til det andet objekt. Her er implementeringen Middlewaremed generateExcuseog dislikeExcusemetoderne:

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 generateExcusesender blot opkaldet til et andet objekt uden yderligere ændringer. Metoden dislikeExcusekræ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 likeExcusemetoden, som er en del af grænsefladen, Excusemen ikke en del af grænsefladen StudentExcuse? Den nye funktionalitet understøtter ikke denne handling. Den UnsupportedOperationExceptionblev opfundet til denne situation. Den kastes, hvis den anmodede handling ikke understøttes. Lad os bruge det. Sådan Middlewareser 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

  1. 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å.

  2. 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

  1. Først skal du være sikker på, at du har et problem, som dette mønster kan løse.

  2. Definer klientgrænsefladen, der skal bruges til indirekte at interagere med inkompatible objekter.

  3. Få adapterklassen til at arve den grænseflade, der er defineret i det foregående trin.

  4. I adapterklassen skal du oprette et felt til at gemme en reference til det tilpassede objekt. Denne reference videregives til konstruktøren.

  5. 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.

  6. Hvis applikationen kun bruger adapterklassen gennem klientgrænsefladen (som i eksemplet ovenfor), så kan adapteren udvides smertefrit i fremtiden.

Selvfølgelig er dette designmønster ikke et universalmiddel for alle dårligdomme, men det kan hjælpe dig med elegant at løse problemet med inkompatibilitet mellem objekter med forskellige grænseflader. En udvikler, der kender de grundlæggende mønstre, er flere skridt foran dem, der kun ved, hvordan man skriver algoritmer, fordi designmønstre er nødvendige for at skabe seriøse applikationer. Genbrug af kode er ikke så svært, og vedligeholdelse bliver sjovt. Det var alt for i dag! Men vi vil snart fortsætte med at lære forskellige designmønstre at kende :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION