Decorator Pattern SISMO - Sistemas e Mobilidade http://www.sismo.deinf.ufma.br Departamento de Informática / UFMA Junho de 2008
Revisando os conceitos Herança é poderosa mas não é flexível Comportamento herdado nas subclasses é fixado na compilação Todas as subclasses herdam o mesmo comportamento Composição e Delegação permitem reúso flexível de comportamento Composição permite estender o comportamento do objeto dinamicamente na execução Isso é possível porque não é necesário modificar o código existente Pelo mesmo motivo, as chances de introdução de erros são reduzidas
Princípio O.O: Estender sem Modificar Classes devem ser abertas para extensão e fechadas para modificação A meta é permitir que classes sejam facilmente estendidas para incorporar novos comportamentos sem modificar o código existente Desejamos obter projetos resilientes a mudanças, suficientemente flexíveis para atender novas funcionalidades e requisitos Como atender essa meta um tanto contraditória? Uso de técnicas O.O provadas e aprovadas (padrões) O princípio não deve ser aplicado de forma generalizada, mas sobretudo em pontos do projeto mais suscetíveis a mudanças Identificar esses pontos depende da experiência do projetista sobre o domínio e em problemas semelhantes
O Problema do Ressaca s Bar O Ressaca s é o bar de maior sucesso da região. A razão é a variada oferta de drinks a disposição dos clientes. Por outro lado, o sistema de pedidos do bar se tornou mais complexo e está difícil mantê-lo por conta da grande variedade de drinks Vamos ver como o problema começou...
Diagrama de Classes dos Drinks Figura: Diagrama Original
Onde está o busílis? O problema é que a pedido dos clientes, novos drinks foram criados pela adição de diferentes aditivos aos drinks existentes Os projetistas criaram novas classes para representar os novos drinks e calcular os preços de cada um deles levando em conta os diferentes ingredientes em decorrência houve uma verdadeira explosão de classes para representar os diferentes tipos de drinks Um pesaselo de manutenção... além de violar dois princípios O.O. Quais?
Princípios O.O violados Encapsule os aspectos mais sujeitos a mudanças Prefira Composição/Delegação em vez de Herança
Experimentando uma solução Figura: Solução baseada em variáveis de instância
Experimentando uma solução II Figura: Calculando preços dos drinks
Solução baseada em variáveis de instância O método price na classe Drink calcula os preços associados aos ingredientes Nas subclasses, o método price calcula o preço básico do drink e chama o método price da classe Drink para calcular os preços dos condimentos que são parte do drink Quais os problemas com essa solução?
Fatores que podem mudar afetando o design A adição de novos ingredientes obriga a adicionar novos métodos e a alterar o método price na superclasse Nem todos os ingredientes são adequados para todos os drinks; no entanto, os métodos associados a esses ingredientes são herdados por todas as subclasses E se alguém quiser uma dose dupla de apenas alguns dos ingredientes?
A abordagem Decorator I Figura: Tome um objeto BaseCaipirinha
A abordagem Decorator II Figura: Decore-o com um objeto Cachaca
A abordagem Decorator III Figura: Decore-o com um objeto Adocante Chame o método price e use delegação para calcular os preços dos ingredientes adicionais
Características da Abordagem Decoradores empacotam os objetos decorados Decoradores têm o mesmo supertipo dos objetos que eles decoram Objetos são decorados em camadas O decorador pode adicionar o seu próprio comportamento antes ou depois de delegar ao objeto que decora a responsabilidade de continuar o trabalho Objetos podem ser decorados dinamicamente em tempo de execução
Padrão Decorator Classificação: Propósito: Estrutural Escopo: Objetos Intenção: agregar, dinamicamente, responsabilidades adicionais a a objetos individuais, e não a toda uma classe. São uma alternativa flexível ao uso de subclasses para extensão de funcionalidades a.k.a.: Wrapper
Aplicabilidade Quando se deseja crescentar responsabilidades a objetos individuais de forma dinâmica e transparente Para permitir a remoção de responsabilidades também dinamicamente Quando a extensão através de subclasses não é prática Por exemplo, quando há possibilidade de explosão do número de subclasses
Estrutura do Decorator Figura: Estrutura Motivação: Adicionar responsabilidades a objetos individuais, e não a toda uma classe
Participantes Component: define a interface para objetos que podem ter responsabilidades acrescentadas dinamicamente ConcreteComponent: define um objeto para o qual responsabilidades adicionais podem ser atribuídas Decorator: define uma interface que segue a de Component e mantém uma referência para um objeto Component ConcreteDecorator: acrescenta responsabilidades ao component
Colaborações no Decorator Decorator repassa solicitações para seus objeto Component e pode, opcionalmente, executar operações adicionais antes e depois de repassar a solicitação
Ressaca s Decorator Figura: Diagrama de Classes
Codificando o sistema - Classe Raiz e Decorador Figura: Classes Drink e AditivoDecorator
Codificando o sistema - Classes Concretas Figura: Classe BaseCaipirinha
Codificando o sistema - Classes Concretas Figura: Classe Cocktail
Codificando o sistema - Um Decorador Concreto Figura: Classe Cachaca
Sistema Ressaca s Figura: Implementando o sistema
Exemplo com Widgets Figura: Decorando widgets Adicionando propriedades, como bordas, ou comportamentos, como rolamento, a componentes de uma interface de usuário, apenas quando for necessário
Decorador para Widgets Figura: Diagrama de Classes
Exemplo de código Figura: Classes Component e Decorator
Exemplo de código II Decorator decora o VisualComponent referenciado por component, que é inicializado no construtor Para cada operação na interface de VisualComponent, Decorator define uma implementação que passa a solicitação para component: Figura: Métodos de Decorator
Exemplo de código III Figura: Classe BorderDecorator
Exemplo de código IV Figura: Inserindo a Classe TextView
Exemplo de código V Figura: Código cliente
Consequências do uso do Decorator Maior flexibilidade que a herança (estática) Evita classes sobrecarregadas de características na parte superior da hierarquia Grande quantidade de pequenos objetos. Padrões de criação como Builder e Factory ajudam a criar objetos no padrão Decorator O uso do Decorator é mais apropriado se não for necessário conhecer os tipos concretos dos objetos encapsulados