Adapterdesignmønster i Python

Introduktion

Adapterdesignmønster er en populær Strukturelt designmønster bruges i software engineering. Denne guide ser på, hvordan vi kan implementere Adapter Design Pattern i Python.

Designmønstre er skabelonlignende løsninger – praktisk talt opskrifter på at løse tilbagevendende, almindelige problemer i softwareudvikling. Adaptermønsteret er baseret på konceptet om en adapter i den virkelige verden! For eksempel kan en laptops oplader have et 3-benet stik i enden, men stikkontakten må kun være en 2-benet stikkontakt. For at tilslutte en 3-benet oplader til denne stikkontakt, har vi brug for en adapter, der accepterer et 3-benet stik, og tilpasser grænsefladen ind i 2-bens fatning.

En 2-benet oplader og en 3-benet oplader har samme grundlæggende funktion (leder strøm fra stikkontakten til den bærbare computer), men har en anden form, og man kan sagtens tilpasse ind i den anden. Når du har softwarekomponenter med den samme grundlæggende funktion, men forskellige former, kan du anvende Adapter Design Pattern.

Adaptermønsteret følger nøjagtigt dette princip. Det gør det muligt for to inkompatible grænseflader at arbejde sammen uden at ændre det indre af hver komponent. Dette opnås ved at tilpasse en grænseflade til en anden eksternt.

Lad os se på nogle grundlæggende terminologier, før vi dykker dybere ned i adaptermønstrenes verden:

  • Klientgrænseflade: En grænseflade, der specificerer de funktioner, som klienten skal implementere.
  • Klient: En klasse, der implementerer klientgrænsefladen.
  • Adaptee/Service: Den inkompatible klasse, der skal samarbejde med klientgrænsefladen.
  • adapter: Klassen, der gør samarbejdet mellem tjenesten og klienten muligt.

Forskellige typer adaptermønstre

Adapterdesignmønsteret kan implementeres på to forskellige måder:

Objektadapter

Med denne metode implementerer adapterklassen metoderne fra klientgrænsefladen. Således er klientobjektet og adapterobjektet kompatible med hinanden. Serviceobjektet danner en has-a forhold til adapterobjektet dvs. serviceobjektet tilhører adapterobjektet.

Vi ved, at serviceklassen ikke er kompatibel med kunden. Adapterklassen omslutter serviceobjektet ved at instansiere sig selv med det objekt. Nu kan serviceobjektet tilgås via adapterobjektet, hvilket giver klienten mulighed for at interagere med det.

Vi kan implementere objektadapteren i alle de moderne programmeringssprog.

Klasse adapter

Med denne metode har adapteren en is-a forhold til serviceklassen. I dette scenarie implementerer adapteren de metoder, der kræves af klienten, men den arver fra flere tilpassede, hvilket giver den mulighed for at kalde deres inkompatible funktioner direkte. Den største ulempe ved denne variation er, at vi kun kan bruge den i de programmeringssprog, der understøtter multipel nedarvning af klasser.

Implementering af Adapter Design Pattern i Python

I afsnittet nedenfor vil vi implementere Adapter-designmønsteret i Python, specifikt ved hjælp af objektadapter variation. Afsnittet er opdelt i to dele. Først vil vi skabe det miljø, hvor adaptermønsteret skal bruges. Det er vigtigt at se klart, hvordan dette mønster kan løse nogle softwareproblemer. Den anden sektion vil bruge en adapter til at løse problemet.

Inkompatibilitetsproblem mellem klasser

Lad os se på kompatibilitetsproblemet, når klienten og serviceklassen implementerer forskellige funktionaliteter. Opret en klientklasse med følgende metoder og gem den i en mappe 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")

Her har vi lavet en Car klasse med tre metoder accelerate(), apply_brakes() , assign_driver(). Vi importerede random modul og brugte det til at generere tal, der indstiller bilens hastighed efter acceleration og brug af bremserne. Det assign_driver() metoden viser bilistens navn.

Dernæst skal vi oprette en service- eller tilpasset klasse, der ønsker at samarbejde med klientklassen Car. Opret en motorcykelklasse som denne og gem den i din mappe 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 serviceklasse, Motorcycle er oprettet ovenfor med tre metoder rev_throttle(), pull_brake_lever()og assign_rider(). Læg mærke til forskellen mellem metoderne for tjenesten og klientklassen på trods af deres lignende funktionalitet. Det accelerator() metode øger bilens hastighed, mens den rev_throttle() metode øger motorcyklens hastighed. Ligeledes, apply_brakes() , pull_brake_lever() bremser i de respektive køretøjer. Endelig assign_driver() , assign_rider() metoder tildele køretøjets operatør.

Lad os derefter oprette en klasse for at få adgang til disse forskellige metoder. Tilføj først en __init.py__ i den samme mappe, som du oprettede car.py , motorcycle.py:

touch __init__.py

Tilføj nu følgende kode 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()

Dette script, der opretter vores klient- og serviceobjekter. Vi importerer først Car , Motorcycle klasser og lav objekter med dem. Så påberåber vi os metoderne fra bike objekt (Motorcycle klasse). Derefter påberåber vi os metoderne til car objekt (Car klasse). Når den udføres, vil al den hidtil nævnte kode virke.

Der rejses dog en undtagelse, når vi forsøger at påberåbe os metoderne i Car klasse med bike objekt. Når vi kører dette script:

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 dette tilfælde kan vi ændre Motorcycle klasse eller drive.py script for at bruge de rigtige metoder. Men i mange tilfælde har vi muligvis ikke adgang til kildekoden for klienten eller serviceklassen. Dette er også et simpelt eksempel. Med større kunder og tjenester er det muligvis ikke muligt at refaktorere nogen af ​​dem, hvis vi bryder kompatibiliteten med andre systemer.

I stedet kan vi bruge en adapter til at bygge bro over kompatibilitetskløften mellem vores klientkode og vores serviceobjekt.

Brug af adaptere til at løse inkompatibilitetsproblemet

I en ny fil, motorcycle_adapter.py, tilføj følgende klasse:

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 har oprettet en MotorcycleAdapter klasse, som instansierer sig selv med et serviceobjekt (motorcycle). Adapteren implementerer klientmetoderne, som er accelerate(), apply_brakes() , assign_driver(). Inde i kroppen af accelerate() metode, har vi brugt motorcycle forekomst af serviceobjektet for at kalde rev_throttle() servicemetode. Ligeledes bruger de andre metoder de tilsvarende metoder fra Motorcycle klasse.

Lad os nu opdatere drive.py så vi kan bruge adapteren i try/except blok:

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()

Her,bike_adapter er et objekt af MotorcycleAdapter klasse. Vi leverede bike gøre indsigelse mod MotorcycleAdapter klasses konstruktør. Udførelse af dette script giver os følgende output:

Tjek vores praktiske, praktiske guide til at lære Git, med bedste praksis, brancheaccepterede standarder og inkluderet snydeark. Stop med at google Git-kommandoer og faktisk lærer 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

Uden at skulle justere det underliggende Motorcycle klasse, kan vi få det til at fungere som en Car ved hjælp af en adapter!

Fordele og ulemper ved adapterdesignmønster

Fordelene ved adaptermønstre er:

  • Vi kan opnå lav kobling mellem adapterklassen og klientklassen.
  • Vi kan genbruge adapterklassen til at inkorporere adskillige serviceklasser i applikationen.
  • Vi kan øge programmets fleksibilitet ved at introducere flere adaptere uden at forstyrre klientkoden

Ulemperne ved Adapter Pattern er:

  • Programmets kompleksitet øges med tilføjelsen af ​​adapterklasse og serviceklasse.
  • Der er en stigning i overhead i programmet, da anmodningerne videresendes fra en klasse til en anden.
  • Adapter Pattern (klasse adapter) bruger flere nedarvninger, som alle programmeringssprogene muligvis ikke understøtter.

Konklusion

I denne artikel lærte vi om adapterdesignmønsteret, dets typer og de problemer, de løser. Vi implementerede Adapter Pattern i Python, så vi kan interagere med en Motorcycle genstand, som en Car objekt ved at bruge en adapter, så grænsefladen for hver klasse ikke ændres.

Tidsstempel:

Mere fra Stablemisbrug