Notas de aula de Engenharia de Software                              (C) Jair C Leite, 2000 
Anterior | Índice | Próximo

7 Design da Arquitetura de Componentes de Software

Cada função da aplicação que teve o seu comportamento descrito através modelos conceituais deve ser agora descrita em termos de funções, classes, estruturas de dados, etc., chamados de componentes de software. Estes componentes que implementam cada função interagem entre si e com os componentes de outras funções da aplicação. Esta estrutura de componentes interconectados entre si que formam o software recebe o nome de arquitetura de componentes de software, ou simplesmente arquitetura de software.

Neste capítulo veremos como o modelo conceitual da aplicação pode ser implementado em termos de componentes de software e como estruturar estes componentes de modo a definir a arquitetura do software.

O que será visto neste capítulo:

Os conceitos teóricos apresentados nas diversas seções deste capítulo serão ilustrados a partir de exemplos que apresentam arquiteturas diferentes para uma mesma função da aplicação. Utilizaremos a função da aplicação Consulta Livro já introduzida em exemplos do capítulo anterior. Os exemplos estão descritos em páginas independentes para que possamos ter uma visão global e fazer referências diretas através de links a partir de cada seção. Estaremos sempre sugerindo uma visita às páginas do exemplo.

7.1 Arquitetura de Software

A arquitetura de software é uma especificação abstrata do funcionamento do software em termos de componentes que estão interconectados entre si. Ela permite especificar, visualizar e documentar a estrutura e o funcionamento do software independente da linguagem de programação na qual ele será implementado. Isto é possível se considerarmos que o software pode ser composto por componentes abstratos - independentes da linguagem - que juntos formam um software completo que satisfaz os requisitos especificados (a funcionalidade). Estes componentes estão interligados ou interconectados de maneira a interagir e cooperar entre si.

Alguns autores utilizam o termo configuração  para se referir à maneira como os componentes estão interconectados. Estrutura e topologia são termos utilizados no mesmo sentido. Para diferenciar a programação do design da arquitetura de software pode-se utilizar, respectivamente, os termos programação-em-ponto-pequeno (programming-in-the-small) e programação-em-ponto-grande (programming-in-the-large).

O conceito de arquitetura de software começou a surgir desde que foram desenvolvidas as primeiras técnicas para o particionamento de programas. As técnicas de modularização e decomposição funcional, por exemplo, introduzem os conceitos de funções, procedimentos, módulos e classes  que permitem particionar um programa em unidades menores. Estes pedaços, ou componentes, interagem entre si através de chamadas de função ou troca de mensagens, por exemplo, para que o software funcione corretamente.

O particionamento do software em componentes oferece vários benefícios. 

Os conceitos e a tecnologia de orientação a objetos consolidaram o conceito de componentes e, conseqüentemente, o de arquitetura de software. Nos anos 90, a arquitetura de software passou a ser um importante tópico de pesquisa. Atualmente, ela faz parte do processo de desenvolvimento de software e dos programas dos cursos de engenharia de software.

A arquitetura não é descrita por um único diagrama. Diversos diagramas descrevem a arquitetura proporcionando diferentes visões do software. Estas visões podem ser estáticas ou dinâmicas, físicas ou lógicas. Os exemplos que apresentamos neste capítulo apresentam diversos diagramas que descrevem a arquitetura do software.

A arquitetura pode descrever tanto a estrutura lógica do funcionamento do software quanto a arquitetura física de componentes físicos que formam o software. A arquitetura lógica descreve o funcionamento lógico do software em termos de funções, variáveis e classes. A arquitetura física descreve o conjunto de arquivos fontes, arquivos de dados, bibliotecas, executáveis e outros que compõem fisicamente o software. A seguir, veremos como os diversos tipos de componentes formam o software.

7.2 Componentes de Software

O termo componente de software está bastante popular atualmente. Diversos autores e desenvolvedores têm usado o termo com propósitos diferentes. Alguns autores utilizam o termo componente para se referir a pedaços do código fonte, como funções, estruturas de dados e classes. Outros chamam de componentes instâncias de classes (objetos) de um programa que podem ser utilizadas por outras instâncias. Há ainda a denominação de componentes para as bibliotecas de funções e bibliotecas de ligação dinâmica.

Para entendermos melhor os diversos tipos de componentes utilizados em engenharia de software vamos identificar alguns critérios de classificação. Na nossa classificação utilizaremos os critérios de componentes lógicos (ou funcionais) e físicos,  de tempo-de-desenvolvimento e de tempo-de-execução.

O componente físico é aquele existe para o sistema operacional e para outras ferramentas do sistema, normalmente na forma de arquivos. Eles podem ser armazenados, transferidos de uma lugar para outro, compilados, etc. O componente lógico ou funcional é aquele que possui uma utilidade para o funcionamento do programa.

O componente de tempo-de-desenvolvimento é aquele utilizado durante o desenvolvimento do software, enquanto o de tempo-de-execução é aquele pronto para ser executado pelo sistema ou que está sendo executado. Existem componentes lógicos e físicos tanto de desenvolvimento quanto de execução.

Com base nestes critérios podemos categorizar os componentes em:

Esta classificação não é a única possível. Além disso, existe um estreito relacionamento entre os diversos tipos de componentes. Os componentes de programa são "manipulados" e referenciados pelo programador durante o processo de programação. Após o processo de compilação e a sua posterior execução eles se transformam em componentes lógicos de tempo-de-execução que são manipulados e referenciados por outros componentes dinâmicos. Os componentes de programas, ditos lógicos, precisam ser armazenados em arquivos de código fonte ou em bibliotecas - componentes físicos de tempo-de-desenvolvimento.

Os componentes lógicos ou funcionais são aqueles que são utilizados para que o software funcione como desejado. Eles são os componentes que permitem que o software faça aquilo que foi especificado. Os componentes físicos não têm papel para a lógica do programa, i.e., para o seu funcionamento. Eles oferecem o apoio necessário para que o software possa ser gerenciado pelo sistema operacional e pelas ferramentas de desenvolvimento e instalação. Daqui por diante concentraremos nossas atenções aos componentes lógicos do software.

7.3 Componentes lógicos

Grande parte dos componentes lógicos de um programa é dependente da linguagem de programação na qual ele será escrito. Na etapa de design, entretanto, nosso objetivo é descrever o funcionamento do software independente de qual linguagem ele será implementado. Vamos utilizar componentes lógicos de programas num nível mais abstrato, independente da linguagem de programação.

No nível abstrato do design da arquitetura, consideremos apenas como tipos de componentes lógicos, as variáveis, as funções ou procedimentos, e as classes (e os módulos). Consideramos que eles são suficientes para projetarmos a arquitetura de componentes de software.

A variável é o componente responsável pelo armazenamento de dados. Cada variável tem um nome que a identifica e o tipo de dados que determina o que pode ser armazenado pela variável.

A função é a unidade lógica que realiza uma ou mais tarefas específicas modificando o estado de outros componentes de software, especialmente as variáveis. Cada função deve se caracterizar por aquilo que ela faz, isto é, pelo serviço que ela oferece. Cada função é referenciada por um nome. A função pode ter argumentos que funcionam como interface de comunicação com outros componentes. Estes argumentos podem ser para recebimento e/ou para o fornecimento de dados. A interface de uma função é formada pelos seu nome e seus argumentos. Uma função pode agregar outras funções. Neste caso as funções agregadas apenas existem para a função que as contêm. Uma função pode utilizar outras funções para poder realizar as suas tarefas. A utilização de uma função por outra é realizada através da sua interface.

A classe é o componente que agrega variáveis e funções num único componente lógico. Esta agregação deve ser feita seguindo alguns critérios específicos. As funções da classe operam sobre as variáveis da classe e estas por sua vez só podem ser alteradas pelo grupo de funções associadas. Para os componentes externos à classe apenas as funções são visíveis. Podemos dizer que a classe oferece uma série de serviços para operar sobre os dados armazenados nas suas variáveis. Cada classe é identificada por um nome e possui como interface uma série de funções que são oferecidas. As funções de uma classe podem utilizar serviços das funções de outra classe, mas não podem operar diretamente sobre suas variáveis.

O módulo ou pacote agrega  funções ou classes de acordo com categorias específicas. Um módulo funcional é aquele no qual todos os seus componentes estão unidos visando atingir um aspecto da funcionalidade. Eles possuem dependência funcional. Um módulo de serviços é aquele que agrega componentes que oferecem serviços que não têm necessariamente dependência funcional. Por exemplo, um módulo com funções matemáticas, um módulos com classes para objetos de interfaces gráficas, etc. Cada módulo possui um nome e uma interface que contém as interface de todas as funções e classes que estão nele contidas.

Os componentes lógicos oferecem recursos e restrições para decidirmos o que podemos construir para implementar o software. Na prática estes recursos podem variar de linguagem para linguagem, mas em geral, eles são comuns a maioria das linguagens. Com os componentes abstratos podemos nos concentrar na solução sem preocupação com as particularidades de cada linguagem. Entretanto, alguns componentes não poderão ser implementados diretamente em uma determinada linguagem.

Existem diversos paradigmas de programação. Podemos citar os paradigmas de programação procedimental, funcional, orientado a objetos e lógica. No paradigma de orientação a objetos o programador tem recursos que não encontra em linguagens no paradigma de programação procedimental e funcional.  Ao realizar o design da arquitetura em termos de componentes o engenheiro pode optar apenas pelos componentes lógicos que poderão ser implementados pela linguagem de programação a ser utilizada. Assim, pode-se optar por utilizar apenas funções se a programação será no paradigma procedimental. Da mesma forma, pode-se considerar classes como componente lógico se o paradigma de programação for orientado a objetos. Não vamos considerar o paradigma de programação lógica por não ser muito utilizado em desenvolvimento de software.

7.4 Princípios para o design da arquitetura e seus componentes

Uma engenharia se faz com métodos, técnicas, ferramentas, formalismos e princípios. Diversos conceitos e princípios ligados a estes conceitos têm sido introduzidos ao longo da história da engenharia de software. Um princípio gira em torno de uma idéia elaborada a partir de experiências anteriores e que aponta um caminho ou solução para os problemas no desenvolvimento. Seguir um princípio não é uma obrigação, mas uma grande chance de se obter sucesso.

Vamos apresentar alguns princípios para o design da arquitetura do software e de seus componentes que foram introduzidos na engenharia de software e que são incorporados em diversas linguagens e formalismos.

Abstração

A abstração é a capacidade psicológica que os seres humanos têm de se concentrar num certo nível de um problema, sem levar em consideração detalhes irrelevantes de menor nível. A abstração deve ser utilizada como técnicas de resolução de problemas nas diversas áreas de engenharia. As linguagens de modelagem e de programação oferecem recursos para que possamos trabalhar a abstração.

No design da arquitetura esta técnica é fundamental. Nosso objetivo é encontrar uma solução funcional de software em termos de componentes abstratos, sem levar em consideração detalhes das linguagens de programação.

A abstração está presente em quase tudo na computação. O sistema operacional oferece recursos que abstraem o funcionamento do computador. Um programa escrito numa linguagem de programação algorítmica (Pascal ou C) oferece comandos que abstraem a maneira como o computador executaria o comando em linguagem de máquina. O conceito de função permite abstrair os detalhes de implementação da função para que possamos nos concentrar apenas naquilo que a função faz. O uso de diagramas que descrevem o software oferece uma visão abstrata do funcionamento, independente de como ele está implementado.

Modularização

A modularização é uma técnica utilizada em diversas áreas da engenharia para se construir um produto que seja formado por componentes, os módulos, que possam ser montados ou integrados. Esta técnica permite que o esforço intelectual para a construção de um programa possa ser diminuído. Ela também facilita o processo de compilação e execução de um programa. Pode-se compilar componentes separadamente, bem como interligá-los apenas durante a execução quando for necessário.

A modularização é um dos caminhos para garantir a abstração. Podemos nos referir a componentes específicos e utilizar alguns dos seus serviços sem preocupação de como ele foi implementado. Com  o conceito de componente, estamos dizendo como uma certa unidade computacional que abstrai certos detalhes pode ser utilizada na composição de outros componentes. Para isto o componente precisa ser referenciado por um nome e precisa ter uma interface que diga como ele pode ser "interconectado" a outros componentes.

Diversos tipos de componentes podem ser encontrados na maioria das linguagens e ferramentas de programação. As variáveis de dados, as funções e as classes de um programa; as bibliotecas de funções e de classes; os arquivos fontes, executáveis e de dados; e diversos outros.

Encapsulamento

Para que a abstração seja implementada com maior rigor, cada componente (módulo) do software deve encapsular todos os detalhes internos de implementação e deixar visível apenas a sua interface. A interface do componente deve dizer aquilo que ele faz, o que ele precisa para se interconectar com outros componentes, e o que ele pode oferecer para os outros componentes.

Este princípio, também chamado de ocultação de informação (information hiding) sugere que os componentes sejam projetados de tal modo que os seus elementos internos sejam inacessíveis a outros componentes. O acesso apenas deve ser feito pela interface. Isto garante a integridade do componentes, uma vez que evita que seus elementos sejam alterados por outros componentes.

Reutilização

Além de facilitar o processo de desenvolvimento ao diminuir o esforço intelectual ou facilitar a compilação, os componentes podem ser reutilizados em diferentes software. Uma função que tenha sido construída para um software específico pode ser reutilizada em um outro software. A funcionalidade específica de cada componentes será utilizada para determinar a funcionalidade global do software. Software com diferentes funcionalidades globais podem ser construídos alguns componentes específicos comuns.

Normalmente os componentes reutilizáveis (tipos de dados, funções ou classes) são armazenados em um outro componentes não-funcional chamados de bibliotecas. Os componentes podem ser incorporados durante a compilação ou durante a execução. No primeiro caso os componentes ficam em bibliotecas de compilação ou de ligação estática e no segundo nas bibliotecas de ligação dinâmicas.

Generalização

A construção de componentes ou módulos específicos que facilitem o processo de desenvolvimento de software deve seguir um outro princípio importante: a generalização. Para que um componente de software tenha utilidade em diversos programas diferentes ele deve ser o mais genérico possível. Para isto ele precisa ser construído com o objetivo de oferecer serviços de propósito geral.

Por exemplo, uma função que desenha na tela um retângulo de qualquer tamanho é mais genérica do que uma função que desenha um retângulo com tamanho fixo. Esta função poderia ser ainda mais genérica se permitisse que o desenho de um retângulo com bordas com diversas espessuras e cores.

7.5 Modelando a arquitetura usando a UML

A arquitetura é essencialmente um modelo abstrato que descreve a estrutura de componentes que compõe um software e o seu funcionamento. Este modelo deve ser descrito através de notações e linguagens apropriadas. Diversas linguagens de descrição de arquitetura têm sido propostas, entretanto nenhuma ainda firmou-se como uma linguagem padrão.

Vimos que a UML foi proposta como uma linguagem para especificação, visualização, construção e documentação de sistemas de software e pode ser utilizada em diversas etapas do desenvolvimento. A UML não foi elaborada com a finalidade de descrever a arquitetura do software. Entretanto, ela apresenta diversos diagramas que permitem representar a estrutura lógica e física do software em termos de seus componentes, bem como a descrição do seu comportamento.

Vamos utilizar alguns dos diagramas UML para descrever a arquitetura lógica do software. É preciso ressaltar, porém, que a UML foi desenvolvida para a modelagem de software desenvolvido no paradigma de orientação a objetos. Para representarmos componentes funções é preciso fazer algumas adaptações. Outro aspecto importante é que na UML o termo componente refere-se a componentes físicos.Na UML existe uma notação específica para a representação de componentes físicos e diagramas que descrevem a arquitetura em termos destes componentes. Os componentes lógicos são representados como através de diagramas de classes e de objetos.

7.5.1 Modelando a dependência de funções

As funções que compõem um software podem ser modeladas usando um diagrama de objetos simplificado. O diagrama de funções é semelhante ao diagrama de objetos e descreve a relação de dependência entre as funções. Neste diagrama cada função é representada por um retângulo e a dependência entre elas é indicada por uma seta tracejada. Esta dependência indica que uma determinada função USA uma outra função.

A solução B dos exemplos é composta por diversas funções que possuem dependência entre si, como mostra a figura abaixo.

Este diagrama mostra apenas que uma função usa uma ou outras funções. A função ConsultarLivro( ) usa as funções xv_init( ), xv_main_loop( ), CriarJanelaConsulta( ) e CriarJanelaResposta( ). Estas duas últimas funções usam xv_create( ).

7.5.2 Usando o diagrama de interação

Para mostrar como as funções interagem entre si podemos utilizar o diagrama de interação da UML. Este diagrama mostra como as funções interagem entre si. Este diagrama possui duas formas: o diagrama de seqüência e o diagrama de colaboração. o diagrama de seqüência mostrar como as interações entre as funções ocorrem ao longo do tempo. A figura abaixo ilustra um diagrama de seqüência .

Cada função é representada por um retângulo e são alinhadas na parte superior. De cada retângulo parte uma linha pontilhada indicando a passagem do tempo de cima para baixo. O tempo no qual cada função está ativa é representado por um retângulo vertical sobre a linha do tempo. Quando uma função chama uma outra função ela ativa esta função. Isto é representado no diagrama por uma linha saindo da função que chama e chegando na linha de tempo da função chama. O retêngulo vertical que indica a ativação da função começa a partir deste ponto. O retorno à função que fez a chamada é representado por uma seta tracejada. Cada uma das setas horizontais de chamada e retorno pode indicar quais valores (dados) são passados ou retornados entre as funções.

7.5.3 Usando diagrama de atividades

As atividades internas a cada função pode ser descrita através de diagramas de atividades. Estes diagramas descrevem cada passo da função e permitem representar estruturas de decisões e repetições internas a cada função. As figuras abaixo descrevem os diagrama sde atividades para as  funções ConsultarLivro( ) e Procurar( ), da solução A dos exemplos.

7.6 Estilos e Padrões de Design Arquitetural

Padrões são soluções para problemas específicos que ocorrem de forma recorrente em um determinado contexto que foram identificados a partir da experiência coletiva de desenvolvedores de software. A proposta original de padrões veio do trabalho de Christopher Alexander na área de arquitetura (para construções). Sua definição para padrões:
 
Cada padrão é uma regra (esquema) de três partes que expressa uma relação entre um certo contexto, um problema, e uma solução.
O contexto descreve uma situação no desenvolvimento na qual existe um problema. O problema, que ocorre repetidamente no contexto, deve também ser descrito bem como as forças (requisitos, restrições e propriedades) associadas a ele. A solução descreve uma configuração ou estrutura de componentes e suas interconexões, obedecendo às forças do problema.
As forças, denominação dada por [Alexander], descrevem os requisitos que caracterizam o problema e que a solução deve satisfazer, as restrições que devem ser aplicadas às soluções e propriedades desejáveis que a solução deve ter.

7.6.1 Padrões no desenvolvimento de software

Os padrões são desenvolvidos ao longo dos anos por desenvolvedores de sistemas que reconheceram o valor de certos princípios e estruturas organizacionais de certas classes de software.

O uso de padrões e estilos de design é comum em várias disciplinas da engenharia. Eles são codificados tipicamente em manuais de engenharia ou em padrões ou normas

Em software podemos ter padrões a nível conceitual, a nível de arquitetura de software ou a nível de algoritmos e estruturas de dados.O padrões podem ser vistos como tijolos-de-construção mentais no desenvolvimento de software. Eles são entidades abstratas que apresentam soluções para o desenvolvimento e podem ser instanciadas quando se encontra problemas específicos num determinado contexto.

Os padrões se diferenciam de métodos de desenvolvimento de software por serem dependentes-do-problema ao passo que os métodos são independentes-do-problema. Os métodos apresentam passos o desenvolvimento e notações para a descrição do sistema, mas não abordam como solucionar certos problemas específicos.

Um sistema de software não deve ser descrito com apenas um padrão, mas por diversos padrões relacionados entre si, compondo uma arquitetura heterogênea.

Os padrões podem ser descritos em sistemas ou catálogos de padrões. Num sistema os padrões são descritos de maneira uniforme, são classificados. Também são descritos os seus relacionamentos. Num catálogo os padrões são descritos de maneira isolada.

7.6.2 Categorias de padrões e Relacionamentos

Cada padrão depende de padrões menores que ele contém e de padrões maiores no qual ele está contido. Os diversos padrões podem ser relacionados entre si. Três relações são identificados: refinamento, variante e combinação.

7.6.3 Descrição de padrões

Cada padrão pode ser descrito através do seguinte esquema:

7.6.4 Exemplos

Vamos apresentar, de forma sucinta, alguns exemplos de padrões de cada uma das categorias. A descrição detalhada de cada padrão pode ser vista em [Buchmann et al. 96].

Model-View-Controller

Contexto: Aplicações interativas com interface de usuário gráfica (IUG) flexível.

Problema: As IUGs mudam bastante, sejam por modificações na funcionalidade que requer uma mudança nos comandos ou menus, na necessidades de alterações na representação dos dados por necessidades dos usuários, ou na utilização de uma nova plataforma com um padrão diferente de look & feel. As seguintes forças influenciam a solução.

Solução: MVC divide a aplicação em três áreas: processamento, saída e entrada.

O componente Model encapsula o núcleo da funcionalidade e os dados que ela manipula. Ele é independente das representações específicas mostradas na saída. O componente View mostra informações ao usuário a partir de dados obtidos do Model. Podem existir múltiplas visualizações do modelo. Cada View possui um Controller associado. Cada Controller recebe eventos da entrada e traduz em serviços para o Model ou para o View.

Estrutura: O Model encapsula os dados e “exporta” as funções que oferecem serviços específicos. Controllers chamam estas funções dependendo dos comandos do usuário. É papel do Model prover funções para o acesso ao seus dados que são usadas pelos componentes View para poderem obter os dados a serem mostrados.

O mecanismo de propagação de mudanças mantém um registro de todos os componentes que são dependentes dos componentes do modelo. Todos os componentes View e alguns Controllers selecionados registram que querem ser informados das mudanças no Model. O mecanismo de propagação de mudanças é a única ligação do Model com Views e Controllers.  Cada View define um procedimento de update que é ativado pelo mecanismo de propagação de mudanças. Quando este procedimento é chamado, o componente View recupera os valores atuais do modelo e mostra-os na tela. Existem uma relação um-para-um entre Views  e Controllers para que seja possível a manipulação destes últimos sem alteração Models.

Se o comportamento de um Controller é dependente do Model ele deve registrar-se no mecanismo de propagação de mudanças. Isto pode ocorrer quando, por exemplo, as opções de um menu podem ser habilitadas ou desabilitadas de acordo o estado do sistema.

Utilizando este padrão elaboramos uma terceira arquitetura - solução C  - para a implementação da função Consultar Livro( ).

Baseado em eventos (ou chamada implícita)

Na solução B dos exemplos que utiliza as funções do XView para utilizar o sistemas de janelas XWindows elaboramos uma arquitetura de software que segue um padrão chamado de arquitetura baseada em eventos. Este padrão caracteriza-se pela presença de um componente, chamado notificador ou gerente de eventos, que associa eventos do sistemas a funções ou procedimentos do sistema (que podem ser funções ou métodos de um objeto). Ao invés de chamar um procedimento ou função explicitamente, um componente anuncia um ou mais eventos. Estes componentes são chamados de anunciantes. Outros componentes do sistema registram no notificador um  procedimento ou função a ser associada a ele. Quando um evento é anunciado o notificador chama todos os procedimentos que tenham sido registrado com aquele evento. Uma característica deste padrão é que não se tem um fluxo de execução pré-definido, a ordem de execução é depende de quais eventos aconteceram.

As principais vantagens são reuso, evolução e manutenção de componentes, um vez que os componentes podem ser alterados realizando-se apenas modificações nos registros do notificador.

Desvantagens:

Sistemas em camadas

Tubos e Filtros

Estratégia

Frequentemente é necessário usar algoritmos alternativos num programa. Um exemplo disto pode ser um programa para compressão de dados que oferece mais de uma alternativa de compressão com diferentes utilização de espaço e tempo dos recursos do sistema. Uma solução elegante é encapsular os algoritmos em diferentes classes intercambiáveis que podem ser instanciadas quando necessárias.

O padrão Estratégia encapsula algoritmos alternativos para uma mesma funcionalidade. Neste padrão, o componente EstratégiaAbstrata é uma classe abstrata que define uma estratégia comum para todas as estratégias. Parte da sua interface é o metódo InterfaceDoAlgoritmo(), que deve ser implementado por todas as subclasses. Cada EstratégiaConcreta é uma subclasse de EstratégiaAbstrata e implementa cada algoritmo específico na especialização do método InterfaceDoAlgoritmo().

Contexto() é o procedimento que utiliza os múltiplos algoritmos e contém uma instância de uma das estratégias. Quando o algoritmo precisa ser aplicado, Contexto() chama o método Interface-do-Contexto(), que por sua vez chama a instância correspondente do método InterfaceDoAlgoritmo() da classe EstratégiaAbstrata. 

7.7 Exemplos de arquiteturas

Para ilustrar os conceitos teóricos deste capítulo vamos apresentar duas arquiteturas distintas que implementam uma única função da aplicação de um sistema de biblioteca. Vamos utilizar a especificação conceitual para a função Consultar Livro apresentada no capítulo 4. Para esta função vamos apresentar duas soluções diferentes. Evidentemente, as soluções apresentadas são bastante simples e estão num escopo bastante pequeno.

Devido à esta simplicidade, pode parecer que o design da arquitetura de software é perda de tempo no desenvolvimento e que seria melhor partirmos direto para a implementação. Num caso simples, talvez sim, mas o objetivo da arquitetura de software é oferecer recursos para a especificação, visualização e documentação de software complexo com centenas ou milhares de linhas de código. Nosso objetivo é ilustrar concretamente como o design da arquitetura de componentes permite abstrair detalhes de programação para chegarmos a solução de software desejada.

7.7.1 Solução A

Vejamos uma solução bastante simples de arquitetura para implementar a função da aplicação Consultar Livro. Este programa poderia ser formado pelas funções: LerDados( ), Procurar( ),  FornecerResultado( ).  Esta solução não utiliza interfaces WIMP e produz uma interface que tem a seguinte aparência.

A função LerDados( ) deve solicitar ao usuário os dados de autor, título e ISBN do livro. A função Procurar( ) deve procurar na base de dados a localização do livro na biblioteca. FornecerResultado( ) deve mostrar na tela a localização do livro para o usuário. Estes três componentes podem ser agrupados em uma função principal responsável por fazer a chamada a cada uma das funções em seqüência. Estas funções utilizam outros dois componentes: Ler( ) e Escrever( ), que fazem parte de uma biblioteca de funções de entrada e saída disponíveis no compilador utilizado.

Representando cada componentes função por um quadrado e a relação de dependência entre uma função que utiliza uma outra podemos ter a seguinte diagrama que descreve arquitetura estática (configuração) de componentes.

As funções ConsultarLivro( ) e Procurar( ) podem ser descrita pelos seguintes diagrama de atividades.


Estes componentes interagem entre si da seguinte forma.
 

Vejamos os algoritmos que implementam estes componentes funções.

Estrutura de dados

struct lista_info_livro { }

Funções

ConsultaLivro( ) { }

LerDados(out:autor,out:titulo,out:isbn,out:escolha) {

}

Procurar (in:autor, in:titulo, in:isbn, out: posicao) {

}

FornecerResultado(in:posicao) {

}
Esta solução não satisfaz complementamente o modelo de interação que foi especificado no capítulo 4. Isto ocorre por limitações da linguagem, dos componentes de bibliotecas que foram utilizados e da maneira como eles podem ser interligados.

7.7.2 Solução B

Vamos apresentar agora uma solução envolvendo interfaces gráficas no estilo WIMP. O usuário vai poder fornecer as informações em uma janela de diálogo (janela consulta) na qual ele pode interagir com qualquer elemento independente de ordem. O resultado é apresentado em uma outra janela (janela resultado). As duas janelas podem ser vistas na figura abaixo.

Uma arquitetura para esta mesma função da aplicação poderia ser construídas pelos componentes: ConsultarLivro( ), DesenharJanelaConsulta( ), JanelaResultado( ), Procurar( ), Desistir( ) que seriam construídas numa linguagem procedimental (como C, por exemplo) e utilizariam funções de uma biblioteca para a construção de widgets de interfaces gráficas, um toolkit (veja seção 4.3), chamada de XView. As funções desta biblioteca são incorporadas ao programa em tempo-de-execução. Ela é portanto uma biblioteca de ligação dinâmica (DLL) que é um componente físico de tempo-de-execução(veja seção 7.4).

Na biblioteca XView vamos utilizar os seguintes componentes lógicos (funções):

A relação de dependência entre estas funções pode ser vista na figura abaixo.

Estas funções interagem entre si de acordo com o seguinte diagrama de seqüência.


Diagramas de Atividades

Na parte esquerda da figura abaixo temos o diagrama de atividades para a função ConsultarLivro( ). As funções CriaJanelaConsulta( ) e CriaJanelaResultado( ) não estão descritas em detalhes. Entretanto, como podemos ver no algoritmo logo adiante, elas são compostas por seqüências de chamadas à função xv_create( ). Desta forma consideramos desnecessário entrar em detalhes.

A seguir temos o diagrama de atividades para a função xv_main_loop( ). Na parte direita da figura indicamos apenas que tratar eventos chama as funções Procurar( ), Desistir( ) e FimJanelaResultado( ).

A seguir, temos os diagramas de atividades para funções Procurar( ) e Desistir( ). A função procurar( ) é semelhante à função de mesmo nome da solução A. Entretanto, devido à limitação do notificador (xv_main_loop( )) não poder passar parâmetros. esta função deve obter as informações fornecidas pelo usuário diretamente dos widgets caixa de texto. Isto é feito pela função ObterDados( ). Da mesma forma, para fornecer o resultado, esta função deve ativar MostraJanelaResultado( ) que se encarregara de fornecer a posição ao usuário. Esta função apenas ativa a janela que havia sido criada anteriormente. Após isto, MostraJanelaResultado( ) retorna o controle para Procurar( ) que por sua vez passa o controle de volta para o xv_main_loop( ).

A função xv_destroy( ) recebe como argumento o objeto JanelaConsulta para destruir o programa e encerrar a execução.

Algoritmos para as funções

A biblioteca XView também oferece algumas estruturas de dados para construirmos os widgets:

Estruturas de dados globais

struct lista_info_livro { }

// janelas e widgets

Frame    JanelaConsulta,  JanelaResultado;
Panel     PanelConsulta, PanelResultado;
Panel_item  text_autor, text_titulo, text_isbn, text_resultado, button_iniciar, button_desistir, button_fechar;

Funções



ConsultarLivro ( ) { }


DesenharJanelaConsulta( ) { }


DesenharJanelaResultado( ) { 

FimJanelaResultado( ) { }


Procurar( ) { }


Desistir( ) { }


MostraJanelaResultado(in:posicao) {  }

7.7.3 Solução C

Usando o padrão MVC, apresentado na seção 7.6, podemos elaborar uma terceira arquitetura para a função Consultar Livro( ). Esta solução utiliza o paradigma de orientação a objetos e é descrita pelo seguinte diagrama de classes.

A interação entre os objetos destas classes é descrita no diagrama abaixo.

Anterior | Índice | Próximo
(C) Jair C Leite, 2000                                                          Última atualização: 12/04/01