CodeGym /Java-blogg /Tilfeldig /Hvilke problemer løser adapterdesignmønsteret?
John Squirrels
Nivå
San Francisco

Hvilke problemer løser adapterdesignmønsteret?

Publisert i gruppen
Programvareutvikling blir vanskeligere av inkompatible komponenter som må fungere sammen. Hvis du for eksempel trenger å integrere et nytt bibliotek med en gammel plattform skrevet i tidligere versjoner av Java, kan du støte på inkompatible objekter, eller snarere inkompatible grensesnitt. Hvilke problemer løser adapterdesignmønsteret?  - 1Hva skal man gjøre i dette tilfellet? Vil du skrive om koden? Vi kan ikke gjøre det, fordi å analysere systemet vil ta mye tid, eller applikasjonens interne logikk vil bli krenket. For å løse dette problemet ble adaptermønsteret opprettet. Det hjelper objekter med inkompatible grensesnitt til å fungere sammen. La oss se hvordan du bruker det!

Mer om problemet

Først vil vi simulere oppførselen til det gamle systemet. Anta at det genererer unnskyldninger for å komme for sent til jobb eller skole. For å gjøre dette har den et Excusegrensesnitt som har generateExcuse(), likeExcuse()og dislikeExcuse()metoder.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Klassen WorkExcuseimplementerer dette grensesnittet:

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
   }
}
La oss teste vårt eksempel:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Produksjon:

"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.
Tenk deg nå at du har lansert en unnskyldningsgenererende tjeneste, samlet inn statistikk og lagt merke til at de fleste av brukerne dine er universitetsstudenter. For å tjene denne gruppen bedre, ba du en annen utvikler om å lage et system som genererer unnskyldninger spesielt for universitetsstudenter. Utviklingsteamet gjennomførte markedsundersøkelser, rangerte unnskyldninger, koblet til litt kunstig intelligens og integrerte tjenesten med trafikkmeldinger, værmeldinger og så videre. Nå har du et bibliotek for å generere unnskyldninger for universitetsstudenter, men det har et annet grensesnitt: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Dette grensesnittet har to metoder: generateExcuse, som genererer en unnskyldning, og dislikeExcuse, som forhindrer at unnskyldningen vises igjen i fremtiden. Tredjepartsbiblioteket kan ikke redigeres, dvs. du kan ikke endre kildekoden. Det vi har nå er et system med to klasser som implementerer grensesnittet Excuse, og et bibliotek med en SuperStudentExcuseklasse som implementerer StudentExcusegrensesnittet:

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 endres. Det nåværende klassehierarkiet ser slik ut: Hvilke problemer løser adapterdesignmønsteret?  - 2Denne versjonen av systemet fungerer kun med Excuse-grensesnittet. Du kan ikke skrive om koden: i en stor applikasjon kan det å gjøre slike endringer bli en langvarig prosess eller bryte applikasjonens logikk. Vi kan introdusere et basisgrensesnitt og utvide hierarkiet: Hvilke problemer løser adapterdesignmønsteret?  - 3For å gjøre dette, må vi endre navn på Excusegrensesnittet. Men det ekstra hierarkiet er uønsket i seriøse applikasjoner: å introdusere et felles rotelement bryter arkitekturen. Du bør implementere en mellomklasse som lar oss bruke både den nye og gamle funksjonaliteten med minimale tap. Kort sagt, du trenger en adapter .

Prinsippet bak adaptermønsteret

En adapter er et mellomobjekt som gjør at metodekallene til ett objekt kan forstås av et annet. La oss implementere en adapter for vårt eksempel og kalle det Middleware. Vår adapter må implementere et grensesnitt som er kompatibelt med ett av objektene. La det være Excuse. Dette gjør det mulig Middlewareå kalle det første objektets metoder. Middlewaremottar anrop og videresender dem på en kompatibel måte til det andre objektet. Her er implementeringen Middlewaremed generateExcuseog- dislikeExcusemetodene:

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
}
Testing (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
   }
}
Produksjon:

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 unnskyldning tilpasset gjeldende værforhold, trafikkorker eller forsinkelser i rutetider for offentlig transport. Metoden generateExcusesender ganske enkelt anropet til et annet objekt, uten ytterligere endringer. Metoden dislikeExcusekrevde at vi først svartelistet unnskyldningen. Evnen til å utføre mellomliggende databehandling er en grunn til at folk elsker adaptermønsteret. Men hva med likeExcusemetoden, som er en del av Excusegrensesnittet, men ikke en del av StudentExcusegrensesnittet? Den nye funksjonaliteten støtter ikke denne operasjonen. Den UnsupportedOperationExceptionble oppfunnet for denne situasjonen. Den kastes hvis den forespurte operasjonen ikke støttes. La oss bruke det. Slik Middlewareser klassens nye 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.
   }
}
Ved første øyekast virker ikke denne løsningen særlig god, men å imitere funksjonaliteten kan komplisere situasjonen. Hvis klienten tar hensyn, og adapteren er godt dokumentert, er en slik løsning akseptabel.

Når du skal bruke en adapter

  1. Når du trenger å bruke en tredjepartsklasse, men grensesnittet er inkompatibelt med hovedapplikasjonen. Eksemplet ovenfor viser hvordan du oppretter et adapterobjekt som bryter samtaler i et format som et målobjekt kan forstå.

  2. Når flere eksisterende underklasser trenger noen felles funksjonalitet. I stedet for å lage flere underklasser (som vil føre til duplisering av kode), er det bedre å bruke en adapter.

Fordeler og ulemper

Fordel: Adapteren skjuler detaljene for behandling av forespørsler fra ett objekt til et annet for klienten. Klientkoden tenker ikke på formatering av data eller håndtering av anrop til målmetoden. Det er for komplisert, og programmerere er late :) Ulempe: Prosjektets kodebase er komplisert av tilleggsklasser. Hvis du har mange inkompatible grensesnitt, kan antallet ekstra klasser bli uhåndterlig.

Ikke forveksle en adapter med en fasade eller dekoratør

Med bare en overfladisk inspeksjon kan en adapter forveksles med fasaden og dekorasjonsmønstrene. Forskjellen mellom en adapter og en fasade er at en fasade introduserer et nytt grensesnitt og omslutter hele delsystemet. Og en dekoratør, i motsetning til en adapter, endrer selve objektet i stedet for grensesnittet.

Trinn-for-trinn algoritme

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

  2. Definer klientgrensesnittet som skal brukes til indirekte å samhandle med inkompatible objekter.

  3. Få adapterklassen til å arve grensesnittet definert i forrige trinn.

  4. I adapterklassen oppretter du et felt for å lagre en referanse til det tilpassede objektet. Denne referansen sendes til konstruktøren.

  5. Implementer alle klientgrensesnittmetodene i adapteren. En metode kan:

    • Send videre anrop uten å gjøre noen endringer

    • Endre eller supplere data, øke/redusere antall anrop til målmetoden osv.

    • I ekstreme tilfeller, hvis en bestemt metode forblir inkompatibel, kast en UnsupportedOperationException. Ikke-støttede operasjoner må være strengt dokumentert.

  6. Hvis applikasjonen bare bruker adapterklassen gjennom klientgrensesnittet (som i eksempelet ovenfor), kan adapteren utvides smertefritt i fremtiden.

Selvfølgelig er dette designmønsteret ikke et universalmiddel for alle sykdommer, men det kan hjelpe deg med å elegant løse problemet med inkompatibilitet mellom objekter med forskjellige grensesnitt. En utvikler som kan de grunnleggende mønstrene er flere steg foran de som kun kan skrive algoritmer, fordi designmønstre kreves for å lage seriøse applikasjoner. Gjenbruk av kode er ikke så vanskelig, og vedlikehold blir hyggelig. Det var alt for i dag! Men vi vil snart fortsette å bli kjent med ulike designmønstre :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION