Ir para o conteúdo
Programador estudando educação.

SubClasses não! Decorators

4 4 comentários Ninguém está seguindo este artigo ainda. 63 visualizações

Imagine: Você está fazendo um sistema que deve apresentar diversos formatos de dados similares e derivados. O que você faz?

Classes e Subclasses! Yeah!
Não... Pare.

Eu também sempre fui por aí, o projeto Noosfero também começou assim, mas qual é o problema com isso?

Imagine que você quer apresentar arquivos em uma página web. Temos a classe File e sabemos que imagens devem ser apresentadas com a tag <img>, enquanto outros arquivos serão apresentados apenas com o link para download. O que você faz?

Podemos fazer duas subclasses estendendo File: Image e GenericFile — ou — colocamos um "if" na visualização (Ugh... mas é mais econômico).

Ok... Mas agora você decidiu que as imagens serão editáveis e você precisa de um modo de edição diferenciado para bitmaps (PNG, JPG) e vetores (SVG). Depois você decide que vai adicionar um player para áudio (OGG), para vídeos html5 (OGV, WebM) e vídeos flash (FLV, MP4). Vai continuar usando "if"?

Agora não dá para fugir de uma representação com classes especializadas. A primeira idéia seria:

  • File
    • Image
      • ImageBitmap
      • ImageVector
    • Playable
      • Audio
      • Video
        • VideoHTML5
        • VideoFlash
    • GenericFile

No caso de uma aplicação Rails e de tantos outros fameworks (se não todos), a classe que representa o objeto persistido em banco (Model), contendo ou não referência para o sistema de arquivos, é igualmente registrada em banco, frequentemente tendo uma tabela exclusiva para sí. Isso torna a busca por itens do tipo X muito mais fácil e rápido.

Qual o problema com isso? Imagine que agora você decide fazer o preview de documentos de texto, como PDFs e ODFs. Ao atualizar sua aplicação será necessário fazer uma correção no banco onde alguns registros dos tipos em questão serão convertidos para o novo tipo.

"Migrations existem para isso. Não é?" Sim, mas você não quer adicionar complexidade (código extra e espalhado) a sua modificação. Você quer tranquilidade. O admin quer tranquilidade. Atualização simples, admin feliz → Admin feliz, usuário seguro. :-)

Ok, esse argumento ainda não convenceu, então imagine que sua aplicação suporta plugins e alguns deploys irão usar um ou outro plugin para um tipo não suportado no core e, para piorar, o plugin pode ser habilitado e desabilitado a qualquer momento. O que fazer? Você faria com que o método que habilita e desabilita plugins rodasse a migration nesses momentos? (Se você respondeu "Er... Sim." eu tenho medo de você.)

Bem, acho que agora eu convenci que é possível chegar em um ponto onde a criação de subclasses para apresentar tipos derivados pode te levar a calvície prematura ou internação em instituição psiquiátrica. Mas qual é a solução?

Não registre tipos derivados em novas classes! Crie decorators.

Você sabe o que é um decorator? Eu também não estudei padrões de projeto com afinco, mas é bom que você tenha ao menos uma idéia do que isso significa.

No livro Objects on Rails, Avdi Grimm descreve o que ele chama de exhibit, um decorator especializado em apresentar models. Perfeito! Inspirado nele eu trabalhei numa solução para a exibição de arquivos no Noosfero. A idéia é o seguinte:

Pegue aquele diagrama de herança de classes que coloquei no início do texto, não derive File, crie uma árvore independente de exhibits:

  • File (sem subclasses)
  • FilePresenter (o nome que dei ao meu exhibit abstrato)
    • Image
      • ImageBitmap
      • ImageVector
    • Playable
      • Audio
      • Video
        • VideoHTML5
        • VideoFlash
    • GenericFile

Quando você quiser apresentar um arquivo não use render_page_to(some_file), use:

fp = FilePresenter.for(some_file)
render_page_to(fp)

Quando quiser extrair uma informação específica ao tipo do arquivo não use some_file.icon(), isso exigiria uma coleção de ifs neste método, use:

fp = FilePresenter.for(some_file)
fp.icon

A princípio pode parecer que precisaremos de muito código extra coletando o FilePresenter de File antes de qualquer exibição, mas não, veja o meu patch para o Noosfero. Poucos locais precisam usar o método FilePresenter.for() e a tal instância é repassada adiante para outros usos. Essa implementação apenas coloca o (praticamente) mesmo código em locais diferentes, tornando "tudo" mais maleável. Com isso os sérios problemas mostrados antes estão solucionados. Você pode, por exemplo, adicionar e remover um plugin que define um presenter para qualquer tipo específico, a qualquer momento, e tudo funcionará de imediato, sem maiores preocupações (se você não usar POG).

Mas como funcionaria o FilePresenter.for()?

Sim, esta é umas das diferenças da minha implementação para a proposta de Avdi (boa para este caso, mas considere a proposta de Avdi mais abrangente), o método de classe deve perguntar a cada subclasse se ele aceita exibir o arquivo e com qual prioridade.

Por exemplo, se temos uma imagem JPG o FilePresenter.GenericFile deve responder positivamente com um valor baixo, o FilePresenter.ImageBitmap deve responder positivamente com um valor alto e o FilePresenter.Audio deve responder positivamente com um valor nulo. Depois de coletar todas as respostas, o método FilePresenter.for(meu_jpg) criará uma instância da classe que respondeu com maior prioridade, encapsula o objeto File meu_jpg.

Como a subclasse de FilePresenter "responde" se aceita ou não encapsular a instancia de File? Toda subclasse deve implementar o método accepts?(file) como método de classe.

Tudo junto (uma visão simples):

class FilePresenter
  def self.for(f)
    # Intera para cada subclasse, ordenando por prioridade
    klass = FilePresenter.subclasses.sort_by {|class_name|
      # Pergunta se a subclasse aceita o arquivo e sua prioridade
      class_name.constantize.accepts?(f) || 0
    }.last.constantize  # Pega a subclasse de maior prioridade
    # Retorna a instância da subclasse, encapsulando o objeto do arquivo
    klass.new(f)
    # Existem outros detalhes a serem considerados, mas isso basta para o exemplo.
  end

  # Isso fará esse decorator funcionar como uma instância de File
  def method_missing(m, *args)
    @file.send(m, *args)
  end

  ...
end

class FilePresenter::Image < FilePresenter
  def initialize(f)
    @file = f  # encapsula a instância de File
  end

  def self.accepts?(f)
    # retorna alta prioridade para imagens
    f.content_type[0..4]=='image' ? 10 : nil
  end

  ...
end

Fonte: http://softwarelivre.org/aurium/blog/subclasses-nao-decorators

4 4 comentários

Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.

Ordenar por
  • Dbf5f3fa1fd9f3cde24d608deea26854?only path=false&size=50&d=404

    kayli 22 de Junho de 2022, 2:04

    Nice post. I was checking continuously this blog and I'm impressed! Very helpful info specifically the last part I care for such info a lot. I was looking for this particular info for a very long time buyc​igar​ette​sonl​ine0​1.bl​ogsp​ot.c​om/ Thank you and good luck.

  • 3993d4e16961e49e555dbd0f8efec728?only path=false&size=50&d=404

    jilly 18 de Junho de 2022, 1:13

    Thanks for sharing such a great information.. It really helpful to me..I always search to read the quality content and finally i found this in you post buyw​eed.​biga​rtic​les.​com/ keep it up!

  • 007df8a6a6e5d96f4ca8d38e3c6d4cbe?only path=false&size=50&d=404

    emile 9 de Junho de 2022, 7:02

    Thanks for sharing such a great information.. It really helpful to me..I always search to read the quality content and finally i found this in you post site​s.go​ogle​.com​/vie​w/bu​ywee​d-/ keep it up!

  • 248adad5c0033c0877fc5b86ccd85ed7?only path=false&size=50&d=404

    somatodrol 11 de Abril de 2018, 18:21

    Porém, mesmo o Somatodrol sendo um suplemento voltado para homens, a mesma empresa desenvolveu o SomaPro Woman (versão feminina), um suplemento voltado para mulheres que pode causar os mesmos efeitos benéficos no organismo, porém, sem os efeitos colaterais que seriam gerados caso a quantidade de testosterona aumentasse na mulher.
    O Somatodrol feminino permite que as mulheres ganhem mais energia, definam melhoro corpo e ainda percam aquela gordura localizada que tanto incomoda sem que para isso sofram qualquer efeito colateral com o uso do suplemento. somatodrol efeitos colaterais yahoo
    Efeitos positivos do Somatodrol.
    Para completar ele ainda aumenta o apetite sexual! Parece que o Somatodrol realmente é uma ótima opção! Veja mais alguns efeitos positivos do Somatodrol:
    Elimina câimbras Aumenta a potência sexual Aumenta a excreção de amônia, ajudando no consumo energético.
    O Somatodrol tem um Гіtimo custo benefГ­cio se considerarmos seus resultados positivos para quem deseja ganhar massa muscular. somatodrol-no-brasil.com