.NET - O padrão de projeto Decorator


O padrão de projeto Decorator é um padrão estrutural que permite agregar dinamicamente responsabilidades adicionais a um objeto fornecendo assim uma alternativa flexível à utilização da herança como modo de estender funcionalidades.

Em uma definição mais formal de acordo com Gamma um padrão Decorator permite:

“Dinamicamente, agregar responsabilidades adicionais a um objeto. Os decoradores fornecem uma alternativa flexível ao uso de subclasses para extensão de funcionalidades.” (GAMMA et al., 2000:170).

Nota: Padrões de projeto estruturais são padrões que lidam com as estruturas do projeto, facilitando a comunicação entre suas entidades. Dessa forma eles tratam a maneira como objetos e classes estão dispostos em um projeto a fim de formar estruturas mais complexas.

De forma mais simples podemos dizer que o padrão Decorator permite estender (decorar) dinamicamente as características de uma classe usando a composição.

O diagrama de classes para o padrão Decorator usando a notação UML é mostrado na figura abaixo:

Classes/Objetos participantes do padrão:
  1. Componente - Define a interface para objetos que podem ter responsabilidades adicionadas a eles
    dinamicamente;
  2. ComponenteConcreto - Define um objeto para o qual responsabilidades adicionais podem ser anexadas;
  3. Decorador - Mantém uma referência para um objeto Componente e define uma interface compatível com
    interface de componente;
  4. DecoradorConcreto - Adiciona responsabilidades ao componente;

Vantagens em usar o padrão Decorator:

Quando usar o padrão Decorator ?

Implementando o padrão de projeto Decorator

Este exemplo foi baseado no livro “Use a Cabeça (head first) Padrões de Projeto”, FREEMAN (2007:81-96).

Para implementar o padrão Decorator eu vou usar o seguinte cenário: Carros e seus opcionais. (poderiamos ter usado Pizzas, Lanches, Bebidas e seus respectivos acessários.)

Imagem um carro , pode ser de qualquer marca ou modelo, e , no nosso exemplo será uma Ferrari.

Geralmente um carro é oferecido na configuração padrão que pode ser acrescida de opcionais que tem um valor que deve ser somando ao valor do carro.

Assim uma Ferrari é vendida com uma configuração padrão e no nosso exemplo vamos decorá-la com dois opcionais : Motor Turbo e Bancos de Couro.

Neste cenário iremos ter as seguintes classes:

  1. Carro - Representa a classe abstrata que será um tipo comum usado tanto para os carros como para os opcionais que usaremos para decorar os carros. Este tipo será a base para todos os carros que iremos decorar;
  2. DecoratorCarro - Será o tipo base para os opcionais dos carros. Essa classe implementa a classe Carro e sobrescreve o seu comportamento. Este tipo será a base para todas as classes que usaremos ara decorar um carro;
  3. Ferrari - Esta classe implementa a classe Carro e sobrescreve o seu comportamento com o comportamento específico do carro Ferrari sem opcionais;
  4. Ferrari_Turbo e Ferrari_Couro - Classes que representam opcionais com o qual iremos decorar um carro. Essas classes implementam a classe DecoratorCarro e substituem o seu comportamento. Cada uma delas possui um referência local a um tipo Carro que será usado para manipular a referência para o carro que esta sendo decorado.

A seguir temos o diagrama de classes:

Vamos agora criar as classes e verificar o seu funcionamento.

Eu vou usar o Visual C# 2010 Express Edition e criar uma aplicação Console chamada PadroProjetoDecorator;

No menu Procjet clique em Add Class e informe o nome Carro.cs e a seguir digite o código da classe Carro conforme abaixo:

namespace PadraoProjetoDecorator
{
    public abstract class Carro
    {
        private double _preco = -1;
        private string _descricao = "Carro Abstrato.";

        public virtual double Preco
        {
            get { return _preco; }
        }
        public virtual string Descricao
        {
            get { return _descricao; }
        }
    }
}

A seguir vamos declarar a classe Decorator chamada DecoratorCarro.cs conforme o código a seguir:

namespace PadraoProjetoDecorator
{
    public abstract class DecoratorCarro : Carro
    {
        double _preco = -1;
        string _descricao = "Decorador Abstrato de Carro";

        public override double Preco
        {
            get { return _preco; }
        }
        public override string Descricao
        {
            get { return _descricao; }
        }
    }
}

Agora vamos criar a classe FerrariSpider que representa um Ferrari padrão:

namespace PadraoProjetoDecorator
{
    class FerrariSpider : Carro
    {
        double _preco = 255000.99;
        string _descricao = "Ferrari Spider, ";

        public override double Preco
        {
            get
            {
                return _preco;
            }
        }
        public override string Descricao
        {
            get
            {
                return _descricao;
            }
        }
    }
}

Agora vamos criar as classes dos opcionais que serão usadas para decorar o carro padrão. Vamos começar com a classe Ferrari_Turbo conforme o código abaixo:

namespace PadraoProjetoDecorator
{
    class Ferrari_Turbo : DecoratorCarro
    {
        double _preco = 9500.50;
        string _descricao = "Turbo, ";
        Carro _carro;

        public Ferrari_Turbo(Carro carro)
        {
            _carro = carro;
        }

        public override double Preco
        {
            get
            {
                return _carro.Preco + _preco;
            }
        }

        public override string Descricao
        {
            get
            {
                return _carro.Descricao + _descricao;
            }
        }
    }
}

Da mesma forma vamos criar a classe Ferrari_Couro conforme abaixo:

namespace PadraoProjetoDecorator
{
    public class Ferrari_Couro : DecoratorCarro
    {
        double _preco = 4250.25;
        string _descricao = "Banco de Couros, ";
        Carro _carro;

        public Ferrari_Couro(Carro carro)
        {
            _carro = carro;
        }

        public override double Preco
        {
            get
            {
                return _carro.Preco + _preco;
            }
        }

        public override string Descricao
        {
            get
            {
                return _carro.Descricao + _descricao;
            }
        }
    }
}

Finalmente para testar o padrão Decorator vamos incluir o código abaixo no arquivo Program.cs:

using System;

namespace PadraoProjetoDecorator
{
    class Program
    {
        static void Main(string[] args)
        {
            //criar um carro
            Console.WriteLine(" --------------Ferrari Padrão-----------------------");
            Carro carro = new FerrariSpider();
            Console.WriteLine("Descricao --> " + carro.Descricao.TrimEnd(' ', ','));
            Console.WriteLine("Preco -->" + carro.Preco.ToString());
            Console.ReadLine();
            Console.WriteLine(" --------------Ferrari Decorada---------------------");
            //decora o carro com banco de couro
            carro = new Ferrari_Couro(carro);
            //decora o carro com motor turbo
            carro = new Ferrari_Turbo(carro);
            Console.WriteLine("Descricao --> "+ carro.Descricao.TrimEnd(' ', ','));
            Console.WriteLine("Preco -->" + carro.Preco.ToString());
            Console.ReadLine();
        }
    }
}

Abaixo vemos o resultado da execução do projeto:

Criando os carros e seus opcionais separados temos que as classes se tornam mais reutilizáveis.

Qualquer opcional pode ser usado para decorar qualquer carro, desde que o carro específico implemente um tipo Carro e o opcional especifico implemente o tipo DecoratorCarro.

Como cada carro e cada opcional são encapsulados em suas próprias classes, não há perigo de quebrar a implementação em outros carros ou opcionais quando modificações de um carro específico ou opcional for necessário.

Pela mesma razão, podemos adicionar novos carros e opcionais sem medo de quebrar implementações existentes. Opcionais já definidos podem ser facilmente reutilizados para carros novos que são criados.

Por outro lado, se novos opcionais são definidos para carros existentes, não há necessidade de modificar o código para o próprio carro. Decorar um carro com opcionais desta maneira é muito fácil, e cria um código muito limpo, sustentável e legível. Tudo que você tem que fazer é criar uma instância do carro que você quer decorar e passar o exemplo para o construtor de qualquer opcional com o qual você quer decorar o carro.

Pegue a solução completa aqui:  PadraoProjetoDecorator1.zip

João 4:22 Vós adorais o que não conheceis; nós adoramos o que conhecemos; porque a salvação vem dos judeus.
João 4:23
Mas a hora vem, e agora é, em que os verdadeiros adoradores adorarão o Pai em espírito e em verdade; porque o Pai procura a tais que assim o adorem.
João 4:24
Deus é Espírito, e é necessário que os que o adoram o adorem em espírito e em verdade.

Referências:


José Carlos Macoratti