CodeGym /Java blogg /Slumpmässig /Vilka problem löser adapterns designmönster?
John Squirrels
Nivå
San Francisco

Vilka problem löser adapterns designmönster?

Publicerad i gruppen
Mjukvaruutveckling försvåras av inkompatibla komponenter som måste fungera tillsammans. Om du till exempel behöver integrera ett nytt bibliotek med en gammal plattform skriven i tidigare versioner av Java, kan du stöta på inkompatibla objekt, eller snarare inkompatibla gränssnitt. Vilka problem löser adapterns designmönster?  - 1Vad ska man göra i det här fallet? Skriva om koden? Det kan vi inte göra, eftersom att analysera systemet kommer att ta mycket tid eller så kommer programmets interna logik att kränkas. För att lösa detta problem skapades adaptermönstret. Det hjälper objekt med inkompatibla gränssnitt att fungera tillsammans. Låt oss se hur man använder det!

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 ett Excusegränssnitt som har generateExcuse(), likeExcuse()och dislikeExcuse()metoder.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Klassen WorkExcuseimplementerar 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: , generateExcusesom genererar en ursäkt och , dislikeExcusesom 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 Excuseoch ett bibliotek med en SuperStudentExcuseklass som implementerar StudentExcusegrä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: Vilka problem löser adapterns designmönster?  - 2Denna version av systemet fungerar bara med Excuse-gränssnittet. Du kan inte skriva om koden: i en stor applikation kan det bli en lång process att göra sådana ändringar eller bryta applikationens logik. Vi skulle kunna introducera ett basgränssnitt och utöka hierarkin: Vilka problem löser adapterns designmönster?  - 3För att göra detta måste vi byta namn på Excusegrä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 det Middleware. 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 Middlewareatt anropa det första objektets metoder. Middlewaretar emot samtal och vidarekopplar dem på ett kompatibelt sätt till det andra objektet. Här är implementeringen Middlewaremed generateExcuseoch dislikeExcusemetoderna:

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 generateExcuseskickar helt enkelt anropet till ett annat objekt, utan några ytterligare ändringar. Metoden dislikeExcusekrä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 likeExcusemetoden, som är en del av gränssnittet Excusemen inte en del av StudentExcusegränssnittet? Den nya funktionen stöder inte denna operation. Den UnsupportedOperationExceptionuppfanns för denna situation. Den kastas om den begärda operationen inte stöds. Låt oss använda det. Så här Middlewareser 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

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

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

  1. Se först till att du har ett problem som det här mönstret kan lösa.

  2. Definiera klientgränssnittet som ska användas för att indirekt interagera med inkompatibla objekt.

  3. Få adapterklassen att ärva det gränssnitt som definierades i föregående steg.

  4. Skapa ett fält i adapterklassen för att lagra en referens till adaptee-objektet. Denna referens skickas till konstruktören.

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

  6. Om applikationen endast använder adapterklassen genom klientgränssnittet (som i exemplet ovan), så kan adaptern utökas smärtfritt i framtiden.

Naturligtvis är detta designmönster inte ett universalmedel för alla sjukdomar, men det kan hjälpa dig att elegant lösa problemet med inkompatibilitet mellan objekt med olika gränssnitt. En utvecklare som kan grundmönstren ligger flera steg före den som bara vet hur man skriver algoritmer, eftersom designmönster krävs för att skapa seriösa applikationer. Återanvändning av kod är inte så svårt, och underhållet blir roligt. Det är allt för idag! Men vi kommer snart att fortsätta lära känna olika designmönster :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION