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:

          7.1

Arquitetura de Software

          7.2

Componentes de Software

          7.3

Componentes lógicos

          7.4

Princípios para o design da arquitetura de componentes

          7.5

Modelando a arquitetura usando a UML

          7.6

Estilos e Padrões de design

          7.7

E xemplos

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 linksa 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.

  • Permite ao programador compreender melhor o software
  • Possibilita que estas partes possam ser reutilizadas mais de uma vez dentro do mesmo programa ou por outro programa
  • Podem ser gerenciadas mais facilmente quanto estiverem sendo executadas.

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:

  • componentes de programa – são componentes lógicos de tempo-de-desenvolvimento fornecidos pelas linguagens de programação e que utilizamos para construir um programa. Ex.: tipos de dados, variáveis, procedimentos, funções, classes, módulos, pacotes – dependem da linguagem de programação. Descrevemos as soluções nos nossos exemplosusando componentes lógicos. As soluções A e B utilizam variáveis e funções. A solução C utiliza classes.
  • componentes físicos de desenvolvimento – são componentes físicos tempo-de-desenvolvimento que contêm os componentes lógicos. Eles são manipulados pelas ferramentas de desenvolvimento (editores e compiladores) e pelo sistema operacional. Ex.: arquivos de código fonte, arquivos de código objeto, arquivos de declarações (.h), bibliotecas de componentes de programa (de ligação estática). As funções Ler( ) e Escrever( ) da solução A dos exemplos fazem parte de uma biblioteca de funções oferecida pelo compilador. Da mesma forma todas as funções do Xview — xv_create( ), xv_set( ), xv_get( ), xv_init( ), xv_destroy( ), xv_main_loop( ) — fazem parte da biblioteca libxview.a. O programa da solução B utiliza os arquivos .h xview.h, frame.h, panel.hque também são componentes físicos.
  • componentes físicos de tempo-de-execução - São os componentes instalação e execução que compõem o sistema antes que ele seja executado. São os componentes que obtemos ao adquirir o software. Ex.: arquivos executáveis, arquivos de configuração, arquivos de dados, bibliotecas de ligação dinâmica (DLL). A biblioteca XView utiliza funções oferecidas por uma outra biblioteca que precisa estar sendo executado ao mesmo tempo que a aplicação. Esta biblioteca de ligação dinâmica é o XServer que oferece os serviços necessários para o desenho de janelas e leitura dos eventos de entrada.
  • componentes lógicos de tempo-de-execução - São os componentes lógicos que existem quando o sistema está sendo executado ou que são criados a partir da execução de outros componentes. Podem ser de dois tipos:
    • intraoperáveis – quando são visíveis apenas por componentes do mesmo programa Ex.: variáveis, funções, objetos de programa.
    • interoperáveis – quando são visíveis por componentes de diferentes programa Ex.: processos, threads, objetos CORBA, objetos COM. O notificador é uma função do XServer incorporado ao programa durante a execução função xv_main_loop( ). O xv_main_loop é um componente lógico de tempo-de-desenvolvimento que faz parte da biblioteca libxview.a — componente físico de tempo de desenvolvimento — que é responsável pela interação em tempo-de-execução da função notificador — componente lógico de tempo de execução — que faz parte do XServer, uma biblioteca de ligação dinâmica — componente físico de tempo de execução.

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 pacoteagrega  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 arquiteturatêm sido propostas, entretanto nenhuma ainda firmou-se como uma linguagem padrão.

Vimos que a UMLfoi 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 USAuma 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 ativaesta 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.

  • Padrões arquiteturais- expressam o esquema de organização estrutural fundamental para um sistema de software. Assemelham-se aos Estilos Arquiteturais descritos por [Shaw & Garlan 96].
  • Padrões de design- provê um esquema para refinamento dos subsistemas ou componentes de um sistema de software.
  • Idiomas - são padrões de baixo nível, específicos para o desenvolvimento em uma determinada linguagem de programação, descrevendo como implementar aspectos particulares de cada componente.

Os diversos padrões podem ser relacionados entre si. Três relações são identificados: refinamento, variante e combinação.

  • Refinamento: Um padrão que resolve um determinado problema pode, por sua vez, gerar novos problemas. Componentes isolados de um padrão específico podem ser descritos por padrões  mais detalhados.
  • Variante: Um padrão pode ser uma variante de um outro. De uma perspectiva geral um padrão e suas variantes descrevem soluções para problemas bastante similares. Estes problemas normalmente variam em algumas das forças envolvidas e não na sua caracterísitica geral.
  • Combinação: Padrões podem ser combinados em uma estrutura mais complexa no mesmo nível de abstração. Tal situação pode ocorrer quando o problema original inclui mais forças do que podem ser balanceadas em um único padrão. Neste caso, combinar vários padrões pode resolver o problema, cada um satisfazendo um conjunto particular de forças.

7.6.3 Descrição de padrões

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

  • Nome- o nome do padrão e uma breve descrição.
  • Também conhecido como - outros nomes, se algum for conhecido.
  • Exemplo- um exemplo ilustrativo do padrão.
  • Contexto- as situações nas quais o padrão pode ser aplicado.
  • Problema- o problema que o padrão aborda, incluindo uma discussão das forças associadas.
  • Solução- o princípio fundamental da solução por trás do padrão
  • Estrutura- um especificação detalhada dos aspectos estruturais.
  • Dinâmica- cenários descrevendo o comportamento em tempo-de-execução.
  • Implementação- diretrizes para implementar o padrão.
  • Exemplo resolvido - discussão de algum aspecto importante que não é coberto nas descrições da Solução, Estrutura, Dinâmica e Implementação.
  • Variantes- Uma breve descrição das variantes ou especializações de um padrão.
  • Usos conhecidos - Exemplos do uso obtidos de sistemas existentes.
  • Conseqüências- Os benefícios que o padrão proporciona e potenciais vantagens.
  • Ver também - Referências a padrões que resolvem problemas similares e a padrões que ajudam refinar os padrões que está sendo descrito.

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.

  • Uma mesma informação pode ser representada de maneira diferente e em janelas diferentes.
  • As representações das informações devem refletir na tela imediatamente as mudanças que ocorrem no sistema.
  • As mudanças na IUG devem ser fáceis de fazer e em tempo-de-execução.
  • O suporte a diferentes padrões de look & feel não devem afetar o código do núcleo funcional.

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:

  • Difícil correção uma vez que não existe um fluxo pré-definido, nem se sabe com precisão quais as pré- e pós-condições quando uma função ou precidemento é chamado pelo notificador.
  • Torna-se difícil a passagem de dados dos componentes que geram eventos para os que serão chamados pelo notificador.
  • Quando um componente anuncia um evento ele não pode garantir que os outro componentes irão responder.

Sistemas em camadas

  • Um sistema em camadas é organizado hierarquicamente com cada camada oferecendo serviços para camadas superiores e utilizando serviços de camadas inferiores.
  • Alguns sistemas apenas permitem que os serviços de uma camada apenas sejam acessados por camadas imediatamente superior, i.e., a interação apenas ocorre entre duas camadas adjacentes.
  • O modelo computacional é o de que cada camada, a partir da inferior, implementa uma máquina virtual que oferece serviços, a partir de serviços oferecidos pelas máquinas de níveis inferiores. São considerados componentes as diversas máquinas virtuais oferecidas em cada camada.
  • Os conectores são definidos pelos protocolos que determinam com as camadas irão interagir. A maioria do sistemas computacionais com este estilo implementam conexões através de chamadas a funções oferecidas por camadas anteriores.
  • Os principais exemplos de aplicação incluem sistemas operacionais e sistemas de bancos de dados.
  • Podemos destacar algumas propriedades interessantes:
    • Possibilita um processo de design incremental - um problema complexo pode ser particionado em níveis crescente de abstração, resolvendo-os num seqüência do mais baixo para o mais alto nível.
    • Oferecem suporte para reuso.Pode-se projetar um sistema reusando camadas inferiores e implementando apenas camadas de mais alto nível de abstração.
    • Oferecem suporte para evolução e manutenção. Uma vez definidas as interfaces (os protocolos de interação) entre as diversas camadas pode-se acrescentar facilmente novas camadas superiores a um sistema, ou modificar os componentes de camadas existentes por outras mais atualizadas.
  • Como desvantagens temos:
    • Nem todo problemas pode ser facilmente particionado em camadas. Muitas vezes é difícl encontrar o nível certo de abstração para cada componente.
    • A performance pode ser afetada quando camadas de mais alto nível precisa interagir com camadas de níveis inferiores.

Tubos e Filtros

  • Cada componente, o filtro, tem um conjunto de entradas e saídas.
  • Um filtro lê um stream de dados de suas entradas e produz streams de dados em suas saídas.
  • Cada filtro realiza uma computação que transforma o stream de dados de entrada no stream de dados de saída
  • O filtro gera dados na saída antes mesmo dos dados da entrada terem sido totalmente consumidos
  • Os tubos são conectores que interligam a saída de um filtro da entrada de outro.
  • Uma configuração de filtros e tubos determinam um modelo computacional formado por elementos que transformam (filtram) dados recebidos nas suas entradas e geram dados nas suas saídas, podendo ser executados sequencialmente ou concorrentemente..
  • Os invariantes mais importantes deste estilos determinam que filtros sejam entidades independentes, não compartilhando estados com outros componentes.
  • Um outro invariante determina que um filtro não tem conhecimento de quais filtros que lhe fornecem dados ou quais os que recebem seus dados.
  • Pipelines são uma especialização deste estilo no qual os filtros são conectados numa seqüência linear.
  • O exemplo mais conhecido de aplicação deste estilo são configurações definidas por usuários de shell do unix.
  • Propriedades:
    • É fácil entender o comportamento geral do sistema, conhecendo-se o comportamento de cada filtro.
    • Incentiva reusabilidade dos componentes.
    • Fácil evolução e manutenção – filtros podem ser trocados e modificados sem alterar outros componentes do sistema.
    • Suporte a execução corrente – um filtro pode ir gerando dados na saída enquanto outro está lendo estes dados através da sua entrada conectada por um tubo.
  • Desvantagens: tende a gerar processamento em lote; não adequado para aplicações interativas; necessidade de adequar dados do stream para um determinado filtro.

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 {

    char autor[30]; char titulo[30]; char isbn[30]; char posicao[10]; lista_info_livro *proximo;

}

Funções

ConsultaLivro( ) {

      string autor,titulo,isbn,posicao; int escolha;

LerDados(autor,titulo,isbn,escolha); if (escolha = 1) {

    Procurar(autor,titulo,isbn,posicao); FornecerResultado(posicao);

}
LimparTela( );

}

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

      Escreva(“Autor:”); Leia(autor); Escreva(“Titulo:”);

 

    Leia(titulo); Escreva(“ISBN:”); Leia(isbn”); Escreva(“1. Iniciar”); Escreva(“2. Desistir”); Leia(escolha);

}

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

      info_livros *lista_info_livros;

info_livros = inicio_lista; while (info_livros != NULL)

    if (autor = = info_livros.autor) ||    (titulo = = info_livros.titulo) ||    (isbn = = info_livros.isbn) then   posicao = info_livros.posicao else   posicao = NULL;

}

}

FornecerResultado(in:posicao) {

      if (posicao != NULL) then

        Escreva(“A posicao do livro na  estante é:”, posicao);

else

      Escreva(“Livro não encontrado”);

} 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):

  • xv_init( ) – que inicia a execução da biblioteca
  • xv_create( ) – que cria os diversos widgets
  • xv_get( ) – que obtem valores de atributos de widgets
  • xv_set( ) – que modifica valores de atributos
  • xv_main_loop( ) – que realiza o papel de notificador

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:

  • Frame – moldura de uma janela.
  • Panel – interior da janela onde podem ser colocados outros widgets.
  • Panel_item – os widgets botões, caixa de texto, etc.

Estruturas de dados globais

struct lista_info_livro {

    char autor[30]; char titulo[30]; char isbn[30]; char posicao[10]; lista_info_livro *proximo;

}

// 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 ( ) {

    xv_init( ); JanelaConsulta( ); JanelaResultado( ); xv_main_loop( );

}


DesenharJanelaConsulta( ) {

      JanelaConsulta = xv_create(NULL,FRAME,

                FRAME_LABEL, “Consultar Livro”, XV_WIDTH, 200, XV_HEIGHT, 100,

 

                  NULL);

PanelConsulta = xv_create(JanelaConsulta, PANEL, NULL);

text_autor = xv_create(PanelConsulta, PANEL_TEXT,

              PANEL_LABEL_STRING, “Autor:”, PANEL_VALUE,”",

 

            NULL);

text_titulo = xv_create(PanelConsulta, PANEL_TEXT,

              PANEL_LABEL_STRING, “Título:”, PANEL_VALUE,”",

 

            NULL);

text_isbn = xv_create(PanelConsulta, PANEL_TEXT,

              PANEL_LABEL_STRING, “Código ISBN:”, PANEL_VALUE,”",

 

            NULL);

button_iniciar = xv_create(PanelConsulta, PANEL_BUTTON,

              PANEL_LABEL_STRING, “Iniciar”, PANEL_NOTIFY_PROC, procurar,

 

            NULL);

button_desistir = xv_create(PanelConsulta, PANEL_BUTTON,

              PANEL_LABEL_STRING, “Desistir”, PANEL_NOTIFY_PROC, desistir,

 

            NULL);

}


DesenharJanelaResultado( ) {

        JanelaResultado = xv_create(JanelaConsulta ,FRAME_CMD,

                  FRAME_LABEL, “Resultado”, NULL);

PanelResultado = xv_create(JanelaResultado, PANEL, NULL);

text_resultado = xv_create(PanelResultado, PANEL_TEXT,

              PANEL_LABEL_STRING, “A posicao do livro é:”, PANEL_VALUE,”",

 

            NULL);

button_fechar = xv_create(PanelResultado, PANEL_BUTTON,

            PANEL_LABEL_STRING, “Fechar”, PANEL_NOTIFY_PROC, FimJanelaResultado,

 

          NULL);

FimJanelaResultado( ) {

    xv_set(JanelaResultado, XV_SHOW, FALSE, NULL);

}


Procurar( ) {

    string autor,titulo,isbn,posicao; info_livros *lista_info_livros;
    autor = xv_get(text_autor,PANEL_VALUE); titulo = xv_get(text_titulo,PANEL_VALUE); isbn = xv_get(text_isbn,PANEL_VALUE);
        info_livros = inicio_lista; while (info_livros != NULL)

          if (autor = = info_livros.autor) ||    (titulo = = info_livros.titulo) ||    (isbn = = info_livros.isbn) then   posicao = info_livros.posicao else   posicao = NULL;

}

 

    MostraJanelaResultado(posicao)

}


Desistir( ) {

    xv_destroy_frame(JanelaConsulta); exit( );

}


MostraJanelaResultado(in:posicao) {

    xv_set(text_resultado,posicao); xv_set(JanelaResultado, XV_SHOW, TRUE, NULL);

}


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.

Fonte: http://www.dimap.ufrn.br/~jair/ES/c7.html#componentes

 

Sobre a DANRESA – Com mais de 14 anos de experiência no mercado de TI, a DANRESA é uma Consultoria de Informática com atuação em todo o território nacional, focada em duas linhas de serviços principais e complementares: Fábrica de SoftwareDesenvolvimento de Sistemas, Infraestrutura e Outsourcing de TI. A área de Desenvolvimento é voltada a Projetos de Negócios por meio de Sistemas Personalizados de TI de acordo com a especificidade de cada cliente, realizando levantamento dos processos, análise e programação através de sua fábrica de software ou com profissionais alocados no cliente. Já a área de Infraestrutura inclui serviços como Outsourcing de TI, Gerenciamento e Monitoramento de equipamentos de missão crítica como Servidores, Roteadores, Switches e Links de conectividade, Instalação e Manutenção de pontos de rede, voz e dados, Suporte Técnico por meio de Service Desk – em que os atendimentos são feitos por uma equipe especializada e certificada nas práticas do ITIL – entre outros. Com cerca de 400 colaboradores e 100 clientes, a DANRESA possui em sua carteira empresas como ANFAVEA, BASF (Suvinil), Ernst Young, Sem Parar, Schneider, CBC, Eurobras, Avape, Alves Feitosa Advogados, Instituto Airton Senna, Grupo Kaduna, CVC, WoodBrook, Salles Leite (Iguaçu Energia ), etc. Para mais informações, ligue: (11) 4452-6450