sexta-feira, setembro 26, 2008

Python e MVC - Parte 2

Continuando o post anterior Python e MVC - Parte 1.

Durante o desenvolvimento de uma aplicação aqui na empresa estive decidido a utilizar um framework MVC para Python que além de me prover as ferramentas necessárias para resolver os meus dilemas de design como manutenção facilitada, separação da aplicação em partes lógicas e criação de uma estrutura de componentes este também deveria ser genérico o suficiente para permitir a troca da view na minha aplicação de maneira simples e fácil.

O problema é que hoje existem basicamente três frameworks MVC para Python: Kiwi, pygtkmvc e PureMVC. O Kiwi é um framework muito bom e desenvolvido para uma aplicação real o Stoq, o pygtkmvc apesar de ter idéias interessantes parece ser um projeto abandonado e o PureMVC por sua vez é um projeto iniciado em ActionScript que mais tarde foi portado para diversas linguagens entre elas Python.

O Kiwi e o pygtkmvc são altamente acoplados com a view no caso PyGtk, o que vai totalmente contra o princípio de fraco acoplamento entre as partes como diz o MVC, e isso IMHO é muito importante e também é um requisito da minha aplicação. Sendo assim, sobra apenas nosso amigo PureMVC.

Eu também poderia construir meu próprio framework MVC, de acordo com minhas necessidades e me baseando nestes últimos três exemplos, reunindo assim o melhor de cada um. Sem dúvida, essa seria a melhor alternativa, mas como sempre o tempo era curto e essa não era uma possibilidade. Sem falar no esforço enorme de análise e um conhecimento muito profundo de design patterns necessário para se criar algo genérico o suficiente.

O PureMVC possui alguns problemas, IMHO. O primeiro problema é seu estilo ou filosofia e sua nomenclatura que são extremamente diferentes do que estamos acostumados em Python. A implementação então nem se fala, obviamente foi feita por um programador Java ou algo do tipo, embora funcione.

Algumas coisas não seguem uma linha de simplicidade e facilidade no uso, tornando coisas simples um pouco complexas demais. Como exemplo, o código abaixo foi retirado do demo do projeto que utiliza wxPython:


class ProductListMediator(Mediator, IMediator):
NAME = "ProductListMediator"
product_proxy = None
def __init__(self, view_component):
self.view_component = view_component
super(ProductListMediator, self).__init__(self.NAME, view_component)


O código acima pode ser simplicado com uma meta class como essa:


class MetaMediator(type):
def __init__(cls, name, bases, namespace):
cls.NAME = name


class BaseMediator(Mediator, IMediator):
__metaclass__ = MetaMediator
def __init__(self, view_component=None, constant_list=[]):
self.constant_list = constant_list
super(BaseMediator, self).__init__(self.NAME, view_component)



Obtendo o mesmo resultado que o código original sem perder em flexibilidade:


class ProductListMediator(BaseMediator):
product_proxy = None


O PureMVC reune uma gama de padrões em uma solução bem interessante. Todas as partes do MVC são agrupadas e invocadas por um padrão facade, este facade invoca o controller através do padrão command utilizando um esquema de execução por variáveis globais, com essa solução o controller não fica acoplado ao facade, já que se o nome do método for alterado, basta alterar em um local, no alias definido na variável global.


class AppFacade(Facade):
ON_ACT_STARTUP = 'on_act_startup'

@staticmethod
def getInstance():
return AppFacade()

def initializeController(self):
super(AppFacade, self).initializeController()
super(AppFacade, self).registerCommand(AppFacade.ON_ACT_STARTUP,\
controller.StartupCommand)


O controller por sua vez utiliza o padrão observer para se comunicar com a view garantido fraca acoplamento com o uso de notificações.


class DeleteCommand(SimpleCommand, ICommand):
def execute(self, note):
product = note.getBody()
product_proxy = self.facade.retrieveProxy(model.ProductProxy.NAME)
product_proxy.delete(product)
self.facade.sendNotification(main.AppFacade.ON_PRODUCT_DELETE)


A view então utilizando o padrão mediator para se comunicar com a visão concreta Gtk, Html, etc mantem mais uma vez acoplamento fraco, se for preciso mudar de Gtk para Html ou Qt, muito pouco do código precisará ser modificado, sanando a minha necessidade inicial.


class DialogMediator(Mediator, IMediator):
NAME = "DialogMediator"
def __init__(self, view_component):
self.view_component = view_component

def listNoticationInterests(self):
return [main.AppFacade.ON_SHOW_DIALOG,]


E por fim o model utiliza o padrão proxy para garantir abstração por interface, assim proporcionando independência da tecnologia utilizada no model, ORM, XML, serialização, etc.


class ProductProxy(Proxy):
NAME = "ProductProxy"
def __init__(self):
super(ProductProxy, self).__init__(self.NAME, [])

def get_all(self):
return value_object.ProductVO.query.all()


O esquema utilizado pelo PureMVC é um tanto complexo a primeira vista, mas se for utilizado com bom senso e sempre com simplicidade em mente o retorno pode ser muito positivo. Além da fraco acoplamento a manutenção fica muito fácil, já que cada entidade da aplicação fica bem separada evitando propagação de erros pela aplicação. Vale lembrar que o PureMVC funciona como um esqueleto para sua aplicação não obrigando o uso explicito de todos os componentes.

Como o PureMVC é feito para um conjunto de linguagens ao qual o Python não se encaixa, uma camada acima do PureMVC se faz necessária afim de simplificar a utilização deste e excluindo as partes desnecessárias, como os exemplos mostrados acima.

Uma dica é simplificar o esquema de notificações do mediator para que as notificações executem os métodos da classe implementa o mediator que tiverem o mesmo nome das notificações, excluindo assim a necessidade do registro e criação das notificações na classe. Como exemplo abaixo, retirado da mesma demo:


def listNotificationInterests(self):
return [
main.AppFacade.ON_PRODUCT_CHANGE,
main.AppFacade.ON_PRODUCT_DELETE,
]

def handleNotification(self, note):
if note.getName() in [main.AppFacade.ON_PRODUCT_CHANGE, main.AppFacade.ON_PRODUCT_DELETE]:
self.view_component.clear()
self.view_component.fill(self.product_proxy.get_all())


Neste parte de código o programador precisa especificar quais as notificações a classe recebe e escrever um método com um monte de condicionais para tratar cada notificação. É muito mas fácil escrever uma metaclass para transformar isso em um esquema de sinal que invoca o sinal que tiver um método na classe com o mesmo nome do sinal.

Fica na minha lista de coisas a fazer uma versão deste framework para Python, com um código e uma filosofia adaptada ao estilo Python de ser.

Por fim um link interessante sobre o PureMVC.

3 comentários:

Anônimo disse...

achei interessante o framework.
um pouco "java" demais, mas muito útil para quem busca desenvolver uma aplicação com um nível de acoplamento nulo ou quase zero.

Anônimo disse...

Talvez não seja Java demais, e sim o foco seja "orientação a objeto". Como java é 100% orientado a objeto, qualquer linguagem que defina bem as regras de orientação a objeto, ficara parecida com java. rsrs

Anônimo disse...

java não é 100% orientada a objetos!