CodeGym /Blog Java /Aleatoriu /Ce probleme rezolvă modelul de design al adaptorului?
John Squirrels
Nivel
San Francisco

Ce probleme rezolvă modelul de design al adaptorului?

Publicat în grup
Dezvoltarea software-ului este îngreunată de componentele incompatibile care trebuie să funcționeze împreună. De exemplu, dacă trebuie să integrați o nouă bibliotecă cu o veche platformă scrisă în versiuni anterioare de Java, este posibil să întâlniți obiecte incompatibile, sau mai degrabă interfețe incompatibile. Ce probleme rezolvă modelul de design al adaptorului?  - 1Ce să faci în acest caz? Rescrie codul? Nu putem face asta, deoarece analiza sistemului va dura mult timp sau logica internă a aplicației va fi încălcată. Pentru a rezolva această problemă, a fost creat modelul adaptorului. Ajută obiectele cu interfețe incompatibile să lucreze împreună. Să vedem cum se folosește!

Mai multe despre problema

Mai întâi, vom simula comportamentul vechiului sistem. Să presupunem că generează scuze pentru a întârzia la serviciu sau la școală. Pentru a face acest lucru, are o Excuseinterfață care are generateExcuse()și metode. likeExcuse()dislikeExcuse()

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Clasa WorkExcuseimplementează această interfață:

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
   }
}
Să testăm exemplul nostru:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Ieșire:

"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.
Acum imaginați-vă că ați lansat un serviciu de generare de scuze, ați colectat statistici și ați observat că majoritatea utilizatorilor dvs. sunt studenți. Pentru a servi mai bine acest grup, ați cerut unui alt dezvoltator să creeze un sistem care să genereze scuze special pentru studenții universitari. Echipa de dezvoltare a efectuat cercetări de piață, a clasat scuzele, a conectat ceva inteligență artificială și a integrat serviciul cu rapoarte de trafic, rapoarte meteorologice și așa mai departe. Acum aveți o bibliotecă pentru generarea de scuze pentru studenții universitari, dar are o interfață diferită: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Această interfață are două metode: generateExcuse, care generează o scuză și dislikeExcuse, care împiedică apariția scuzei din nou în viitor. Biblioteca terță parte nu poate fi editată, adică nu îi puteți modifica codul sursă. Ceea ce avem acum este un sistem cu două clase care implementează interfața Excuseși o bibliotecă cu o SuperStudentExcuseclasă care implementează StudentExcuseinterfața:

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
   }
}
Codul nu poate fi schimbat. Ierarhia de clasă curentă arată astfel: Ce probleme rezolvă modelul de design al adaptorului?  - 2Această versiune a sistemului funcționează numai cu interfața Excuse. Nu puteți rescrie codul: într-o aplicație mare, efectuarea unor astfel de modificări poate deveni un proces îndelungat sau poate rupe logica aplicației. Am putea introduce o interfață de bază și am extinde ierarhia: Ce probleme rezolvă modelul de design al adaptorului?  - 3Pentru a face acest lucru, trebuie să redenumim Excuseinterfața. Dar ierarhia suplimentară este nedorită în aplicațiile serioase: introducerea unui element rădăcină comun rupe arhitectura. Ar trebui să implementați o clasă intermediară care ne va permite să folosim atât funcționalitatea nouă, cât și cea veche cu pierderi minime. Pe scurt, ai nevoie de un adaptor .

Principiul din spatele modelului adaptorului

Un adaptor este un obiect intermediar care permite apelurilor de metodă ale unui obiect să fie înțelese de către altul. Să implementăm un adaptor pentru exemplul nostru și să îl numim Middleware. Adaptorul nostru trebuie să implementeze o interfață compatibilă cu unul dintre obiecte. Lasă să fie Excuse. Aceasta permite Middlewareapelarea metodelor primului obiect. Middlewareprimește apeluri și le redirecționează într-un mod compatibil către al doilea obiect. Iată implementarea Middlewarecu metodele generateExcuseși dislikeExcuse:

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
}
Testare (în codul client):

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
   }
}
Ieșire:

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:
O scuză incredibilă adaptată condițiilor meteo actuale, blocajelor de trafic sau întârzierilor în orarul transportului public. Metoda generateExcusetrece pur și simplu apelul către alt obiect, fără modificări suplimentare. Metoda dislikeExcusene-a impus să punem mai întâi pe lista neagră scuza. Capacitatea de a efectua procesare intermediară a datelor este un motiv pentru care oamenii iubesc modelul adaptorului. Dar cum rămâne cu likeExcusemetoda, care face parte din Excuseinterfață, dar nu face parte din StudentExcuseinterfață? Noua funcționalitate nu acceptă această operațiune. A UnsupportedOperationExceptionfost inventat pentru această situație. Este aruncat dacă operația solicitată nu este suportată. Să-l folosim. Iată cum Middlewarearată noua implementare a clasei:

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.
   }
}
La prima vedere, această soluție nu pare foarte bună, dar imitarea funcționalității poate complica situația. Dacă clientul acordă atenție, iar adaptorul este bine documentat, o astfel de soluție este acceptabilă.

Când să folosiți un adaptor

  1. Când trebuie să utilizați o clasă terță parte, dar interfața sa este incompatibilă cu aplicația principală. Exemplul de mai sus arată cum să creați un obiect adaptor care include apeluri într-un format pe care un obiect țintă îl poate înțelege.

  2. Când mai multe subclase existente au nevoie de unele funcționalități comune. În loc să creați subclase suplimentare (care va duce la duplicarea codului), este mai bine să utilizați un adaptor.

Avantaje și dezavantaje

Avantaj: Adaptorul ascunde de la client detaliile procesării cererilor de la un obiect la altul. Codul clientului nu se gândește la formatarea datelor sau la gestionarea apelurilor către metoda țintă. Este prea complicat, iar programatorii sunt leneși :) Dezavantaj: baza de cod a proiectului este complicată de clase suplimentare. Dacă aveți o mulțime de interfețe incompatibile, numărul de clase suplimentare poate deveni de negestionat.

Nu confundați un adaptor cu o fațadă sau un decorator

Doar cu o inspecție superficială, un adaptor ar putea fi confundat cu modelele de fațadă și decor. Diferența dintre un adaptor și o fațadă este că o fațadă introduce o nouă interfață și înfășoară întregul subsistem. Și un decorator, spre deosebire de un adaptor, schimbă obiectul în sine mai degrabă decât interfața.

Algoritm pas cu pas

  1. În primul rând, asigurați-vă că aveți o problemă pe care acest model o poate rezolva.

  2. Definiți interfața client care va fi utilizată pentru a interacționa indirect cu obiecte incompatibile.

  3. Faceți ca clasa adaptorului să moștenească interfața definită în pasul anterior.

  4. În clasa adaptorului, creați un câmp pentru a stoca o referință la obiectul adaptat. Această referință este transmisă constructorului.

  5. Implementați toate metodele de interfață client în adaptor. O metodă poate:

    • Transmite apelurile fără a face modificări

    • Modificați sau completați datele, măriți/scădeți numărul de apeluri către metoda țintă etc.

    • În cazuri extreme, dacă o anumită metodă rămâne incompatibilă, aruncați o excepție UnsupportedOperationException. Operațiunile nesuportate trebuie să fie strict documentate.

  6. Dacă aplicația folosește doar clasa adaptorului prin interfața client (ca în exemplul de mai sus), atunci adaptorul poate fi extins fără durere în viitor.

Desigur, acest model de design nu este un panaceu pentru toate bolile, dar te poate ajuta să rezolvi elegant problema incompatibilității dintre obiectele cu interfețe diferite. Un dezvoltator care cunoaște modelele de bază este cu câțiva pași înaintea celor care știu doar să scrie algoritmi, deoarece modelele de design sunt necesare pentru a crea aplicații serioase. Reutilizarea codului nu este atât de dificilă, iar întreținerea devine plăcută. Asta e tot pentru azi! Dar în curând vom continua să cunoaștem diverse modele de design :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION