Skip to content

Instantly share code, notes, and snippets.

@alexandre
Last active August 29, 2015 14:26
Show Gist options
  • Save alexandre/5bed42f22a639559fe14 to your computer and use it in GitHub Desktop.
Save alexandre/5bed42f22a639559fe14 to your computer and use it in GitHub Desktop.
misturando strategy e herança.
"""
Vamos supor que temos um numero X de clientes e cada cliente tem 2 tipos de
fechamento diferentes: fechamento parcial e fechamento geral.
Além disso, cada cliente tem um padrão para esses arquivos (cada fechamento gera um), já que esses
arquivos serão utilizados em um sistema de terceiros.
A principio, eu pensei que apenas o pattern strategy solucionava o problema...
de acordo com o cliente eu devolvia uma classe especifica que geraria o
arquivo. Mas o problema é que eu esqueci que haviam 2 tipos de fechamento.
Com isso, eu pensei em utilizar herança. Por que? porque o processo básico
para chamar os arquivos de fechamento (indiferente do tipo) é o mesmo.
No final ficaria algo como:
>>> import exporters
>>> customer = Customer()
>>> exporters.ExporterA(customer=customer.name)
>>> exporter.generate_file()
>>> # o mesmo para o segundo tipo
>>> exporters.ExporterB(customer=customer.name)
>>> exporter.generate_file()
Obs.: Supondo que o meu entendimento do pattern strategy está correto. =]
"""
import collections
class Exporter():
@property
def default_exporter(self):
raise NotImplementedError('Should have implemented this')
@property
def available_exporters(self):
raise NotImplementedError('Should have implemented this')
def __new__(cls, *args, **kwargs):
if 'customer' not in kwargs.keys():
raise RuntimeError('You have to inform a customer name')
exporter_type = super(Exporter, cls).__new__(cls)
exporter = collections.defaultdict(
lambda: exporter_type.default_exporter, exporter_type.available_exporters)
return exporter[kwargs['customer']](*args, **kwargs)
class ExporterA(Exporter):
@property
def default_exporter(self):
return default_exporter_a
@property
def available_exporters(self):
return {
'Customer_foo': exporter_a_customer_foo,
'Customer_bar': exporter_a_customer_bar,
'Customer_ble': exporter_a_customer_ble,
}
class ExporterB(Exporter):
@property
def default_exporter(self):
return default_exporter_b
@property
def available_exporters(self):
return {
'Customer_foo': exporter_b_customer_foo,
'Customer_bar': exporter_b_customer_bar,
'Customer_ble': exporter_b_customer_ble,
}
@drgarcia1986
Copy link

Brother, acredito que tenha um gap de conceito ou talvez eu não tenha entendido corretamente o problema.
É legal no strategy primeiro identificar os participantes (vou explicar mais ou menos como tenho na cabeça):

  • Context: É o objeto que irá encapsular a estratégia. O contexto só conhece a interface da estratégia. O contexto deve possibilitar a troca da estratégia tanto no momento da criação de sua instancia como em tempo de execução
  • AbstractStrategy: É apensar a API (ou interface) da estratégia (como em python usamos duck typing, não precisamos criar essa interface).
  • ConcretStrategy: Aqui é onde fica a implementação concreta das estratégias seguindo a interface determinada na AbstractStrategy.

Vou tentar fazer uma exemplo com o seu problema, vou imaginar que esse fechamento possa ser chamado de Closer e o que varia (as estratégias) são os exportadores, ou Exporter.

(vou digitar direto aqui no comentário, algo pode estar errado e fora do pep8 😄 )

class Closer:  # <- esse é o context

    def __init__(self, exporter):
        self.exporter = exporter

    def close(self):
        self.exporter.export(self)
class ExporterConsole: # <- essa é uma estratégia concreta

    def export(self, closer):
        print(repr(closer))

class ExporterFile: # <- essa aqui tmb

    def export(self, closer):
        with open('foo.txt', 'w') as f:
            f.write(repr(closer))
>>> closer = Closer(ExporterConsole())
>>> closer.close()
>>> closer = Closer(ExporterFile())
>>> closer.close()

Notw que para o usuário da classe Closer é indiferente qual estratégia está ativa no momento? a api não muda por causa disso.

Um detalhe é que no python, nesse exemplo simples poderia substituir as classes de estratégia concreta por simples funções (já que não estou guardando estado na classe, sendo assim, usar classes nesse caso seria desnecessário)

Sobre o processo básico de chamar os arquivos de fechamento, acredito que isso seja uma tarefa para o context (já que é independente das estratégias)

Ajudei ?

Copy link

ghost commented Jul 30, 2015

seguindo a ideia do Diego e o comentário sobre "trocar a estratégia em tempo de execução", essa é uma das grandes vantagens do Strategy.

closer.exporter = ExporterFile()
closer.close()
closer.exporter = ExporterConsole()
closer.close()

o seu objeto "closer" ainda é o mesmo, não há necessidade de instancia-lo novamente para aplicar outra estratégia.

@alexandre
Copy link
Author

Diego,

Deve existir um gap mesmo. =]
Mas a minha dúvida e também o motivo de usar herança é que além de ter o exporter diferente (A e B), cada cliente tem uma variação do A e uma variação do B. (available_exporters)

Eu poderia usar apenas 1 função com um dict para recuperar a variação(e.g. recuperar a variação do exporter A para o cliente foo) e passar isso para o exporter (da mesma forma que vc explicou a estratégia concreta). Faz sentido?

@alexandre
Copy link
Author

Eu acho que agora eu consegui entender melhor... eu não preciso de uma classe para cada tipo de exporter mesmo. Eu vou separar essa parte de recuperar a "variação" e simplesmente passar ela para o "closer".

@drgarcia1986
Copy link

Isso ai :)
Talvez suas estratégias sejam as "variações".

@alexandre
Copy link
Author

@drgarcia1986 e @daniloakamine-luizalabs

Apenas um rascunho mental enquanto eu não paro para ler...

# module exporters.py
import collections

EXPORTERS_A = collections.defaultdict(
     lambda: default_a_exporter,
     {
         'Customer_foo': exporter_a_customer_foo,
         'Customer_bar': exporter_a_customer_bar,
         'Customer_ble': exporter_a_customer_ble,
     }
)

EXPORTERS_B = collections.defaultdict(
     lambda: default_b_exporter,
     {
         'Customer_foo': exporter_b_customer_foo,
         'Customer_bar': exporter_b_customer_bar,
         'Customer_ble': exporter_b_customer_ble,
     }
)

class Closer:
    def __init__(self, exporter):
        self.exporter = exporter

    def generate_file(self, *args, **kwargs):
         return self.exporter.generate_file(*args, **kwargs)
customer = Customer()

# mesmo processo para o EXPORTERS_A e o EXPORTERS_B

exporter = exporters.EXPORTER_A[customer.name]()
closer = exporters.Closer(exporter)
closer.generate_file('xyz') # ... segue a vida

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment