Atenção: esse artigo tem uma palestra complementar. Clique e assista!
Atenção: esse artigo tem dois vídeos complementares.
Clique e assista o primeiro!
Clique e assista o segundo!
Neste artigo veremos o que são design patterns e a implementação de dois dos 23 padrões definidos no livro Design Patterns Elements of Reusable Object-Oriented Software. São eles o padrão Strategy e o padrão Flyweight. Ao final, veremos como eles podem se relacionar desenvolvendo exemplos práticos dos mesmos.
Para que serve
Design patterns são utilizados para melhorar a forma que aplicações são construídas e documentadas, permitindo o reuso de soluções. Os padrões que apresentaremos são utilizados quando há necessidade de executar diferentes algoritmos de acordo com a situação, e também quando precisamos diminuir o uso de memória nas nossas aplicações construídas sob o paradigma da orientação a objetos.
Em que situação o tema é útil
O Strategy pode ser utilizado para alternar rotinas de cálculos matemáticos em tempo de execução e já com o Flyweight, podemos ter vantagem com o melhor uso de memória em aplicações que possuem uma grande quantidade de objetos com características semelhantes.
Resumo do DevMan
Neste artigo teremos uma visão geral sobre design patterns e sua importância. Explicaremos dois padrões: Strategy e Flyweight. Para cada um iremos descrever suas características e forma de implementação, demonstrando o uso desses padrões através de exemplos em aplicações construídas em .NET. Por fim, veremos como utilizar esses dois padrões simultaneamente, demonstrando suas relações.
Podemos definir design patterns como soluções bem experimentadas para problemas conhecidos. Apesar de o termo ser bastante conhecido na Engenharia de Software, ele teve origem na Engenharia Civil, através dos trabalhos do arquiteto austríaco Chistopher Alexander que, nas décadas de 60 e 70, produziu um método estruturado para descrição de boas práticas de projeto dentro de uma área de conhecimento.
No mundo da TI, um dos livros mais importantes sobre padrões é Design Patterns Elements of Reusable Object-Oriented Software, escrito por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, também conhecidos como GoF, ou Gang of Four. Neste livro estão descritos 23 padrões de modelagem baseados no conhecimento prático dos autores. Estes padrões estão divididos em padrões de criação (creational patterns), que tratam do processo de instanciação de classes; padrões estruturais (structural patterns), que tratam da composição dos objetos; e padrões comportamentais (behavioral patterns), que se preocupam com a comunicação entre os objetos.
Neste artigo vamos comentar e exemplificar o uso de dois padrões descritos pela GoF. São os design patterns conhecidos como Strategy e Flyweight. O primeiro permite que diferentes algoritmos sejam alternados de acordo com a necessidade, enquanto que o segundo provê uma forma de minimizar o uso de memória dos aplicativos, através de cache de objetos. Depois, vamos demonstrar que os padrões não trabalham isolados, mas possuem relacionamentos. Os exemplos que serão vistos são simples, mas possuem todos os elementos necessários para a implementação do padrão. Eles foram desenvolvidos em C#, utilizando a ferramenta Visual Studio 2010.
Conceitualizando o padrão Strategy
O padrão Strategy consiste em se possuir vários algoritmos, ou estratégias, encapsulados em uma classe chamada Context. O programa cliente pode selecionar um desses diferentes algoritmos, de acordo com a situação. Em alguns casos, a classe Context pode até decidir qual é a melhor no momento. O interessante é que essa escolha é feita em tempo de execução.
Com esse padrão, podemos mais facilmente adicionar novos algoritmos, alterar os já existentes ou estender suas funcionalidades.
A Figura 1 ilustra o diagrama UML que define este padrão. Temos os seguintes elementos:
• Context – é a classe que mantém a informação contextual para que um algoritmo possa funcionar. Serve de mediador entre o cliente e os algoritmos;
• Strategy – é a interface, o contrato que define todas as operações comuns que o algoritmo deve suportar. Perceba que a classe Context possui um atributo deste tipo e que servirá para centralizar todas as chamadas;
• ConcreteStrategy – são os próprios algoritmos. Podemos ter quantas classes desse tipo forem necessárias, cada uma representando um algoritmo diferente.
Figura 1. Diagrama UML para o padrão Strategy
UML é uma linguagem visual para a modelagem de sistemas orientados a objetos. Ela tem origem na compilação do que existia de melhor nas técnicas de notações Booch, OMT e OOSE, cujos autores são Grady Booch, James Rumbaugh e Ivar Jacobson, respectivamente.
Um dos seus principais diagramas é o diagrama de classes, que é utilizado para representar a estrutura e relações das classes. Por essa característica, é o diagrama utilizado para demonstrar e documentar os relacionamentos dos objetos que compõem cada design pattern.
Em resumo, este padrão pode ser aplicado quando você possuir uma aplicação onde existem diversas formas de se fazer uma coisa, onde a decisão de quando utilizar um algoritmo ou outro é feita de acordo com a necessidade.
Alternando algoritmos - Exemplo
Para poder demonstrar a utilidade do padrão Strategy, vamos supor uma aplicação que faça o cálculo de área e perímetro de figuras geométricas. Este tipo de rotina teria utilidade, por exemplo, em sistemas para cálculo de gastos de materiais em uma fábrica de embalagens.
Antes do exemplo, vamos relembrar alguns conceitos de geometria que são utilizados aqui. O cálculo do perímetro de um retângulo é a soma de todos os seus lados; já sua área é sua altura multiplicada pela largura. Para círculos, o perímetro é obtido multiplicando-se o raio pela constante (pi); sua área é a multiplicação dessa mesma constante pelo raio elevado ao quadrado. Aqui, estamos considerando o raio igual à metade da altura do objeto, para simplificar o exemplo.
A Listagem 1 contém o código da aplicação que faz o consumo de um componente responsável pelos cálculos. Este componente, chamado CalculadoraGeometrica, possui dois métodos: CalcularArea e CalcularPerimetro. Coincidentemente, os dois possuem a mesma assinatura. Recebem três parâmetros, que são:
1. Tipo da figura que vamos efetuar o cálculo, sendo ele um retângulo, círculo etc. Temos os valores possíveis para esses parâmetro definidos no enumerador TipoFigura;
2. A altura da figura;
3. A largura da figura.
Nesse código, percorremos um array de objetos contendo as informações das figuras e efetuando uma chamada ao componente para os cálculos de áreas e perímetros, sendo que essas informações são exibidas na tela.
Listagem 1. Aplicação que consome o componente de cálculo de áreas e perímetros
using System;
using ExemploStrategy.SemPadraoAlterado;
namespace ExemploStrategy
{
class Program
{
static void Main(string[] args)
{
var figuras = new[] {
new { Tipo = TipoFigura.Retangulo, Altura = 10.0, Largura = 50.0 }
, new { Tipo = TipoFigura.Retangulo, Altura = 10.0, Largura = 40.0 }
, new { Tipo = TipoFigura.Circulo, Altura = 30.0, Largura = 30.0 }
, new { Tipo = TipoFigura.Retangulo, Altura = 10.0, Largura = 20.0 }
, new { Tipo = TipoFigura.Retangulo, Altura = 10.0, Largura = 10.0 }
, new { Tipo = TipoFigura.Circulo, Altura = 20.0, Largura = 20.0 }
, new { Tipo = TipoFigura.Retangulo, Altura = 10.0, Largura = 20.0 }
};
CalculadoraGeometrica calculadora = new CalculadoraGeometrica();
Console.WriteLine(" Figura | Altura | Largura | Area | Perimetro");
Console.WriteLine(" --------------------------------------------------------------");
foreach (var figura in figuras)
{
Console.WriteLine(" {0, -20}| {1:00.00} | {2:00.00} | {3:000.00} | {4:000.00}"
, figura.Tipo
, figura.Altura
, figura.Largura
, calculadora.CalcularArea(figura.Tipo, figura.Altura, figura.Largura)
, calculadora.CalcularPerimetro(figura.Tipo, figura.Altura, figura.Largura));
}
Console.ReadLine();
}
}
public enum TipoFigura : int
{
Retangulo = 1,
Circulo = 2
}
}
...