CodeGym /Java Blog /Willekeurig /Welke problemen lost het ontwerppatroon van de adapter op...
John Squirrels
Niveau 41
San Francisco

Welke problemen lost het ontwerppatroon van de adapter op?

Gepubliceerd in de groep Willekeurig
Softwareontwikkeling wordt bemoeilijkt door incompatibele componenten die moeten samenwerken. Als u bijvoorbeeld een nieuwe bibliotheek moet integreren met een oud platform dat in eerdere versies van Java is geschreven, kunt u incompatibele objecten tegenkomen, of eigenlijk incompatibele interfaces. Welke problemen lost het ontwerppatroon van de adapter op?  - 1Wat te doen in dit geval? De code herschrijven? Dat kunnen we niet doen, omdat het analyseren van het systeem veel tijd kost of de interne logica van de applicatie wordt geschonden. Om dit probleem op te lossen, is het adapterpatroon gemaakt. Het helpt objecten met incompatibele interfaces om samen te werken. Laten we eens kijken hoe het te gebruiken!

Meer over het probleem

Eerst simuleren we het gedrag van het oude systeem. Stel dat het excuses genereert om te laat op het werk of op school te komen. Om dit te doen, heeft het een Excuseinterface met generateExcuse(), likeExcuse()en dislikeExcuse()methoden.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
De WorkExcuseklasse implementeert deze interface:

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
   }
}
Laten we ons voorbeeld testen:

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

"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.
Stel je nu voor dat je een dienst voor het genereren van excuses hebt gelanceerd, statistieken hebt verzameld en merkt dat de meeste van je gebruikers universiteitsstudenten zijn. Om deze groep beter van dienst te zijn, heb je een andere ontwikkelaar gevraagd een systeem te maken dat excuses genereert, specifiek voor universiteitsstudenten. Het ontwikkelingsteam voerde marktonderzoek uit, rangschikte excuses, koppelde wat kunstmatige intelligentie aan en integreerde de dienst met verkeersrapporten, weerberichten, enzovoort. Nu heb je een bibliotheek voor het genereren van excuses voor universiteitsstudenten, maar deze heeft een andere interface: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Deze interface heeft twee methoden: generateExcuse, waarmee een excuus wordt gegenereerd, en dislikeExcuse, waarmee wordt voorkomen dat het excuus in de toekomst opnieuw verschijnt. De bibliotheek van derden kan niet worden bewerkt, dwz u kunt de broncode niet wijzigen. Wat we nu hebben is een systeem met twee klassen die de Excuseinterface implementeren, en een bibliotheek met een klasse die de interface SuperStudentExcuseimplementeert :StudentExcuse

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
   }
}
De code kan niet worden gewijzigd. De huidige klassenhiërarchie ziet er als volgt uit: Welke problemen lost het ontwerppatroon van de adapter op?  - 2Deze versie van het systeem werkt alleen met de Excuse-interface. U kunt de code niet herschrijven: in een grote toepassing kan het aanbrengen van dergelijke wijzigingen een langdurig proces worden of de logica van de toepassing doorbreken. We kunnen een basisinterface introduceren en de hiërarchie uitbreiden: Welke problemen lost het ontwerppatroon van de adapter op?  - 3hiervoor moeten we de Excuseinterface hernoemen. Maar de extra hiërarchie is ongewenst bij serieuze toepassingen: het introduceren van een gemeenschappelijk root-element breekt de architectuur. U moet een tussenklasse implementeren waarmee we zowel de nieuwe als de oude functionaliteit kunnen gebruiken met minimale verliezen. Kortom, je hebt een adapter nodig .

Het principe achter het adapterpatroon

Een adapter is een tussenliggend object waarmee de methodeaanroepen van het ene object door een ander kunnen worden begrepen. Laten we een adapter voor ons voorbeeld implementeren en deze noemen Middleware. Onze adapter moet een interface implementeren die compatibel is met een van de objecten. Laat het zo zijn Excuse. Dit maakt het mogelijk Middlewareom de methoden van het eerste object aan te roepen. Middlewareontvangt oproepen en stuurt ze op een compatibele manier door naar het tweede object. Dit is de Middlewareimplementatie met de methoden generateExcuseen 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
}
Testen (in klantcode):

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
   }
}
Uitgang:

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:
Een ongelooflijk excuus aangepast aan de huidige weersomstandigheden, files of vertragingen in de dienstregeling van het openbaar vervoer. De generateExcusemethode geeft de aanroep gewoon door aan een ander object, zonder aanvullende wijzigingen. De dislikeExcusemethode vereiste dat we eerst het excuus op de zwarte lijst zetten. De mogelijkheid om tussentijdse gegevensverwerking uit te voeren is een reden waarom mensen dol zijn op het adapterpatroon. Maar hoe zit het met de likeExcusemethode, die deel uitmaakt van de Excuseinterface maar geen deel uitmaakt van de StudentExcuseinterface? De nieuwe functionaliteit ondersteunt deze bewerking niet. De UnsupportedOperationExceptionis uitgevonden voor deze situatie. Het wordt gegenereerd als de gevraagde bewerking niet wordt ondersteund. Laten we het gebruiken. Zo Middlewareziet de nieuwe implementatie van de klas eruit:

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.
   }
}
Op het eerste gezicht lijkt deze oplossing niet erg goed, maar het imiteren van de functionaliteit kan de situatie bemoeilijken. Als de klant oplet en de adapter goed gedocumenteerd is, is een dergelijke oplossing acceptabel.

Wanneer een adapter gebruiken?

  1. Wanneer u een klasse van derden moet gebruiken, maar de interface niet compatibel is met de hoofdtoepassing. Het bovenstaande voorbeeld laat zien hoe u een adapterobject maakt dat oproepen verpakt in een formaat dat een doelobject kan begrijpen.

  2. Wanneer verschillende bestaande subklassen een gemeenschappelijke functionaliteit nodig hebben. In plaats van extra subklassen te maken (wat zal leiden tot duplicatie van code), is het beter om een ​​adapter te gebruiken.

Voor-en nadelen

Voordeel: de adapter verbergt voor de client de details van de verwerkingsverzoeken van het ene object naar het andere. De clientcode denkt niet na over het formatteren van gegevens of het afhandelen van aanroepen naar de doelmethode. Het is te gecompliceerd en programmeurs zijn lui :) Nadeel: de codebasis van het project wordt gecompliceerd door extra klassen. Als u veel incompatibele interfaces heeft, kan het aantal extra klassen onhandelbaar worden.

Verwar een adapter niet met een gevel of decorateur

Met slechts een oppervlakkige inspectie zou een adapter kunnen worden verward met de gevel- en decoratiepatronen. Het verschil tussen een adapter en een gevel is dat een gevel een nieuwe interface introduceert en het hele subsysteem omhult. En een decorateur verandert, in tegenstelling tot een adapter, het object zelf in plaats van de interface.

Stap voor stap algoritme

  1. Zorg er eerst voor dat je een probleem hebt dat dit patroon kan oplossen.

  2. Definieer de clientinterface die zal worden gebruikt voor indirecte interactie met incompatibele objecten.

  3. Laat de adapterklasse de interface overnemen die in de vorige stap is gedefinieerd.

  4. Maak in de adapterklasse een veld om een ​​verwijzing naar het adaptee-object op te slaan. Deze verwijzing wordt doorgegeven aan de constructor.

  5. Implementeer alle clientinterfacemethoden in de adapter. Een methode kan:

    • Gesprekken doorgeven zonder wijzigingen aan te brengen

    • Wijzig of vul gegevens aan, verhoog/verlaag het aantal oproepen naar de doelmethode, etc.

    • In extreme gevallen, als een bepaalde methode onverenigbaar blijft, genereert u een UnsupportedOperationException. Niet-ondersteunde bewerkingen moeten strikt worden gedocumenteerd.

  6. Als de applicatie alleen de adapterklasse gebruikt via de clientinterface (zoals in het bovenstaande voorbeeld), kan de adapter in de toekomst probleemloos worden uitgebreid.

Dit ontwerppatroon is natuurlijk geen wondermiddel voor alle kwalen, maar het kan u helpen om op een elegante manier het probleem van incompatibiliteit tussen objecten met verschillende interfaces op te lossen. Een ontwikkelaar die de basispatronen kent, is een aantal stappen voor op degenen die alleen weten hoe ze algoritmen moeten schrijven, omdat ontwerppatronen nodig zijn om serieuze applicaties te maken. Hergebruik van code is niet zo moeilijk en onderhoud wordt leuk. Dat is alles voor vandaag! Maar we zullen binnenkort verschillende ontwerppatronen blijven leren kennen :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION