Do que trata o artigo

Neste artigo, veremos dois dos 23 padrões definidos no livro “Design Patterns Elements of Reusable Object-Oriented Software”: o padrão Composite e o padrão Chain of Responsibility. Explicaremos como eles podem ser utilizados visando melhorar a forma com que nossas aplicações são desenvolvidas.

Para que serve

Reuso de soluções e documentações bem conhecidas fazem parte da principal meta em se utilizar design patterns. Neste artigo, teremos foco em padrões que nos ajudem a desenhar nossas aplicações de forma que sejam mais fáceis de manter e permitir extensão.

Em que situação o tema é útil

Composite pode ser utilizado para se ter uma forma única de tratar diferentes objetos, sendo que eles podem ser primitivos ou compostos. Já o Chain of Responsibility define uma cadeia onde uma requisição é trafegada, permitindo que ela seja analisada e processada pelo respectivo elemento responsável.

Resumo do DevMan

O artigo foca na apresentação e demonstração de uso dos padrões Composite e Chain of Responsibility, definidos pelo GoF (“Gang of Four”). Serão mostrados os seus conceitos e aplicações, além de três exemplos de código simples, porém práticos, para auxiliar o leitor no entendimento.

A importância em se ter padrões

O que seria dos desenvolvedores .NET se não pudessem reutilizar componentes desse framework nas aplicações? Se, para mostrar uma mensagem na tela, não fosse possível contar com uma classe como System.Windows.Forms.MessageBox, e fosse necessário recriar o código que o faça em cada novo sistema? Teríamos que ter, no mínimo, muitas horas para gastar. De uma maneira similar, o que seria de nós se não tivéssemos padrões a serem seguidos? Se para cada nova aplicação, tivéssemos que repensar qual a estrutura a ser usada na hora de programar para deixar o código mais fácil de entender, modificar e estender? É neste momento que design patterns fazem sentido, pois eles provêm soluções estruturadas e bem testadas na vida real, com uma documentação de domínio público. Com isso, poupamos nosso esforço em muitas atividades, permitindo que nosso tempo seja mais bem utilizado dentro dos nossos projetos (que convenhamos, eles sempre têm uma expectativa de entrega do cliente menor que o prazo que gostaríamos de ter).

Muitos padrões de programação foram catalogados no livro “Design Patterns: Elements of Reusable Object-Oriented Software”, escrito por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides (“Gang of Four”), lançado em 1995. São dessa obra que foram retirados os padrões que aqui serão apresentados, mas ela não é a única fonte desse tipo de informação. “Patterns of Enterprise Application Architecture” de Martin Fowler e “Applying UML and Patterns - An Introduction to Object-Oriented Analysis and Design and Iterative Development” de Craig Larman são outras obras que abordam o assunto.

Conceitualizando o padrão Composite

Podemos dizer que o Composite é um padrão “justo”, pois o seu objetivo é tratar diferentes objetos da mesma maneira, sejam eles classes distintas ou mesmo agrupamentos desses próprios objetos, desde que estejam organizados em uma estrutura de árvore (hierárquica).

Imagine um sistema de folha de pagamento, onde é necessário calcular aumento de salários para os funcionários de uma empresa. Podemos ter um aumento de salário para uma única pessoa, ou um aumento de salário para toda uma categoria. Note que neste caso cada funcionário representaria um objeto individual, e a categoria representaria um agrupamento, uma composição desses objetos individuais.

Outro exemplo que podemos dar, e que será utilizado na demonstração de código para este pattern, é uma estrutura de diretórios e arquivos. Esta é a situação que está descrita na Figura 1 onde podemos ver os relacionamentos entre os objetos. Cada arquivo é um objeto individual, e cada diretório é uma composição de arquivos e até mesmo outros diretórios.

Figura 1. Estrutura em árvore - diretórios e arquivos

Mas até aqui não comentamos uma das principais características deste padrão: estes elementos devem ser acessados de uma mesma forma, tanto faz se é um objeto individual ou se é uma composição. Para chegar neste objetivo, todos eles devem compartilhar uma mesma interface ou herdar de uma mesma classe abstrata, de forma que todos possuam os mesmos métodos. Ainda no exemplo de uma hierarquia de pastas e arquivos, cada objeto pode ter um método para retornar o espaço em disco que ele ocupa. É claro que a implementação desse método será diferente entre objetos que representam um diretório e os que representam arquivos. Como um diretório em si não ocupa espaço em disco, este método de cálculo pode ser feito a partir do resultado da chamada deste mesmo método executada em cada arquivo contido na pasta.

Vejamos a Figura 2, que é a representação deste padrão. Temos uma classe abstrata comum, Component. Note que o Composite possui, como atributo, uma lista de objetos do tipo Component. Desta forma é que podemos manter a estrutura hierárquica contida neste pattern. Além disso, o cliente acessa apenas a classe abstrata / interface Component, para que todos os objetos possam ser tratados de forma uniforme.

Outra questão importante é que a classe Composite redireciona as chamadas das operações definidas na interface / classe abstrata que ela recebe para seus filhos. Isso significa que, ao se chamar o método Operation de Composite, serão chamados também os mesmos métodos Operation de todos os filhos que a instância dessa classe possuir.

Figura 2. Definição dopattern Composite

Em resumo:

• Component – é uma classe abstrata ou interface que declara os métodos comuns que serão utilizados pelo cliente para acessar os objetos de uma maneira uniforme;

• Leaf – dentro da estrutura em árvore que é criada por este padrão, esta classe representa uma “folha”, ou seja, um objeto individual que não possui filhos dentro da hierarquia;

• Composite – é o objeto que agrupa os demais objetos que realizam a classe / interface, e que propaga a execução das suas operações para as respectivas operações dos seus filhos. Antes e após essa propagação de métodos, é permitido que sejam efetuados outros processamentos adicionais.

Recuperando espaço consumido – exemplo

Nosso exemplo para demonstrar o padrão Composite será uma aplicação que retorna a quantidade de espaço em disco consumido pelos arquivos que estão guardados em um diretório do Windows. Note que esse cálculo não deve apenas considerar o tamanho dos arquivos que estão diretamente dentro da pasta, mas também o tamanho dos arquivos que estão dentro de suas subpastas e assim por diante.

O primeiro código que temos é a Listagem 1, que mostra a interface que define todas as operações que serão utilizadas para manipular, de modo único, as classes que representam diretórios e arquivos.

Listagem 1. Interface IRecurso


using System;
   
  namespace Composite
  {
      public interface IRecurso
      {
          string Caminho { get; set; }
          long ObterTamanho();
      }
  }

Esta interface possui duas operações. Uma é a propriedade Caminho, que será utilizada para armazenar o caminho do diretório ou pasta (exemplo: C:\temp\ ou C:\temp\arquivo.txt). O outro método, ObterTamanho, será responsável por retornar o tamanho em bytes que é ocupado no disco, seja por um arquivo ou por um diretório inteiro.

Note que neste exemplo eu não estou utilizando uma classe abstrata, mas sim uma interface. É uma preferência pessoal, pois poderíamos utilizar a classe abstrata da maneira que está descrito no modelo de classes que vimos na Figura 2. Inclusive, se trabalharmos com essa herança, nós ganhamos a possibilidade de definir comportamentos padrão para os métodos, ou seja, só precisaríamos efetuar a sobrecarga dos métodos que realmente nos interessam. Mas como disse, a utilização de uma interface é uma preferência minha.

Desta vez, nesta demonstração, eu quero mudar um pouco a ordem como apresento cada pedaço do código de exemplo, colocando o trecho final agora no meio da explicação. Com isso, eu quero mostrar que o cliente faz acesso aos métodos definidos na interface de modo que para ele tanto faz se estamos lidando com um objeto que representa um diretório ou um arquivo.

...

Quer ler esse conteúdo completo? Tenha acesso completo