Padrão de design do adaptador em Python

Introdução

A Adaptador de padrão de design é um popular Padrão de projeto estrutural usado em engenharia de software. Este guia mostra como podemos implementar o Adapter Design Pattern em Python.

Padrões de design são soluções do tipo template – praticamente receitas para resolver problemas recorrentes e comuns no desenvolvimento de software. O Padrão Adaptador é baseado no conceito de um adaptador do mundo real! Por exemplo, o carregador de um laptop pode ter um plugue de 3 pinos na extremidade, mas a tomada de parede pode ser apenas de 2 pinos. Para conectar um carregador de 3 pinos neste soquete, precisaríamos de um adaptador que aceite um plugue de 3 pinos e adapta a interface no 2-pin tomada.

Um carregador de 2 pinos e um carregador de 3 pinos têm a mesma função básica (conduzem eletricidade da tomada para o laptop), mas uma forma diferente, e pode-se facilmente adaptar no outro. Sempre que você tiver componentes de software com a mesma função básica, mas formas diferentes, poderá aplicar o Adapter Design Pattern.

O Padrão Adaptador segue exatamente este princípio. Ele permite que duas interfaces incompatíveis trabalhem juntas sem modificar as partes internas de cada componente. Isto é conseguido adaptando uma interface, a outra, externamente.

Vejamos algumas terminologias básicas antes de nos aprofundarmos no mundo dos Padrões de Adaptadores:

  • Interface do cliente: Uma interface que especifica as funções que o cliente deve implementar.
  • Cliente: Uma classe que implementa a interface do cliente.
  • Adaptado/Serviço: a classe incompatível que precisa colaborar com a interface do cliente.
  • adaptador: A classe que possibilita a colaboração entre o serviço e o cliente.

Diferentes tipos de padrões de adaptador

O padrão de design do adaptador pode ser implementado de duas maneiras diferentes:

Adaptador de Objeto

Com esse método, a classe do adaptador implementa os métodos da interface do cliente. Assim, o objeto cliente e o objeto adaptador são compatíveis entre si. O objeto de serviço forma um has-a relacionamento com o objeto adaptador, ou seja, o objeto de serviço pertence ao objeto adaptador.

Sabemos que a classe de serviço não é compatível com o cliente. A classe do adaptador envolve o objeto de serviço instanciando-se com esse objeto. Agora, o objeto de serviço pode ser acessado por meio do objeto adaptador, permitindo que o cliente interaja com ele.

Podemos implementar o adaptador de objeto em todas as linguagens de programação modernas.

Adaptador de classe

Com este método, o adaptador tem um is-a relação com a classe de serviço. Nesse cenário, o adaptador implementa os métodos exigidos pelo cliente, mas herda de vários adaptadores, dando a ele a capacidade de chamar suas funções incompatíveis diretamente. A maior desvantagem dessa variação é que só podemos usá-la nas linguagens de programação que suportam herança múltipla de classes.

Implementação do Adapter Design Pattern em Python

Na seção abaixo, implementaremos o padrão de design Adapter em Python, usando especificamente o variação do adaptador de objeto. A seção está dividida em duas partes. Primeiro, vamos criar o ambiente onde o Adapter Pattern deve ser usado. É importante ver claramente como esse padrão pode resolver alguns problemas de software. A segunda seção usará um adaptador para resolver o problema.

Problema de incompatibilidade entre classes

Vejamos o problema de compatibilidade quando o cliente e a classe de serviço implementam funcionalidades diferentes. Crie uma classe de cliente com os seguintes métodos e salve-a em uma pasta como 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")

Aqui, criamos um Car classe com três métodos accelerate(), apply_brakes() e assign_driver(). Nós importamos o random módulo e usou-o para gerar números que definem a velocidade do carro depois de acelerar e aplicar os freios. o assign_driver() método exibe o nome do motorista do carro.

Em seguida, temos que criar uma classe de serviço ou adapte que deseja colaborar com a classe cliente Car. Crie uma classe Moto como esta e salve-a em sua pasta como 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")  

Uma classe de serviço, Motorcycle é criado acima com três métodos rev_throttle(), pull_brake_lever() e assign_rider(). Observe a diferença entre os métodos do serviço e da classe cliente, apesar de sua funcionalidade semelhante. o accelerator() método aumenta a velocidade do carro enquanto o rev_throttle() método aumenta a velocidade da motocicleta. Da mesma maneira, apply_brakes() e pull_brake_lever() aciona os freios nos respectivos veículos. finalmente, o assign_driver() e assign_rider() métodos atribuem o operador do veículo.

Em seguida, vamos criar uma classe para acessar esses diferentes métodos. Primeiro, adicione um __init.py__ na mesma pasta que você criou car.py e motorcycle.py:

touch __init__.py

Agora adicione o seguinte código em um novo arquivo 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()

Este script que cria nossos objetos cliente e serviço. Primeiro importamos o Car e Motorcycle classes e criar objetos com eles. Em seguida, invocamos os métodos do bike objeto (Motorcycle classe). Depois, invocamos os métodos do car objeto (Car classe). Quando executado, todo o código mencionado até agora funcionará.

No entanto, uma exceção é levantada quando tentamos invocar os métodos do Car aula com o bike objeto. Quando executamos este 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'

Neste caso, podemos modificar o Motorcycle classe ou o drive.py script para usar os métodos corretos. No entanto, em muitos casos, podemos não ter acesso ao código-fonte do cliente ou classe de serviço. Além disso, este é um exemplo simples. Com clientes e serviços maiores, pode não ser viável refatorar qualquer um deles no caso de quebrarmos a compatibilidade com outros sistemas.

Em vez disso, podemos usar um adaptador para preencher a lacuna de compatibilidade entre nosso código de cliente e nosso objeto de serviço.

Usando adaptadores para resolver o problema de incompatibilidade

Em um novo arquivo, motorcycle_adapter.py, adicione a seguinte 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)

Nós criamos um MotorcycleAdapter classe, que se instancia com um objeto de serviço (motorcycle). O adaptador implementa os métodos do cliente que são accelerate(), apply_brakes() e assign_driver(). Dentro do corpo do accelerate() método, usamos o motorcycle instância do objeto de serviço para chamar o rev_throttle() método de serviço. Da mesma forma, os outros métodos usam os métodos correspondentes do Motorcycle classe.

Agora vamos atualizar drive.py para que possamos usar o adaptador no try/except quadra:

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

Aqui,bike_adapter é um objeto do MotorcycleAdapter classe. Nós fornecemos o bike objetar ao MotorcycleAdapter construtor da classe. A execução deste script nos dá a seguinte saída:

Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!

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

Sem ter que ajustar o subjacente Motorcycle classe, podemos fazê-lo funcionar como um Car usando um adaptador!

Prós e contras do padrão de design do adaptador

As vantagens dos Padrões de Adaptadores são:

  • Podemos obter baixo acoplamento entre a classe do adaptador e a classe do cliente.
  • Podemos reutilizar a classe do adaptador para incorporar várias classes de serviço no aplicativo.
  • Podemos aumentar a flexibilidade do programa introduzindo vários adaptadores sem interferir no código do cliente

As desvantagens do Padrão Adaptador são:

  • A complexidade do programa aumenta com a adição de classe de adaptador e classe de serviço.
  • Há um aumento de sobrecarga no programa à medida que as solicitações são encaminhadas de uma classe para outra.
  • Adapter Pattern (adaptador de classe) usa heranças múltiplas, que todas as linguagens de programação podem não suportar.

Conclusão

Neste artigo, aprendemos sobre o padrão de design do adaptador, seus tipos e os problemas que eles resolvem. Implementamos o Adapter Pattern em Python para que possamos interagir com um Motorcycle objeto, como um Car objeto usando um adaptador para que a interface de cada classe não seja alterada.

Carimbo de hora:

Mais de Abuso de pilha