Adapterdesignmönster i Python

Beskrivning

Smakämnen Adapterdesignmönster är en populär Strukturellt designmönster används inom mjukvaruteknik. Den här guiden tittar på hur vi kan implementera Adapter Design Pattern i Python.

Design mönster är mallliknande lösningar – praktiskt taget recept för att lösa återkommande, vanliga problem inom mjukvaruutveckling. Adaptermönstret är baserat på konceptet med en verklig adapter! Till exempel kan laddaren för en bärbar dator ha en 3-stiftskontakt i änden, men vägguttaget kan bara vara ett 2-stiftsuttag. För att koppla in en 3-stifts laddare till det här uttaget skulle vi behöva en adapter som accepterar en 3-stiftskontakt och anpassar sig gränssnittet till 2-pin uttag.

En 2-stifts laddare och en 3-stifts laddare har samma grundläggande funktion (leda el från uttaget till den bärbara datorn), men har en annan form, och man kan lätt anpassa in i den andra. Närhelst du har programvarukomponenter med samma grundläggande funktion men olika former, kan du använda adapterdesignmönstret.

Adaptermönstret följer exakt denna princip. Det tillåter två inkompatibla gränssnitt att arbeta tillsammans utan att modifiera de interna funktionerna i varje komponent. Detta uppnås genom att anpassa ett gränssnitt till ett annat externt.

Låt oss titta på lite grundläggande terminologi innan vi dyker djupare in i världen av adaptermönster:

  • Klientgränssnitt: Ett gränssnitt som specificerar de funktioner som klienten ska implementera.
  • Klient: En klass som implementerar klientgränssnittet.
  • Adaptee/Service: Den inkompatibla klassen som behöver samarbeta med klientgränssnittet.
  • adapter: Klassen som möjliggör samarbetet mellan tjänsten och uppdragsgivaren.

Olika typer av adaptermönster

Adapterns designmönster kan implementeras på två olika sätt:

Objektadapter

Med denna metod implementerar adapterklassen metoderna från klientgränssnittet. Således är klientobjektet och adapterobjektet kompatibla med varandra. Tjänsteobjektet bildar en has-a relation med adapterobjektet dvs tjänstobjektet tillhör adapterobjektet.

Vi vet att serviceklassen inte är kompatibel med kunden. Adapterklassen omsluter serviceobjektet genom att instansiera sig själv med det objektet. Nu kan serviceobjektet nås via adapterobjektet, vilket gör att klienten kan interagera med det.

Vi kan implementera objektadaptern i alla moderna programmeringsspråk.

Klassadapter

Med denna metod har adaptern en is-a förhållande till tjänsteklassen. I det här scenariot implementerar adaptern de metoder som krävs av klienten, men den ärver från flera adapterade, vilket ger den möjlighet att anropa sina inkompatibla funktioner direkt. Den största nackdelen med denna variant är att vi bara kan använda den i de programmeringsspråk som stöder multipla nedärvning av klasser.

Implementering av Adapter Design Pattern i Python

I avsnittet nedan kommer vi att implementera adapterns designmönster i Python, speciellt med hjälp av objektadaptervariation. Avsnittet är uppdelat i två delar. Först kommer vi att skapa miljön där adaptermönstret ska användas. Det är viktigt att se tydligt hur detta mönster kan lösa vissa programvaruproblem. Det andra avsnittet kommer att använda en adapter för att lösa problemet.

Inkompatibilitetsproblem mellan klasser

Låt oss titta på kompatibilitetsproblemet när klienten och tjänsteklassen implementerar olika funktioner. Skapa en klientklass med följande metoder och spara den i en mapp som car.py:

import random

class Car:
    def __init__(self):
        self.generator = random.Random()

    def accelerate(self):
        random_num = self.generator.randint(50, 100)
        speed = random_num
        print(f"The speed of the car is {speed} mph")

    def apply_brakes(self):
        random_num = self.generator.randint(20, 40)
        speed = random_num
        print(f"The speed of the car is {speed} mph after applying the brakes")

    def assign_driver(self, driver_name):
        print(f"{driver_name} is driving the car")

Här har vi skapat en Car klass med tre metoder accelerate(), apply_brakes() och assign_driver(). Vi importerade random modulen och använde den för att generera siffror som anger bilens hastighet efter att ha accelererat och använt bromsarna. De assign_driver() metoden visar bilförarens namn.

Därefter måste vi skapa en tjänst eller anpassad klass som vill samarbeta med klientklassen Car. Skapa en motorcykelklass som denna och spara den i din mapp som motorcycle.py:

import random

class Motorcycle:
    def __init__(self):
        self.generator = random.Random()

    def rev_throttle(self):
        random_num = self.generator.randint(50, 100)
        speed = random_num
        print(f"The speed of the motorcycle is {speed} mph")

    def pull_brake_lever(self):
        random_num = self.generator.randint(20, 40)
        speed = random_num
        print(
            f"The speed of the motorcycle is {speed} mph after applying the brakes")

    def assign_rider(self, rider_name):
        print(f"{rider_name} is riding the motorcycle")  

En serviceklass, Motorcycle skapas ovan med tre metoder rev_throttle(), pull_brake_lever()och assign_rider(). Lägg märke till skillnaden mellan metoderna för tjänsten och klientklassen trots deras liknande funktionalitet. De accelerator() metoden ökar hastigheten på bilen medan rev_throttle() metoden ökar motorcykelns hastighet. Likaså, apply_brakes() och pull_brake_lever() bromsar i respektive fordon. Slutligen, den assign_driver() och assign_rider() metoder tilldela fordonsoperatören.

Låt oss sedan skapa en klass för att komma åt dessa olika metoder. Lägg först till en __init.py__ i samma mapp som du skapade car.py och motorcycle.py:

touch __init__.py

Lägg nu till följande kod i en ny fil drive.py:

from car import Car
from motorcycle import Motorcycle
import traceback

if __name__ == '__main__':
    car = Car()
    bike = Motorcycle()

    print("The Motorcyclen")
    bike.assign_rider("Subodh")
    bike.rev_throttle()
    bike.pull_brake_lever()
    print("n")

    print("The Carn")
    car.assign_driver("Sushant")
    car.accelerate()
    car.apply_brakes()
    print("n")

    print("Attempting to call client methods with the service objectn")

    try:
        bike.assign_driver("Robert")
        bike.accelerate()
        bike.apply_brakes()
    except AttributeError:
        print("Oops! bike object cannot access car methods")
        traceback.print_exc()

Detta skript som skapar våra klient- och tjänsteobjekt. Vi importerar först Car och Motorcycle klasser och skapa objekt med dem. Sedan åberopar vi metoderna från bike objekt (Motorcycle klass). Därefter åberopar vi metoderna för car objekt (Car klass). När den körs kommer all kod som nämnts hittills att fungera.

Ett undantag tas dock upp när vi försöker åberopa metoderna för Car klass med bike objekt. När vi kör det här skriptet:

The Motorcycle

Subodh is riding the motorcycle
The speed of the motorcycle is 91 mph
The speed of the motorcycle is 37 mph after applying the brakes


The Car

Sushant is driving the car
The speed of the car is 59 mph
The speed of the car is 33 mph after applying the brakes


Attempting to call client methods with the service object

Oops! bike object cannot access car methods
Traceback (most recent call last):
  File "drive.py", line 24, in 
    bike.assign_driver("Robert")
AttributeError: 'Motorcycle' object has no attribute 'assign_driver'

I det här fallet kan vi ändra Motorcycle klass eller drive.py skript för att använda rätt metoder. Men i många fall kanske vi inte har tillgång till källkoden för klienten eller tjänsteklassen. Detta är också ett enkelt exempel. Med större kunder och tjänster kanske det inte är möjligt att refaktorera någon av dem om vi bryter kompatibiliteten med andra system.

Istället kan vi använda en adapter för att överbrygga kompatibilitetsgapet mellan vår klientkod och vårt serviceobjekt.

Använda adaptrar för att lösa inkompatibilitetsproblemet

I en ny fil, motorcycle_adapter.py, lägg till följande klass:

class MotorcycleAdapter:

    def __init__(self, motorcycle):
        self.motorcycle = motorcycle

    def accelerate(self):
        self.motorcycle.rev_throttle()

    def apply_brakes(self):
        self.motorcycle.pull_brake_lever()

    def assign_driver(self, name):
        self.motorcycle.assign_rider(name)

Vi skapade en MotorcycleAdapter klass, som instansierar sig själv med ett serviceobjekt (motorcycle). Adaptern implementerar klientmetoderna som är accelerate(), apply_brakes() och assign_driver(). Inuti kroppen av accelerate() metoden har vi använt motorcycle instans av tjänstobjektet att anropa rev_throttle() servicemetod. På samma sätt använder de andra metoderna motsvarande metoder för Motorcycle klass.

Nu ska vi uppdatera drive.py så vi kan använda adaptern i try/except blockera:

from car import Car
from motorcycle import Motorcycle
from motorcycle_adapter import MotorcycleAdapter 
import traceback

if __name__ == '__main__':
    car = Car()
    bike = Motorcycle()
    bike_adapter = MotorcycleAdapter(bike) 

    ...

    try:
        print("Attempting to call client methods with the service object using an adaptern")
        bike_adapter.assign_driver("Robert")
        bike_adapter.accelerate()
        bike_adapter.apply_brakes()
    except AttributeError:
        print("Oops! bike object cannot access car methods")
        traceback.print_exc()

Här,bike_adapter är ett objekt för MotorcycleAdapter klass. Vi levererade bike invända mot MotorcycleAdapter klassens konstruktör. Att köra det här skriptet ger oss följande utdata:

Kolla in vår praktiska, praktiska guide för att lära dig Git, med bästa praxis, branschaccepterade standarder och medföljande fuskblad. Sluta googla Git-kommandon och faktiskt lära Det!

The Motorcycle

Subodh is riding the motorcycle
The speed of the motorcycle is 88 mph
The speed of the motorcycle is 35 mph after applying the brakes


The Car

Sushant is driving the car
The speed of the car is 91 mph
The speed of the car is 24 mph after applying the brakes


Attempting to call client methods with the service object

Attempting to call client methods with the service object using an adapter

Robert is riding the motorcyle
The speed of the motorcycle is 67 mph
The speed of the motorcycle is 25 mph after applying the brakes

Utan att behöva justera det underliggande Motorcycle klass, kan vi få det att fungera som en Car med en adapter!

För- och nackdelar med adapterdesignmönster

Fördelarna med Adapter Patterns är:

  • Vi kan uppnå låg koppling mellan adapterklassen och klientklassen.
  • Vi kan återanvända adapterklassen för att införliva många serviceklasser i applikationen.
  • Vi kan öka programmets flexibilitet genom att introducera flera adaptrar utan att störa klientkoden

Nackdelarna med Adapter Pattern är:

  • Programmets komplexitet ökar med tillägg av adapterklass och serviceklass.
  • Det finns en ökning av overhead i programmet eftersom förfrågningarna vidarebefordras från en klass till en annan.
  • Adapter Pattern (klassadapter) använder flera arv, som alla programmeringsspråk kanske inte stöder.

Slutsats

I den här artikeln lärde vi oss om adapterns designmönster, dess typer och problemen de löser. Vi implementerade Adapter Pattern i Python så att vi kan interagera med en Motorcycle föremål, som en Car objekt genom att använda en adapter så att gränssnittet för varje klass inte ändras.

Tidsstämpel:

Mer från Stackabuse