Alex Martelli's Design Patterns in Python (bilingual)
English and Portuguese slides of Alex Matelli's introduction to Design Patterns in Python. Translated by Luciano Ramalho with written permission from Alex Martelli.
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
programadores do código cliente e também do sub-sistema (complexidade + rigidez) • 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”
flexibilidade, os clientes ganham simplicidade • interpor um objeto/classe 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 Django: classe do domínio versus um monte de classes de modelo, para facilitar a api exposta às views
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 • tem que ser estudado no contexto de uma linguagem! • tem que fornecer Usos Conhecidos (UC / KU) Façade é uma exceção PP não são inventados, são descobertos por arqueólogos
• 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)
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)
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
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++
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!
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
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
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!
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.
uma classe • 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 “Queremos que apenas uma instância possa existir” Resolve 90% dos casos
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 class Bar(Foo): pass f = Foo(); b = Bar(); # ...???... problema intrínseco do Singleton
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
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 Subcategoria “Disfarçe/Adaptação:
σ 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 σ)
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
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, ...)
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
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!
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
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.
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 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 de organização e que chamam os seus métodos.
quando fazem sentido, mas “obrigatórias” servem como boa documentação 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 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!
“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”
(talvez descendente) em tempo de execução • descobrir que métodos existem • despachar apropriadamente (incluindo “catch-all” e/ou outros tratamento de erros)