Modello di progettazione dell'adattatore in Python

Introduzione

I Modello di progettazione dell'adattatore è un popolare Modello di progettazione strutturale utilizzato nell'ingegneria del software. Questa guida esamina come possiamo implementare l'Adapter Design Pattern in Python.

Modelli di progettazione sono soluzioni simili a modelli, praticamente ricette per risolvere problemi ricorrenti e comuni nello sviluppo del software. L'Adattatore Pattern si basa sul concetto di adattatore del mondo reale! Ad esempio, il caricabatterie di un laptop potrebbe avere una spina a 3 pin all'estremità, ma la presa a muro potrebbe essere solo una presa a 2 pin. Per collegare un caricabatterie a 3 pin a questa presa, avremmo bisogno di un adattatore che accetti una spina a 3 pin e adatta l'interfaccia nel 2-pin zoccolo.

Hanno un caricabatterie a 2 pin e un caricabatterie a 3 pin la stessa funzione di base (condurre l'elettricità dalla presa al laptop), ma hanno una forma diversa, e si può facilmente adattare nell'altro. Ogni volta che si hanno componenti software con la stessa funzione di base ma forme diverse, è possibile applicare l'Adapter Design Pattern.

Il modello adattatore segue questo esatto principio. Consente a due interfacce incompatibili di lavorare insieme senza modificare gli interni di ciascun componente. Ciò si ottiene adattando un'interfaccia, a un'altra, esternamente.

Diamo un'occhiata alla terminologia di base prima di immergerci più a fondo nel mondo degli Adapter Patterns:

  • Interfaccia client: un'interfaccia che specifica le funzioni che il client deve implementare.
  • .: una classe che implementa l'interfaccia client.
  • Adattato/servizio: la classe incompatibile che deve collaborare con l'interfaccia client.
  • adattatore: La classe che rende possibile la collaborazione tra il servizio e il cliente.

Diversi tipi di modelli di adattatori

Il modello di progettazione dell'adattatore può essere implementato in due modi diversi:

Adattatore oggetto

Con questo metodo, la classe adapter implementa i metodi dall'interfaccia client. Pertanto, l'oggetto client e l'oggetto adattatore sono compatibili tra loro. L'oggetto del servizio forma a has-a relazione con l'oggetto adattatore, ovvero l'oggetto servizio appartiene all'oggetto adattatore.

Sappiamo che la classe di servizio non è compatibile con il client. La classe dell'adattatore avvolge l'oggetto di servizio creando un'istanza con quell'oggetto. Ora è possibile accedere all'oggetto servizio tramite l'oggetto adattatore, consentendo al client di interagire con esso.

Possiamo implementare l'adattatore oggetti in tutti i moderni linguaggi di programmazione.

Adattatore di classe

Con questo metodo, l'adattatore ha un is-a rapporto con la classe di servizio. In questo scenario, l'adattatore implementa i metodi richiesti dal client, ma eredita da più adattatori, dandogli la possibilità di chiamare direttamente le loro funzioni incompatibili. Il più grande svantaggio di questa variazione è che possiamo usarla solo nei linguaggi di programmazione che supportano l'ereditarietà multipla delle classi.

Implementazione dell'Adapter Design Pattern in Python

Nella sezione seguente, implementeremo il modello di progettazione dell'adattatore in Python, in particolare utilizzando il variazione dell'adattatore oggetto. La sezione è divisa in due parti. In primo luogo, creeremo l'ambiente in cui dovrebbe essere utilizzato il modello adattatore. È importante vedere chiaramente come questo modello può risolvere alcuni problemi software. La seconda sezione utilizzerà un adattatore per risolvere il problema.

Problema di incompatibilità tra classi

Esaminiamo il problema di compatibilità quando il client e la classe di servizio implementano funzionalità diverse. Crea una classe client con i seguenti metodi e salvala in una cartella come 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")

Qui abbiamo creato un Car classe con tre metodi accelerate(), apply_brakes() ed assign_driver(). Abbiamo importato il random modulo e lo ha utilizzato per generare numeri che impostano la velocità dell'auto dopo l'accelerazione e l'applicazione dei freni. Il assign_driver() il metodo visualizza il nome del conducente dell'auto.

Successivamente, dobbiamo creare una classe di servizio o adattatore che desideri collaborare con la classe client Car. Crea una classe di moto come questa e salvala nella tua cartella come 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")  

Una classe di servizio, Motorcycle viene creato sopra con tre metodi rev_throttle(), pull_brake_lever()e assign_rider(). Notare la differenza tra i metodi del servizio e della classe client nonostante la loro funzionalità simile. Il accelerator() il metodo aumenta la velocità dell'auto mentre il rev_throttle() il metodo aumenta la velocità della moto. Allo stesso modo, apply_brakes() ed pull_brake_lever() aziona i freni nei rispettivi veicoli. Infine, il assign_driver() ed assign_rider() metodi assegnano l'operatore del veicolo.

Quindi, creiamo una classe per accedere a questi diversi metodi. Innanzitutto, aggiungi un __init.py__ nella stessa cartella che hai creato car.py ed motorcycle.py:

touch __init__.py

Ora aggiungi il seguente codice in un nuovo file 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()

Questo script che crea i nostri oggetti client e servizi. Per prima cosa importiamo il Car ed Motorcycle classi e creare oggetti con esse. Quindi invochiamo i metodi da bike oggetto (Motorcycle classe). Dopo, invochiamo i metodi di car oggetto (Car classe). Una volta eseguito, tutto il codice menzionato finora funzionerà.

Tuttavia, viene sollevata un'eccezione quando si tenta di invocare i metodi di Car classe con il bike oggetto. Quando eseguiamo questo 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'

In questo caso, possiamo modificare il Motorcycle classe o il drive.py script per utilizzare i metodi corretti. Tuttavia, in molti casi, potremmo non avere accesso al codice sorgente del client o della classe di servizio. Inoltre, questo è un semplice esempio. Con client e servizi più grandi, potrebbe non essere possibile eseguire il refactoring di nessuno dei due nel caso in cui si rompa la compatibilità con altri sistemi.

Invece, possiamo utilizzare un adattatore per colmare il divario di compatibilità tra il nostro codice client e il nostro oggetto di servizio.

Utilizzo di adattatori per risolvere il problema di incompatibilità

In un nuovo file, motorcycle_adapter.py, aggiungi la seguente classe:

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)

Abbiamo creato una MotorcycleAdapter class, che istanzia se stessa con un oggetto di servizio (motorcycle). L'adattatore implementa i metodi client che sono accelerate(), apply_brakes() ed assign_driver(). All'interno del corpo del accelerate() metodo, abbiamo usato il motorcycle istanza dell'oggetto servizio per chiamare il rev_throttle() metodo di servizio. Allo stesso modo, gli altri metodi utilizzano i metodi corrispondenti di Motorcycle classe.

Ora, aggiorniamo drive.py quindi possiamo usare l'adattatore nel try/except bloccare:

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

Qui,bike_adapter è un oggetto del MotorcycleAdapter classe. Abbiamo fornito il bike opporsi alla MotorcycleAdapter costruttore di classe. L'esecuzione di questo script fornisce il seguente output:

Dai un'occhiata alla nostra guida pratica e pratica per l'apprendimento di Git, con le migliori pratiche, gli standard accettati dal settore e il cheat sheet incluso. Smetti di cercare su Google i comandi Git e in realtà imparare esso!

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

Senza dover aggiustare il sottostante Motorcycle classe, possiamo farlo funzionare come a Car usando un adattatore!

Pro e contro del modello di progettazione dell'adattatore

I vantaggi dei modelli di adattatore sono:

  • Possiamo ottenere un basso accoppiamento tra la classe dell'adattatore e la classe del client.
  • Possiamo riutilizzare la classe dell'adattatore per incorporare numerose classi di servizio nell'applicazione.
  • Possiamo aumentare la flessibilità del programma introducendo più adattatori senza interferire con il codice client

Gli svantaggi di Adapter Pattern sono:

  • La complessità del programma aumenta con l'aggiunta della classe dell'adattatore e della classe di servizio.
  • Si verifica un aumento del sovraccarico nel programma poiché le richieste vengono inoltrate da una classe all'altra.
  • Adapter Pattern (adattatore di classe) utilizza eredità multiple, che tutti i linguaggi di programmazione potrebbero non supportare.

Conclusione

In questo articolo, abbiamo appreso del modello di progettazione dell'adattatore, dei suoi tipi e dei problemi che risolvono. Abbiamo implementato l'Adapter Pattern in Python in modo da poter interagire con a Motorcycle oggetto, come a Car oggetto utilizzando un adattatore in modo che l'interfaccia di ogni classe non cambi.

Timestamp:

Di più da Impilamento