Alex Martelli's 45' talk about Design Patterns translated to Brazilian Portuguese under permission from the author.
PT-BR:
A palestra de 45 min sobre Padrões de Projeto do Alex Martelli traduzida para o português do Brasil, com a permissão do autor.
1. Alex Martelli’s
Design Patterns
in Python
Apresentada originalmente no Google Developer’s Day 2007
Vídeo no YouTube: http://bit.ly/okP7hh
Traduzida para a PythonBrasil 7 por Luciano Ramalho com
autorização de Alex Martelli gentilmente cedida em 23/08/2011
2.
3. Alex Martelli and Anna Martelli Ravenscroft
EuroPython, Firenze, June 2011
photo by Josette Garcia
4. O melhor livro sobre Python já escrito
(mesmo sendo de 2006)
5.
6.
7. Saímos cantando os pneus...
• Forças: um sub-sistema
rico e complexo oferece
muita funcionalidade útil;
componentes clientes
interagem com várias
partes dessa funcionali-
dade, de um jeito que
está “fora de controle”
• isto causa muitos problemas para programadores do
código cliente e também do sub-sistema
(complexidade + rigidez)
8.
9. Solução: o padrão “Façade”
• interpor um objeto/classe
Django: classe do domínio
versus um monte de classes
de modelo, para facilitar a
api exposta às views
de “fachada”, expondo um
sub-conjunto controlado
da funcionalidade
• clientes acessam
somente a fachada
• a fachada implementa a
funcionalidade simples
invocando o sub-sistema complexo
• a implementação do sub-sistema ganha
flexibilidade, os clientes ganham simplicidade
10.
11. Façade é um padrão de projeto
• resumo de um problema comum de projeto +
estrutura de uma solução (+ vantagens e desvantagens,
alternativas, ...) e:
• um nome (mais fácil de lembrar/discutir)
• “descrições de objetos e classes que se comunicam,
customizados para resolver um problema geral de
projeto em um contexto específico” (GOF)
• não é: estrutura de dados, algoritmo, arquitetura
específica de domínio, recurso de uma linguagem ou
de uma biblioteca Façade é uma exceção
• tem que ser estudado no contexto de uma linguagem!
• tem que fornecer Usos Conhecidos (UC / KU) PP não são inventados, são
descobertos por arqueólogos
12.
13. Alguns UC de Façade
• ...na biblioteca padrão de Python...
• dbhash é uma fachada para o bsddb
• acesso muito simplificado a uma parte da API
• também atende à interface dbm (portanto, é
também um exemplo do PP Adapter)
• os.path: basename, dirname são fachada para
split + acesso a item; isdir (etc.) fachada para
os.stat + stat.S_ISDIR (etc.)
• Façade é um PP estrutural (veremos outro, Adapter,
depois; no exemplo do dbhash eles se fundem)
14.
15.
16. O que é um padrão de projeto
• resumo de um problema comum de projeto +
estrutura de uma solução (+ vantagens e desvantagens,
alternativas, ...) e:
• um nome (mais fácil de lembrar/discutir)
• “descrições de objetos e classes que se comunicam
customizadas para resolver um problema geral de
projeto em um contexto específico” (GOF)
• um PP não é: estrutura de dados, algoritmo,
arquitetura específica de domínio, recurso de uma
linguagem ou de uma biblioteca
• tem que ser estudado no contexto de uma linguagem!
• tem que fornecer Usos Conhecidos (UC / KU)
19. Categorias clássicas de PP
• Criacionais: formas e modos de instanciar
objetos
• Estruturais: composição mútua de classes
ou objetos (o Façade é um PP estrutural)
• Comportamental: como classes e
objetos interagem e dividem
responsabilidades entre si
• Cada um pode ser a nível de classe ou a
nível de objeto
20.
21. Princípios básicos de PP
• “programe para uma interface, não
para uma implementação” (GOF)
• isto é feito principalmente através
de “tipagem pato” em Python –
raramente com interfaces “formais”
• semelhante a “polimorfismo
baseado em assinatura” nas
templates de C++
22.
23.
24. Princípios básicos de PP
• “prefira composição de objetos do que herança
de classes” (GOF)
• em Python, segurar ou embrulhar
• herdar apenas quando é realmente
conveniente
• expor todos os métodos da super classe
(reusar + normalmente sobrescrever +
talvez estender)
• mas é um acoplamento muito forte!
25.
26.
27. Python: segurar ou embrulhar?
• “Segurar”: objeto O tem um sub-objeto
S como um atributo (talvez uma
propriedade) – só isso
• usa-se self.S.method ou O.S.method
• simples, direto, imediato,
porém...
acoplamento bem forte,
frequentemente no eixo
errado
28.
29. Python: segurar ou embrulhar?
• “Embrulhar”: segurar (frequentemente com
um nome privado) e delegar (de modo que se
possa acessar diretamente O.metodo)
• delegação explícita
(def metodo(self...): self.S.metodo)
• automática (via __getattr__)
• acoplamento correto
(Lei de Deméter) Evitar expressões do tipo
a.b.c. porque isso limita a
a expor um b que tem um
30.
31. Ex.: embrulhar para “restringir”
class EmbrulhoRestritor(object):
def __init__(self, embrulhado, bloqueios):
self._embrulhado = embrulhado
self._bloqueios = bloqueios
def __getattr__(self, nome):
if nome in self._bloqueios:
raise AttributeError, nome
return getattr(self._embrulhado, nome)
...
Herança não tem o poder de restringir!
34. “Factory já
vem pronto”
• Em linguagens que tem o
operador new, a chamada
new Foo() implica que
Foo é uma classe concreta
específica. Isso aumenta o
acoplamento.
• Python é mais flexível,
porque Foo() pode ser a
invocação de uma classe
ou de uma função factory.
O cliente não precisa
saber.
35.
36. Padrões criacionais [1]
“Queremos que apenas uma instância possa existir”
• use um módulo em vez de uma classe Resolve 90% dos casos
• não pode ter subclasses, métodos especiais...
• crie somente uma instância (sem restrição)
• necessário definir exatamente quando criar
• singleton (“Highlander”)
• criar sub-classes não é tão tranquilo
• monoestado (“Borg”)
• Guido não curte
37.
38. Singleton (“Highlander”)
class Singleton(object):
def __new__(cls, *a, **k):
if not hasattr(cls, '_inst'):
cls._inst = super(Singleton,
cls).__new__(cls, *a, **k)
return cls._inst
Subclasses são problemáticas, porém:
class Foo(Singleton): pass problema
class Bar(Foo): pass intrínseco
f = Foo(); b = Bar(); # ...???... do Singleton
39.
40. Monoestado (“Borg”)
class Borg(object):
_shared_state = {}
def __new__(cls, *a, **k):
obj = super(Borg, cls
).__new__(cls, *a, **k)
obj.__dict__ = cls._shared_state
return obj
Subclasses sem problemas, basta fazer:
class Foo(Borg): pass
class Bar(Foo): pass
class Baz(Foo): sobrecarga de
_shared_state = {} dados resolve!
41.
42. Padrões criacionais [2]
• “não queremos ser forçados a instanciar uma
classe concreta específica”
• injeção de dependência
• nada se cria “dentro”, tudo vem “de fora”
• e se múltiplas criações são necessárias?
• subcategoria “Factory” de PPs
• pode criar qq. coisa ou reusar o q. já existe
• funções factory (e outros invocáveis)
• métodos factory (podem ser sobrescritos)
• classes factory abstratas
43.
44. Padrões Estruturais
Subcategoria “Disfarçe/Adaptação:
• Adapter: ajustar uma interface (existem variantes
de classe e de objetos)
• Facade: simplificar a interface de um sub-sistema
• ... e muitos outros que não vou abordar, como:
• Brige: muitas implementações de uma
abstração, muitas implementações de uma
funcionalidade, sem codificação repetitiva
• Decorator: reusar+ajustar sem herança
• Proxy: desacoplar acesso/localização
45.
46. Adapter
• código-cliente γ exige um protocolo C
• cógido-fornecedor σ oferece um protocolo diferente
S (com um superconjunto da funcionalide de C)
• código-adaptador α “se infiltra entre eles”:
• para γ, α é um fornecedor (produz o protocolo C)
• para σ, α é um cliente (consome o protocolo S)
• “dentro”, α implementa C (por meio de chamadas
apropriadas a S em σ)
47.
48. Exemplo simples de Adapter
• C requer o método foobar(foo, bar)
• S fornece o método barfoo(bar, foo)
• por exemplo, σ poderia ser:
class Barfooer(object):
def barfoo(self, bar, foo):
...
49.
50. Adapter de objeto
• por instância, com delegação por embrulho:
class EmbrulhadorFoobar(object):
def __init__(self, embrulhado):
self.e = embrulhado
def foobar(self, foo, bar):
return self.e.barfoo(bar, foo)
foobarador = EmbrulhadorFoobar(barfooador)
51.
52. Adapter de classe (direto)
• por classe, com derivação e auto-delegação:
class Foobarador(Barfooador):
def foobar(self, foo, bar):
return self.barfoo(bar, foo)
foobarador = Foobarador(...qq. coisa...)
53.
54. Adapter de classe (mixin)
• flexível, bom uso de herança múltipla:
class BF2FB:
def foobar(self, foo, bar):
return self.barfoo(bar, foo)
class Foobarador(BF2FB, Barfooador):
pass
foobarador = Foobarador(...qq. coisa...)
55.
56. Adapter: Usos Conhecidos
• socket._fileobject: de sockets para objetos “file-
like” (com muito código para gerenciar o buffer)
• doctest.DocTestSuite: adapta testes doctest para
unittest.TestSuite
• dbhash: adapta bsddb para dbm
• StringIO: adapta str ou unicode para “file-like”
• shelve: adapta “dict limitado” (chaves e valores str,
métodos básicos) para um dicionário completo
• via pickle para qualquer coisa <-> string
• + UserDict.DictMixin
57.
58. Observações sobre Adapter
• alguns adapters reais podem demandar muito código
• classes mixin são uma ótima maneira de auxiliar na
adaptação de protocolos ricos (implementam
métodos avançados a partir de métodos
fundamentais)
• Adapter é encontrado em todos os níveis de
complexidade
• Em Python, _não_ se trata sempre de classes e suas
instâncias (de modo algum!) -- muitas vezes
_invocáveis_ são adaptados (via decorators e outras
funções de ordem superior, closures, functools, ...)
59.
60. Facade vs Adapter
• Adapter trata de fornecer um determinado
protocolo exigido por código-cliente
• ou, conquistar polimorfismo via
homogeneidade
• Facade trata de simplificar uma interface
rica quando apenas um sub-conjunto é
necessário
• Facade frequentemente mascara um
subsistema formado por muitas classes/
objetos, Adapter mascara apenas um
objeto ou classe
61.
62. Padrões comportamentais
• Template Method: self-delegation (auto-delegação)
• ... “a essência da POO”
• algumas de suas variantes específicas em Python
63.
64. Template Method
• padrão excelente, nome péssimo
• “template” é um termo muito sobrecarregado
• programação genérica em C++
• geração de documento a partir de esqueleto
• ...
• um nome melhor: self-delegation
• auto-delegação
• diretamente descritivo!
65.
66. TM Clássico
• classe-base abstrata (ABC) oferece “método
organizador” que invoca “métodos gancho”
• na ABC, métodos-gancho são abstratos
• sub-classes concretas implementam os ganchos
• código-cliente invoca o método organizador
• em alguma referência à ABC (injetor, ou...)
• que obviamente refere-se a uma sub-classe
concreta
67.
68. Esqueleto de TM
class AbstractBase(object):
def orgMethod(self):
self.doThis()
self.doThat()
class Concrete(AbstractBase):
def doThis(self):
...
def doThat(self):
...
69.
70. UC: cmd.Cmd.cmdloop
def cmdloop(self):
self.preloop() while True:
s = self.doinput()
s = self.precmd(s)
finis = self.docmd(s)
finis = self.postcmd(finis,s)
if finis: break
self.postloop()
Na implementação em Python, os
métodos-gancho são
implementados com pass na classe
abstrata, porque Python é uma
linguagem pragática e não
ideológica.
71.
72. Justificativa p/ TM Clássico
Diferença entre um framework
e uma bibiblioteca: uma
biblioteca é um conjunto de
funções que vc chama, a
organização fica por sua conta.
Um framework tem os métodos
• o “método organizador” fornece “lógica
de organização e que chamam os
seus métodos.
estrutural” (sequenciamento etc.)
• os “métodos gancho” realizam de fato as “ações
elementares”
• é frequentemente apropriado para fatorar
comportamento comuns e variações
• deixa claras as responsabilidades e colaborações
entre objetos (classes): classe base invoca
ganchos, sub-classes os implementam
• aplica o “Hollywood Principle”: “don't call us, we'll
call you” - não ligue para nós, nós te ligamos
73.
74. Uma escolha de ganchos
class TheBase(object):
def doThis(self):
# provide a default (often a no-op)
pass
def doThat(self):
# or, force subclass to implement
# (might also just be missing...)
raise NotImplementedError
• Implementações default são mais práticas,
quando fazem sentido, mas “obrigatórias”
servem como boa documentação
78. TM em Queue
• Não abstrata, frequentemente usada “no estado”,
implementa todos os métodos ganchos
• sub-classes podem customizar o comportamento
• sem se preocupar com travas, timing...
• comportamento default é FIFO, simples e útil
• with no worry about locking, timing, ...
• pode-se sobrescrever métodos gancho (__init__,
qsize, _empty, _full, _put, _get) E...
• ...dados (maxsize, queue), uma exclusividade
Python!
82. Fatorando os ganchos
• “método organizador” em uma classe
• “métodos gancho” em outra classe
• UC: HTML formatter vs writer
• UC: SAX parser vs handler
• adiciona um eixo de variabilidade/flexibilidade
• aproxima-se do PP Strategy:
• Strategy: uma classe abstrata por ponto de
decisão, e classes concretas indepententes
• TM fatorado: classes abstratas e concretas
mais “agrupadas”
83.
84. TM + introspection
• classe “organizadora” pode examinar classe
“gancho” (talvez descendente) em tempo
de execução
• descobrir que métodos existem
• despachar apropriadamente (incluindo
“catch-all” e/ou outros tratamento de
erros)