Como usar Adapter Pattern com python

Olá! Dando continuidade ao post anterior, sobre Decorator Pattern. Mostrarei neste post aqui um exemplo de como e porquê usar o Adapter Pattern. Qualquer dúvida, comente!

O que é Adapter Pattern?

Bom, imaginemos aqueles cartões de memória de máquina fotográfica. Existem de várias tamanhos, micro, pequenos, grandes, gigantes. Seria realmente uma merda se uma máquina fotográfica suportasse somente 1 tamanho, por isso foi inventada aqueles... adivinhem... adaptadores que permitem que micros e pequenos caibam em grande, por exemplo. Todos tem a mesma finalidade, porém, têm tamanhos diferentes.Para isso serve o Adapter Pattern, para resolver justamente esse tipo de problema.

Quando se fala em abstração, geralmente se fala em Adapter Pattern, justamente por isso, porque ele te permite, com um mesmo objeto, fazer ações diferentes com uma finalidade em comum.

Para entendermos melhor, pensemos no caso de abstração de banco de dados. A sintaxe de cada um é diferente, porém têm as mesmas claúsulas. Alguns permitem transações, outros não. Pois esta é uma situação perfeita para o Adapter Pattern. Mas seria muito complexo explicar utilizando ela como exemplo, logo, fiz um exemplo em python que vai ajudar.

class ShapeAbstract(object):

	def countCorner(self):
		return self.corners

	def __str__(self):
		return self.name

	def area(self): abstract

class SquareShape(ShapeAbstract):
	def __init__(self, x, y=None):
		self.corners = 4
		self.name = 'Square'
		if (x is not None):
			self.l = x

	def area(self):
		return self.l*self.l

class TriangleShape(ShapeAbstract):
	def __init__(self, x, y):
		self.corners = 3
		self.name = 'Triangle'
		self.x = x
		self.y = y

	def area(self):
		return ((self.x*self.y)/2)

]

Bom, Tudo começa na nossa classe abstrata ShapeAbstract. Ela é a classe que vai servir de base para todos as formas que vamos calcular a área. Temos a classe SquareShape que representa um quadrado (jura? :/). O construtor da classe, __init__, recebe 2 valores: altura e a largura. Visto que é um quadrado, todos os lados são iguais, não é necessário passar a altura, ficamos somente com a largura. O que difere do triângulo, cujas medidas são base e altura. A nomenclatura não importa, no caso, vamos utilizar no máximo 2 medidas.

Nosso adapter é simplório, caso houvesse a necessidade de calcular a área de um trapésio, seria necessário mais um parâmetro, afinal, a fórmula é: ((base maior + base menor) / 2 * altura). Isso é facilmente implantado utilizando apenas mais um parâmetro no __init__, porque o que realmente queremos é fazer com que o método area() nos retorne a área.

import Shapes

class Shape(object):

	def setAdapter(self, Adapter):
		self.Adapter = Adapter

	def countCorner(self):
		return self.Adapter.countCorner()

	def area(self):
		return self.Adapter.area()

	def __str__(self):
		return self.Adapter.__str__();

	def newShape(ShapeAdapter):
		shape = Shape()
		shape.setAdapter(ShapeAdapter)
		return shape

	newShape = staticmethod(newShape)

if __name__ == '__main__':
	shape = Shape.newShape(Shapes.SquareShape(5))
	print shape
	print shape.countCorner()
	print shape.area()
        #Imprime:
        #Square
        #4
        #25

	shape = Shape.newShape(Shapes.TriangleShape(5,6))
	print shape
	print shape.countCorner()
	print shape.area()
        #Imprime:
        #Triangle
        #3
        #15

Nesta "aplicação principal" vemos a real classe adaptativa: Shape. Ela recebe a instância da classe e executa os métodos dela. Parece bobo ter uma classe que executa o método de outra classe, mas é fundamental para o reuso e a famosa abstração.

Vejam que elas se comportam diferentes. Claro, são formas diferentes! Áreas diferentes! Número de cantos diferentes! E nomes direfentes!

Agora fica fácil ver esse exemplo abstraindo, por exemplo, um banco de dados? Pense que a classe Shape seja Database, TriangleShape seja MySQLDatabase e SquareShape seja MSSQLDatabase, o método area() seja o método setLimit().

shape = Database(MySQLDatabase())
print shape.setLimit(5);
#Imprimi: LIMIT 5
shape = Database(MSSQLDatabase())
print shape.setLimit(5);
#Imprimi: TOP 5

Usando Adapter para 3rd part

Uma das minhas sugestões de uso do Adapter Pattern é com integração de código de terceiros. Vamos supor que você tenha 3 bibliotecas que geram gráficos: X, Y e Z. A bilioteca X é muito boa, mas não tem gráficos em pizza, que na Y tem. Mas a Z, por sua vez, tem gráficos em pizza em 3d. Vamos analizar a situação:

  • A responsabilidade é a mesma: gerar um gráfico.
  • Independente do tipo de gráfico (com barras, pizza, etc), todas elas vão receber os mesmos tipos de dados.
  • Cada biblioteca tem seus próprios métodos, com nomes diferentes, mas para as mesma finalidade

Não parece um lugar perfeito para se utilizar o Adapter Pattern? Ora, vamos fazer um pequeno esboço:

graph = GraphAdapter()
graph.setData(([20,30,10],[15,25,35]))
graph.desenhaGrafico(GRAFICOS.BARRA)
graph.desenhaGrafico(GRAFICOS.PIZZA)
graph.desenhaGrafico(GRAFICOS.PIZZA3D)

Observem que não utilizei a instância de uma classe como adapter, utilizei constantes que vão determinar, na hora de desenhar o gráfico, o tipo dele. Se for BARRA, faz justamente como a biblioteca X exige, usando a API dela. O mesmo para PIZZA (Y) e PIZZA3D (Z). Pronto! Você, utilizando patterns, terá seu próprio modo de gerar gráficos, mesmo que com bibliotecas diferentes! Comentem e até a próxima!

Comentários

"Por favor, se você deseja opinar, criticar ou até mesmo mandar uma receita de bolo, deixe um comentário! Ou fique atento aos feeds."

---- Bolo de fubá ----
# 1 colher (sobremesa) de sementes de erva-doce
# 1 colher (sobremesa) de fermento em pó
# 1 xícara (chá) de óleo
# 1 xícara (chá) de leite
# 1 xícara (chá) de farinha de trigo
# 2 xícaras (chá) de fubá
# 2 xícaras (chá) de açúcar

Coloque no liquidificador os ovos, o açúcar, o fubá, a farinha de trigo, o leite e o óleo. Bata até obter uma mistura homogênea. Junte o fermento e as sementes de erva-doce e misture sem bater. Unte e enfarinhe uma fôrma com furo no meio e despeje a massa. Leve ao forno preaquecido em temperatura média (200ºC) até dourar, ou até que enfiando um palito no bolo ele saia seco. Desenforme o bolo ainda morno e, se preferir, salpique açúcar e canela antes servir.

rá!

Anotado =P