Tecnologia de objetos
Autores: Lisiane Volpi Sipert - GPT
Vidal Martins - GPT
DESIGN PATTERNS - COMPOSITE
Abstract
Software engineers are always looking for mechanisms that improve their productivity and the quality of their work. Design pattern is one of these mechanisms, which make possible the reuse of successful experiences at Design phase of a software life cycle. This article contains an overview about Design patterns and describes one of them, called Composite. Here are the informations that you can obtain reading this article: What is a Design pattern? Design patterns in Smalltalk MVC; Design patterns classification; How Design patterns solve Design problems? How to select a Design pattern? How to use a Design pattern? A case study – Composite.
Palavras-Chave
Componente; Composite; Design Pattern; Implementação; Interface; Orientação a Objetos; Recursividade; Reuso;
1. Introdução.
Se você perguntasse para Engenheiros de Software por que a tecnologia de objetos é mais recomendada que as suas concorrentes, provavelmente a resposta mais freqüente seria "reuso". Na realidade, a reutilização de Componentes de software está diretamente relacionada a duas questões que afetam diretamente os projetistas e os usuários dos produtos: qualidade e produtividade.
É mais rápido montar um software a partir de Componentes predefinidos do que escrever todo o código desde o início. Além disso, o esforço necessário para se garantir confiabilidade e ergonomia ao produto, durante a fase de testes, é consideravelmente menor quando o trabalho se fundamenta em elementos pré-fabricados. Por esse motivo, todos concordam que, atualmente, o ideal é desenvolver sistemas orientados a objetos. O objeto é uma unidade de software auto-gerenciável e com grande potencial para reuso. Note que usamos a expressão potencial para reuso, ou seja, o simples fato de usar tecnologia de objetos não garante isso. O reuso tem que ser planejado.
É difícil, ou talvez impossível, construir um produto totalmente reutilizável logo na primeira tentativa. Isto significa que a experiência dos projetistas é fundamental para se desenvolver bons Componentes de software. Profissionais experientes costumam reutilizar soluções que já funcionaram no passado, ao invés de resolver cada problema partindo do zero. A conseqüência importante deste fato é a constatação de que determinados padrões de classes e de comunicações entre objetos se repetem em diversos sistemas. Com base nesse cenário, podemos afirmar que os projetistas sabem reaproveitar experiências de sucesso, porém falham no momento em que não registram essas experiências.
O propósito do livro Design Patterns - Elements of Reusable Object-Oriented Software, escrito por . Erich GAMMA, Richard HELM, Ralph JOHNSON e John VLISSIDES, no qual se baseia este artigo, é o de registrar experiências bem sucedidas com projetos de software orientados a objetos, em forma de Design patterns. Os autores do livro acreditam (e nós também) que outros projetistas podem reutilizar essas estratégias de sucesso nos seus trabalhos, em virtude da qualidade da sua documentação.
O objetivo deste artigo é dar uma visão geral sobre Design patterns. Para isso, o estruturamos nas seguintes seções: O que é um Design pattern? Design patterns em Smalltalk MVC; Classificação dos Design patterns; Como os Design patterns resolvem problemas de projeto? Como selecionar um Design pattern? Como usar um Design pattern? Um estudo de caso – Composite.
2. O que é um Design pattern?
Segundo Christopher Alexander, cada pattern descreve um problema que ocorre várias vezes em nosso ambiente e o núcleo de uma solução para esse problema, de forma que se possa reutilizá-la diversas vezes. Em geral, um pattern tem quatro elementos essenciais:
- Nome: é uma referência que pode ser usada para descrever um problema de projeto, suas soluções e conseqüências, através de uma ou duas palavras. A nomeação de um pattern aumenta o nosso vocabulário de projeto e nos permite trabalhar com um alto nível de abstração, pois podemos descrever uma solução inteira utilizando apenas uma expressão, ao invés de uma lista de objetos e seus relacionamentos.
- Problema: descreve quando aplicar o pattern. Explica o problema e seu contexto. Algumas vezes, o problema incluirá uma lista de condições que devem ser satisfeitas antes de aplicar o pattern.
- Solução: descreve os elementos que compõem o projeto, seus relacionamentos, responsabilidades e colaborações. A solução não descreve uma implementação ou projeto concreto em particular, porque um pattern é como um molde que pode ser aplicado em várias situações diferentes.
- Conseqüências: são os resultados, as vantagens e as desvantagens da aplicação do pattern. Geralmente envolvem questões de espaço, tempo, flexibilidade, extensiblidade, portabilidade, linguagem e implementação.
3. Design Patterns em Smalltalk MVC.
Descrevemos Design pattern na seção anterior deste artigo, mas talvez fique mais claro o significado do termo pattern se analisarmos um exemplo real. Então, vamos ver como são construídas as interfaces de usuário em Smalltalk-80. Basicamente, são utilizadas três classes: Modelo, Visão e Controlador (MVC). Modelo é o objeto da aplicação, Visão é a sua apresentação na tela e Controlador define a maneira que a interface reage às intervenções do usuário.
Uma Visão deve assegurar que sua aparência reflita o estado do Modelo. Isto pode ser feito de duas formas: sempre que houver alteração nos dados do Modelo, este notifica todas as Visões que dependem dele; ou, a própria Visão se responsabiliza por obter o estado do Modelo e atualizar-se.
A maneira como uma Visão responde às intervenções do usuário pode ser alterada sem mudar o visual da interface, pois a arquitetura MVC encapsula o mecanismo de resposta no objeto Controlador. Isto é, se desejarmos mudar o comportamento de uma interface sem mexer no seu visual, basta trocarmos o Controlador dessa interface.
Quais são as informações essenciais presentes neste exemplo? O problema que está sendo tratado é a interface do usuário em Smalltalk-80; a solução é a arquitetura MVC (Modelo, Visão, Controlador); os patterns são os modelos resultantes da abstração dessa solução, que podem ser reutilizados para resolver outros problemas da mesma natureza. Neste caso específico, podemos identificar exemplos dos seguintes Design patterns:
- Observer: separação de objetos de tal forma que a mudança em um deles afete qualquer número de instâncias dos outros, com a condição de que o objeto alterado não conheça detalhes sobre os demais objetos (relacionamento entre Visões e Modelos).
- Strategy: é um objeto que representa um algoritmo. É útil quando desejamos alterar o algoritmo que será executado estática ou dinamicamente, quando temos várias opções de algoritmos, ou quando precisamos encapsular estruturas de dados complexas (relacionamento entre Visão e Controlador).
O livro Design Patterns: Elements of Reusable Object-Oriented Software, descreve 24 patterns que podem ser reutilizados em qualquer projeto de software orientado a objetos. Nunca é demais lembrar que se trata de reutilização de solução proposta para determinado tipo de problema, não é reutilização de código, pois os patterns não são Componentes de software implementados.
Os próprios autores do livro classificam os patterns, com o objetivo de facilitar seu aprendizado e de direcionar os esforços de busca do pattern apropriado para solucionar determinado problema. A próxima seção descreve essa classificação.
4. Classificação dos Design Patterns.
Propósito |
||||
Criação | Estrutural | Comportamental | ||
Escopo | Classe | Factory Method | Adapter (classe) | Interpreter
Template Method |
Objeto | Abstract Factory
Builder Prototype Singleton |
Adapter (objeto)
Bridge Composite Decorator Facade Flyweight Proxy |
Chain of Responsibility
Command Iterator Mediator Memento Observer State Strategy Visitor |
Os patterns com propósito de criação preocupam-se com o processo de instanciação dos objetos. Os patterns estruturais tratam das composições de classes e objetos. E os patterns comportamentais caracterizam a maneira pela qual as classes e os objetos interagem e distribuem responsabilidades.
Os patterns com escopo de classe tratam relacionamentos entre classes e subclasses. Estes relacionamentos são estabelecidos através de herança, o que significa que eles são estáticos, ou seja, são definidos em tempo de compilação. Os patterns com escopo de objeto tratam relacionamentos entre objetos, os quais podem ser mudados em tempo de execução e são mais dinâmicos.
Fazendo uma referência cruzada entre esses conceitos, chegamos às seguintes conclusões
- Os patterns com propósito de criação e escopo de classe delegam algumas partes da criação dos objetos para subclasses, enquanto os patterns com propósito de criação e escopo de objeto delegam algumas partes da criação dos objetos para outros objetos.
- Os patterns estruturais com escopo de classe usam herança para compor classes, enquanto os pattenrs estruturais com escopo de objeto descrevem maneiras de "juntar" objetos.
- Os patterns comportamentais com escopo de classe usam herança para descrever algoritmos e fluxos de controle, já os patterns comportamentais com escopo de objeto descrevem como um grupo de objetos coopera para realizar uma tarefa que nenhum deles seria capaz de suportar sozinho.
5. Como os Design patterns resolvem problemas de projeto?
Alguns princípios básicos devem estar muito claros para aqueles que pretendem usar Design patterns de forma apropriada. Esta seção do nosso artigo trata desse assunto.
Em primeiro lugar, é importante entender a diferença entre a classe de um objeto e seu tipo. Uma classe define como um objeto é implementado, enquanto um tipo define apenas a interface de um objeto, ou seja, qual é o conjunto de requisições que ele é capaz de responder.
Também é importante entender a diferença entre herança de classes e herança de interface (ou subtipo). Herança de classes define a implementação de um objeto com base na implementação de outro objeto, isto é, a herança de classes é um mecanismo para compartilhamento de código e representação. Por outro lado, herança de interface (ou subtipo) descreve quando um objeto pode ser usado no lugar de outro.
Um dos princípios de projeto orientado a objetos reutilizável é: programe orientado à interface, não à implementação. Uma conseqüência prática deste princípio é a recomendação de não declararmos variáveis como instâncias de classes concretas. Ao invés disso, devemos nos comprometer com o uso de interfaces definidas por classes abstratas. Devemos usar patterns de criação para instanciar classes concretas. Isto significa que a associação entre uma interface e a sua implementação deve ocorrer de forma transparente, durante a instanciação.
Outro princípio é: use preferencialmente composição de objetos em vez de herança de classes. Algumas desvantagens da herança são as seguintes: nós não podemos mudar as implementações herdadas das superclasses em tempo de execução; a herança expõe uma subclasse aos detalhes de implementação da sua classe pai e isto quebra o encapsulamento; a implementação de uma subclasse torna-se tão dependente da implementação da sua superclasse que qualquer mudança nesta forçará uma mudança naquela.
Idealmente, nós deveríamos combinar Componentes já existentes para obtermos toda a funcionalidade necessária aos nossos projetos, mas infelizmente isso raramente acontece, pois os Componentes disponíveis, na prática, não são sofisticados o suficiente para atingir esse objetivo. A reutilização através de herança torna mais fácil a criação de novos Componentes a partir de outros já existentes. Por essa razão, embora a recomendação seja priorizar o uso de composição, herança e composição são recursos complementares.
Existem outros princípios que poderiam ser discutidos neste momento, referentes à delegação, tipos parametrizados, associação versus agregação, manutenibilidade, etc., mas deixaremos esses temas para outra oportunidade.
6. Como selecionar Design patterns?
Escolher um Design pattern para resolver o problema é uma tarefa pesada, principalmente para quem não está familiarizado com o catálogo. Execute as atividades abaixo para encontrar o Design pattern certo para o seu problema:
- Analise como cada Design pattern resolve os problemas de projeto. Determine a granularidade do objeto, especifique as interfaces dos objetos.
- Examine as seções de intenção de cada pattern.
- Estude como inter-relacionar patterns. Verifique o gráfico de relacionamento entre os Design patterns.
- Estude patterns de propósitos parecidos.
- Examine uma causa de reprojeto.
- Considere o que deveria ser variável em seu projeto.
7. Como usar Design patterns?
Uma abordagem passo a passo para aplicar um Design pattern:
1. Leia o pattern uma vez, prestando atenção nas seções de Aplicabilidade e Conseqüência, para ter uma visão geral do mesmo.
2. Volte e estude as seções de Estrutura, Participantes e Colaboração.
3. Procure a seção de Exemplo de código para ver um exemplo concreto do pattern em código.
4. Escolha nomes para participantes do pattern que sejam significativos no contexto da aplicação.
5. Defina as classes. Identifique as classes existentes em sua aplicação que o pattern afetará e modifique-as de acordo.
6. Defina nomes específicos da aplicação para operações no pattern.
7. Implemente as operações para cumprir as responsabilidades e colaborações no pattern.
Design patterns não deveriam ser aplicados indiscriminadamente. Eles tornam os projetos flexíveis mas podem complicar um projeto e acrescentar um custo de performance. Um Design pattern deveria ser aplicado somente quando a flexibilidade é realmente necessária. As seções de Conseqüências são muito úteis para avaliar os benefícios e as desvantagens do pattern.
8. Composite.
O Design pattern chamado Composite envolve a cooperação de 3 classes: Componente (Component), Folha (Leaf) e Composição (Composite). Componente é uma classe base da qual as classes Folha e Composição são derivadas. Folha é um objeto simples, mas Composição inclui uma coleção de Componentes. Este é um meio elegante de representar hierarquias genéricas, onde qualquer objeto pode incluir uma coleção de outros objetos. Operações como adicionar ou remover são geralmente métodos virtuais associados a ambos, Componente e Composição.
8.1 Intenção.
Objetos compostos dentro de estruturas de árvores representam hierarquias parte-todo. Composite permite aos clientes tratarem composições de objetos e objetos individuais uniformemente.
8.2 Motivação.
Aplicações gráficas como editores de desenho e sistemas de captura de esquema deixam os usuários construírem diagramas complexos a partir de Componentes simples. O usuário pode agrupar Componentes para formar Componentes maiores, que, por sua vez, podem ser agrupados para formar Componentes maiores ainda. O pattern Composite descreve como usar composição recursiva, de modo que os clientes não tenham que fazer distinção entre componentes simples e composições.
A chave para o pattern Composite é uma classe abstrata que representa ambos, contenedores e primitivos. Para sistemas gráficos esta classe é Graphic. Ela declara operações como Draw que são específicas para objetos gráficos. Também declara operações que todos os objetos Composite compartilham, tal como operações de acesso e gerenciamento nos seus filhos.
As subclasses Line, Retângulo e Text (conforme a figura anterior) definem objetos gráficos primitivos. Estas classes implementam Draw para desenhar linhas, retângulos e textos, respectivamente. Desde que gráficos primitivos não tenham gráficos filhos, nenhuma destas subclasses implementarão operações relacionadas com filhos.
A classe Picture define uma agregação de objetos gráficos. Picture implementa Draw para chamar Draw em seus filhos, e ela implementa operações relacionadas com filhos do modo devido. Uma vez que a interface Picture se ajusta à interface Graphic, objetos Picture podem compor outros Pictures recursivamente.
O seguinte diagrama mostra uma estrutura de objetos Composite, típica para objetos gráficos compostos recursivamente:
8.3 Aplicação.
Use o pattern Composite quando:
- você quiser representar hierarquias parte-todo de objetos;
- você quiser que os clientes sejam capazes de ignorar as diferenças entre composição de objetos e objetos individuais. Clientes tratarão todos os objetos uniformemente na estrutura Composite.
8.4 Estrutura.
Uma estrutura de objeto Composite típica deve parecer como esta:
8.5 Participantes.
- Componente (Component-Graphic):
- declara a interface para objetos na composição;
- implementa comportamento default para interface comum a todas as classes, como apropriado;
- declara uma interface para acessar ou gerenciar seus Componentes filhos;
- (opcional) define uma interface para acessar o pai de um Componente na estrutura recursiva, e implementa se apropriado.
- Folha (Leaf-Retângulo, Line, Text, etc):
- representa objetos folhas na composição. Uma folha não tem filhos;
- define comportamento para objetos primitivos na composição;
- Composição (Composite-Picture):
- define comportamento para Componentes que têm filhos;
- armazena Componentes filhos;
- implementa operações relacionadas com filhos na interface do Componente.
- Cliente (Client):
- manipula objetos na composição através da interface Componente.
8.6 Colaboração.
Clientes usam a interface da classe Componente para interagir com objetos na estrutura Composite. Se o recipiente é uma Folha, então a requisição é manipulada diretamente. Se o recipiente é uma composição, então ele geralmente passa adiante requisições para seus Componentes filhos e, possivelmente executa operações adicionais.
8.7 Consequência:
- define a consistência das hierarquias de classes de objetos primitivos e objetos composição. Objetos primitivos podem ser compostos dentro de um objeto mais complexo, que podem ser compostos, e assim recursivamente. Sempre que códigos de clientes esperam um objeto primitivo, podem também obter um objeto composto;
- faz o cliente simples. Clientes podem tratar estruturas compostas e objetos individuais uniformemente. Clientes normalmente não sabem (e não deveriam se preocupar) se eles estão tratando com uma folha ou uma composição;
- torna mais fácil adicionar novos tipos de Componentes. Novas composições ou subclasses Folhas trabalham automaticamente com estruturas existentes e código do cliente. Clientes não têm que ser mudados para novas classes de Componentes;
- algumas vezes você quer que uma composição tenha apenas certos Componentes. Você terá de usar uma checagem em tempo de execução para isto.
8.8 Implementação.
Há muitas questões a serem consideradas quando se implementa o pattern Composite:
1. Explicitar referências ao pai. Manter referências dos Componentes filhos para seus pais pode simplificar a navegação e o gerenciamento de uma estrutura Composite. A referência do pai simplifica a promoção da estrutura e a exclusão do Componente. O lugar geral para definir a referência dos pais é na classe Component. Classes Composite e Leaf podem herdar a referência e as operações que as gerenciam.
2. Compartilhar Componentes. É freqüente o uso de Componentes compartilhados, por exemplo, para reduzir requisições de armazenagem. Mas quando um Componente não pode ter mais que um pai, compartilhar Componentes torna-se difícil. Uma solução possível é os filhos armazenarem múltiplos pais. Mas isto pode provocar ambigüidades como uma propagação de requisições na estrutura.
3. Maximizar a interface do Componente. Um dos objetivos do pattern Composite é fazer clientes ignorarem se eles estão usando especificamente classes Composite ou Leaf. Para atender este objetivo, a classe Component deveria definir tantas operações comuns para classes Composite e Leaf quanto possível. A classe Component geralmente provê uma implementação default para estas operações, e subclasses Composite e Leaf as sobrescreverão. Por exemplo, a interface para acessar filhos é uma parte fundamental de uma classe Composite mas não é necessária para classe Leaf. Mas se nós visualizarmos uma Folha como um Componente que nunca tem filhos, então nós podemos definir uma operação default para acessar filhos na classe Component que nunca retorna nenhum filho.
4. Declarando as operações de gerenciamento dos filhos. Embora a classe Composite implemente operações Add e Remove para gerenciamento dos filhos, uma questão importante no pattern Composite é que as classes declarem estas operações na hierarquia de classes Composite. Nós deveríamos declarar estas operações no Component e fazê-las significativas para classes Folhas, ou deveríamos declará-las e defini-las apenas no Composite e suas subclasses?
- A decisão envolve um balanceamento entre segurança e transparência:
* Definindo a interface de gerenciamento do filho na raiz da hierarquia da classe dá a você transparência, porque você pode tratar todos os Componentes uniformemente. Isto custa a segurança, de qualquer modo, pois clientes devem tentar fazer coisas sem sentido como adicionar e remover objetos de folhas.
* Definindo gerenciamento do filho na classe Composite dá a você segurança, porque qualquer tentativa de adicionar ou remover objetos das folhas será detida em tempo de compilação em uma linguagem tipada estaticamente como C++. Mas você perde transparência, porque folhas e composições têm interfaces diferentes.
- Nós temos enfatizado transparência sobre segurança neste pattern. Se você optar por segurança, então às vezes você perderá informação de tipo e terá que converter um Component para um Composite.
5. Deveria um Componente implementar uma lista de Componentes? Você pode ser tentado a definir um conjunto de filhos como uma variável de instância na classe Component, onde as operações de acesso e gerenciamento dos filhos estão declaradas. Mas colocar o ponteiro do filho na classe base incorre em uma perda de espaço para todas as folhas. Isto vale a pena somente se há relativamente poucos filhos na estrutura.
6. Ordenação dos filhos. Muitos projetos especificam uma ordenação nos filhos do Composite. No exemplo Gráfico mais recente, ordenação deve refletir ordenação de frente-para-trás.
7. Armazenar para melhorar performance. Se você necessita navegar ou pesquisar composições freqüentemente, a classe Composite pode armazenar informações de pesquisa ou navegação sobre seus filhos. O Composite pode armazenar resultados atuais ou apenas informação que permita um circuito curto de navegação ou pesquisa.
8. Quem deveria excluir Componentes? Em linguagens sem coleta de lixo (garbage collection), geralmente o melhor é responsabilizar o Composite para exclusão dos seus filhos quando destruídos. Uma exceção a esta regra é quando objetos Folha são imutáveis e, portanto, podem ser compartilhados.
9. Qual a melhor estrutura de dados para armazenar Componentes? Composites devem usar uma variedade de estrutura de dados para armazenar seus filhos, incluindo ligações de listas, árvores, vetores e tabelas hash. A escolha da estrutura de dados depende (como sempre) da eficiência. De fato, não é sempre necessário usar uma estrutura de dados genérica para tudo. Algumas vezes Composites têm uma variedade para cada filha, embora isto requeira que cada sub-classe do Composite implemente seus próprios gerenciadores de interface. Um exemplo disso é o pattern Interpreter.
8.9 Exemplo de Código.
Para ilustrar o uso de Composite, nós vamos representar uma situação real que ocorre dentro da nossa empresa.
A CELEPAR tem como clientes, dentre outros, as Secretarias do Estado. Quando estas Secretarias precisam de algum serviço na área de informática, elas requisitam ao coordenador de atendimento correspondente dentro da CELEPAR. Esta requisição é conhecida internamente como RDD (Registro de Demanda). Para atender uma demanda específica a CELEPAR subcontrata as gerências especializadas nos serviços que a demanda requer. Por exemplo, a GPS (Gerência de Projetos de Sistemas) é especializada no desenvolvimento e manutenção de software e a GPT (Gerência de Prospecção Tecnológica) é especializada no estudo e internalização de novas tecnologias; e assim sucessivamente. Logo, o atendimento de uma demanda pode gerar diversas contratações. O serviço contratado pode ser de dois tipos:
- Tarefa: Atividade isolada como, por exemplo, uma palestra sobre uma determinada tecnologia.
- Projeto: Trabalho composto de diversas atividades que podem ser organizadas através de subprojetos. Por exemplo, treinamento numa tecnologia específica pode ser visto como um projeto, pois envolve as seguintes tarefas: divulgação do evento, inscrições dos treinandos, preparação do material didático, preparação do ambiente, execução do treinamento, avaliação do evento, etc. Neste caso, na CELEPAR, a GRH (Gerência de Recursos Humanos) seria contratada para realizar as atividades administrativas referentes ao evento, como inscrição, divulgação, avaliação, etc. Cada área contratada por sua vez pode subcontratar outras áreas ou mesmo serviço de terceiros para resolver a parte que lhe cabe do problema.
A Classe Contrato define uma interface para todos os contratos na hierarquia, independente se ele é um projeto ou uma simples tarefa.
class Contrato{
private:
const char* _name;
protected:
Contrato(const char*);
public:
virtual ~Contrato();
string Contratante;
string Contratado;
string Objetivo;
const char* Name() { return _name;}
virtual Apresente();
virtual ObtemDuracao();
virtual void Add(Contratot*);
virtual void Remove(Contrato*);
virtual Iterator<Contrato*>* CreateIterator();
}
Contrato declara uma operação ObtemDuracao() que retorna a duração da tarefa ou das tarefas do projeto, e a operação Apresente() que mostra na tela as informações relativas às tarefas ou aos projetos. Subclasses implementam estas operações para tipos específicos de contrato, subcontratos, ou tarefas. Contrato também declara uma operação CreateIterator que retorna um Iterator para acessar suas partes. A implementação default para esta operação retorna NullIterator, que interage sobre um conjunto vazio.
Subclasses de Contrato devem incluir classe Folha que representa Tarefas:
class Tarefa : public Contrato {
public:
Tarefa(const char*);
Descricao:string;
DataIni:date;
DataFim:date;
virtual ~Tarefa();
virtual Apresente();
virtual ObtemDuracao();
}
Projeto é a classe base para contratos que contêm outros contratos, ou seja, Projetos que têm outros subprojetos ou tarefas. É também uma subclasse de Contrato.
class Projeto: public Contrato {
private:
List<Contrato*> _contrato;
protected:
Projeto (const char*);
public:
virtual ~Projeto();
virtual Apresente();
virtual ObtemDuracao();
virtual void Add(Contratot*);
virtual void Remove(Contrato*);
virtual Iterator<Contrato*>* CreateIterator();
}
Projeto define as operações para acessar e gerenciar subcontratos. As operações Add e Remove inserem e excluem contratos da lista de subcontratos armazenados no projeto. A operação CreateIterator retorna um operador (especificamente, uma instância de ListIterator) que navega por esta lista.
Uma implementação default para ObtemDuracao deve usar CreateIterator para somar as horas dos subcontratos (é fácil esquecer de excluir o iterator. O pattern Iterator mostra como se prevenir contra esse erro).
Int Projeto::ObtemDuracao() {
Iterator<Contrato*>* i = CreateIterator();
Int total = 0;
For(i->First(); !i-IsDone(); i->Next()) {
Total += i->CurrentItem()->ObtemDuracao();
}
delete i;
return total;
}
Agora nós podemos representar um Treinamento como uma subclasse de Projeto. Treinamento herda as operações de Projeto.
Class Treinamento: public Projeto {
public:
Treinamento(const char*);
virtual ~Treinamento();
virtual Apresente();
virtual ObtemDuracao();
};
8.10 Usos Conhecidos.
Exemplos de pattern Composite podem ser encontrados em quase todos os sistemas orientados a objetos. A original classe View do SmallTalk Model/View/Controller era um Composite, e quase todo kit de ferramentas de interface do usuário ou framework tem seguido estes passos.
O framework do compilador Smalltalk RTL usa o pattern Composite extensivamente. RTLExpression é uma classe Component para analisar árvores. Ela tem subclasses tal como BinaryExpression, que contem objetos RTLExpression filho. Estas classes definem uma estrutura Composite para analisar árvores. RegisterTransfer é a classe Component para uma forma de tarefa estática simples intermediária do programa. Subclasses folhas do RegisterTransfer definem diferentes tarefas estáticas tais como:
- tarefas primitivas que executam uma operação em dois registradores e associam o resultado a um terceiro;
- uma tarefa com um registrador de origem mas nenhum registrador de destino, que indica que o registrador é usado depois que uma rotina retorna; e
- uma tarefa com um registrador destino mas nenhum de origem, que indica que o registrador é associado antes de iniciar a rotina.
Outra subclasse, RegisterTransferSet, é uma classe Composite para tarefas de atribuição que mudam vários registradores de uma vez.
Outro exemplo de pattern ocorre no domínio financeiro, onde um portfolio agrega bens individuais. Você pode suportar agregações complexas de bens implementando um portfolio como uma Composição que se molda à interface de um bem individual.
O pattern Command descreve como objetos Command podem ser compostos e seqüenciados com uma classe Composite MacroCommand.
8.11 Patterns Relacionados.
Freqüentemente a ligação dos Componentes pais é usada para uma cadeia de responsabilidade (Chain of Responsibility).
Decorator é freqüentemente usado com Composite. Quando decoradores e composições são usados juntos, eles geralmente terão uma classe pai comum. Assim sendo, decoradores terão que suportar a interface do Component com operações como Add, Remove e GetChild.
Flyweight permite você compartilhar Componentes, mas eles não podem fazer referência mais longe que seus pais.
Iterator pode ser usado para navegar entre composições.
Visitor localiza operações e comportamentos que, caso contrário, seriam distribuídos através de classes Folha e Composição.
9. Conclusão.
Hoje a evolução do hardware é muito mais rápida do que a do software. Uma das razões para isso é o reuso de Componentes. A criação de um novo equipamento envolve a reutilização de Componentes já existentes e o desenvolvimento de alguns elementos adicionais, responsáveis por implementar as funcionalidades que ainda não estão disponíveis.
A diferença entre patterns e Componentes de software é que estes propciam reuso de código, enquanto os patterns propciam reutilização de soluções bem sucedidas para problemas que se repetem. Isto significa que os Componentes aumentam a produtividade na fase de construção do software, já os patterns geram esse efeito na fase de Design. Portanto, esses dois recursos são complementares e altamente recomendáveis.
Como foi visto, existem muitos detalhes na hora de pensar em estruturas recursivas. Projetos que tenham este tipo de estrutura tornam-se muito complexos. Pattern é uma solução que simplifica a análise porque provê uma representação genérica de hierarquias que solucionam problemas dessa natureza.
Empresas que utilizam a filosofia de Design patterns para projetar software reduzem a distância existente entre projetistas iniciantes e experientes. A criação e utilização de patterns permite perpetuar as experiências de sucesso, bem como reduzir o tempo e o esforço necessários para a formação de novos engenheiros de software.
Referências Bibliográficas:
GAMMA, Erich; HELM, Richard; JOHNSON, Ralph, VLISSIDES, John. Design Patterns - elements of reusable object-oriented software. Addison-Wesley : Massachusetts, 1995.
CIVELLO, F. Roles for Composite objects in object oriented analysis and Design. Proceedings OOPSLA’93 : ACM, 1993.
HUANG, John Q. Thesis: a tool that assists programmer to apply design patterns. Canada : McMaster University, 1996. Master degree in Computer Science.
SANE, Aamod; CAMPBELL, Roy. Composite messages: a structural pattern for communication between Components. University of Illinois, Urbana-Champaign.