Pergunta para iniciantes sobre o padrão de design do Decorator

18

Eu estava lendo um artigo de programação e ele mencionava o padrão Decorator. Venho programando há algum tempo, mas sem nenhum tipo de educação ou treinamento formal, mas estou tentando aprender sobre os padrões padrão e tal.

Então, procurei o Decorator e encontrei um artigo da Wikipedia sobre ele. Agora eu entendo o conceito do padrão Decorator, mas fiquei um pouco confuso com esta passagem:

Como exemplo, considere uma janela em um sistema de janelas. Para permitir a rolagem do conteúdo da janela, podemos adicionar barras de rolagem horizontais ou verticais, conforme apropriado. Suponha que janelas sejam representadas por instâncias da classe Window e assuma que essa classe não tem funcionalidade para adicionar barras de rolagem. Poderíamos criar uma subclasse ScrollingWindow que os fornecesse ou criar um ScrollingWindowDecorator que adiciona essa funcionalidade aos objetos Window existentes. Nesse ponto, qualquer uma das soluções estaria bem.

Agora vamos supor que também desejamos a capacidade de adicionar bordas às nossas janelas. Novamente, nossa classe original do Window não tem suporte. A subclasse ScrollingWindow agora apresenta um problema, porque criou efetivamente um novo tipo de janela. Se desejarmos adicionar suporte de borda a todas as janelas, devemos criar as subclasses WindowWithBorder e ScrollingWindowWithBorder. Obviamente, esse problema piora a cada novo recurso a ser adicionado. Para a solução do decorador, simplesmente criamos um novo BorderedWindowDecorator - em tempo de execução, podemos decorar janelas existentes com o ScrollingWindowDecorator ou o BorderedWindowDecorator ou ambos, conforme entendermos.

OK, quando eles dizem adicionar bordas a todas as janelas, por que não adicionar funcionalidade à classe Window original para permitir a opção? A meu ver, a subclasse é apenas para adicionar funcionalidades específicas a uma classe ou substituir um método de classe. Se eu precisasse adicionar funcionalidade a todos os objetos existentes, por que não modificaria a superclasse para fazer isso?

Havia outra linha no artigo:

O padrão do decorador é uma alternativa à subclassificação. A subclasse adiciona comportamento em tempo de compilação, e a alteração afeta todas as instâncias da classe original; a decoração pode fornecer um novo comportamento em tempo de execução para objetos individuais.

Não entendo onde eles dizem "... a mudança afeta todas as instâncias da classe original" - como a subclasse altera a classe pai? Não é esse o objetivo da subclasse?

Eu vou assumir que o artigo, como muitos Wiki, simplesmente não está escrito claramente. Eu posso ver a utilidade do Decorator nessa última linha - "... forneça um novo comportamento em tempo de execução para objetos individuais".

Sem ter lido sobre esse padrão, se eu precisasse alterar o comportamento em tempo de execução de objetos individuais, provavelmente teria construído alguns métodos na super ou subclasse para ativar / desativar o referido comportamento. Por favor, ajude-me a entender realmente a utilidade do Decorador e por que meu pensamento de novato é falho?

Jim
fonte
agradeço a edição, Walter ... fyi, usei "rapazes" não para excluir mulheres, mas como uma saudação informal.
Jim
A edição seria apenas para remover a saudação em geral. Não é padrão SO protocolo / SE usar um em perguntas [Não se preocupe, nós não pensamos que rude para saltar directamente para a pergunta]
Farrell
legal, obrigado! foi só que ele marcou "removendo o gênero" e eu odiaria que alguém pensasse que eu sou misógino ou algo assim! :)
Jim
Eu os uso intensamente para evitar procedimentos legais chamados "serviços": medium.com/@wrong.about/…
Zapadlo

Respostas:

13

O padrão decorador é aquele que favorece a composição sobre a herança [outro paradigma de POO que é útil para aprender]

O principal benefício do padrão decorador - sobre a subclasse é permitir mais opções de combinação e combinação. Se você tem, por exemplo, 10 comportamentos diferentes que uma janela pode ter, isso significa que, com a subclasse, você precisa criar todas as combinações diferentes, o que também incluirá inevitavelmente muita reutilização de código.
No entanto, o que acontece quando você decide adicionar um novo comportamento?

Com o decorador, você apenas adiciona uma nova classe que descreve esse comportamento, e é isso - o padrão permite que você efetivamente o insira sem nenhuma modificação no restante do código.
Com a subclasse, você tem um pesadelo em suas mãos.
Uma pergunta que você fez foi "como a subclasse altera a classe pai?" Não é que isso mude a classe pai; quando diz uma instância, significa qualquer objeto que você "instancia" (se você estiver usando Java ou C #, por exemplo, usando o newcomando). A que se refere é que, quando você adiciona essas alterações a uma classe, você não tem escolha para que essa alteração ocorra, mesmo que não seja realmente necessário.

Como alternativa, você pode colocar toda a funcionalidade dele em uma única classe com ela ativada / desativada por meio de sinalizadores ... mas isso acaba com uma única classe que se torna cada vez maior à medida que o projeto cresce.
Não é incomum iniciar seu projeto dessa maneira e refatorar para um padrão de decorador depois de atingir uma massa crítica efetiva.

Um ponto interessante que deve ser destacado: você pode adicionar a mesma funcionalidade várias vezes; para que você possa, por exemplo, ter uma janela com quantia ou borda dupla, tripla ou qualquer que seja a sua necessidade.

O ponto principal do padrão é permitir alterações em tempo de execução: talvez você não saiba como deseja que a janela seja exibida até que o programa esteja em execução, e isso permite que você a modifique facilmente. Concedido, isso pode ser feito através da subclasse, mas não tão bem.
E, finalmente, permite que a funcionalidade seja adicionada a classes que talvez você não consiga editar - por exemplo, em classes seladas / finais ou fornecidas por outras APIs

Farrell
fonte
2
Obrigado, Farrell - quando você diz "Como alternativa, você pode colocar toda a funcionalidade dele em uma única classe com ela ativada / desativada por meio de sinalizadores ... mas isso acaba com uma única classe que se torna cada vez maior à medida que o seu projeto cresce". que fez clique para mim. Acho que parte da questão é que nunca trabalhei em uma turma tão grande que o decorador fazia sentido para mim. Pensando grande, posso ver definitivamente os benefícios ... obrigado!
Jim
Além disso, a parte sobre aulas seladas faz muito sentido ... obrigado!
Jim
3

Considere as possibilidades de adicionar barras de rolagem e bordas com subclassificação. Se você quiser todas as possibilidades, terá quatro classes (Python):

class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)

Agora, WindowWithScrollBarAndBorder.draw()a janela é desenhada duas vezes e, na segunda vez, pode ou não substituir a barra de rolagem já desenhada, dependendo da implementação. Portanto, seu código derivado está fortemente associado à implementação de outra classe, e você precisa se preocupar com isso toda vez que alterar o Windowcomportamento da classe. Uma solução seria copiar e colar o código das superclasses das classes derivadas e ajustá-lo às necessidades das classes derivadas, mas cada alteração em uma superclasse deve ser copiada e colada novamente e ajustada novamente, para que as classes derivadas sejam novamente fortemente acoplado à classe base (através da necessidade de copiar, colar e ajustar). Outro problema é que, se você precisar de outra propriedade que uma janela possa ou não ter, será necessário duplicar todas as classes:

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

Isso significa para um conjunto de recursos mutuamente independentes f com | f | sendo o número de recursos, você deve definir 2 ** | f | classes, por exemplo. se você tiver 10 recursos, receberá 1024 classes fortemente vinculadas. Se você usar o Padrão Decorador, cada recurso terá sua própria classe independente e pouco acoplada e você terá apenas 1 + | f | classes (11 no exemplo acima).

pillmuncher
fonte
Veja, meu pensamento não seria continuar adicionando classes - seria adicionar a funcionalidade à (sub) classe original. então na classe Window (object): você tem scrollBar = true, border = false, etc ... A resposta Farrell me mostra onde isso pode dar errado, no entanto - apenas levando a classes inchadas e tal ... obrigado pela resposta, +1!
Jim
bem, +1 quando eu tiver o representante para fazer isso!
Jim
2

Eu não sou especialista nesse padrão em particular, mas, a meu ver, o padrão Decorator pode ser aplicado a classes que você pode não ter a capacidade de modificar ou subclasse (elas podem não ser o seu código e ser seladas, por exemplo ) No seu exemplo, e se você não escrevesse a classe Window, mas apenas a estivesse consumindo? Desde que a classe Window tenha uma interface e você programe nessa interface, o Decorator pode usar a mesma interface, mas estender a funcionalidade.

O exemplo que você menciona é realmente abordado aqui com bastante clareza:

Estender a funcionalidade de um objeto pode ser feito estaticamente (em tempo de compilação) usando herança, no entanto, pode ser necessário estender a funcionalidade de um objeto dinamicamente (em tempo de execução) à medida que um objeto é usado.

Considere o exemplo típico de uma janela gráfica. Para estender a funcionalidade da janela gráfica, por exemplo, adicionando um quadro à janela, seria necessário estender a classe window para criar uma classe FramedWindow. Para criar uma janela emoldurada, é necessário criar um objeto da classe FramedWindow. No entanto, seria impossível começar com uma janela simples e estender sua funcionalidade no tempo de execução para se tornar uma janela emoldurada.

http://www.oodesign.com/decorator-pattern.html

Dan Diplo
fonte