A fase seguinte é a implementação prática do projecto: a construção. Durante a construção surgem problemas imprevistos que têm de ser resolvidos, o que pode implicar refazer partes do projecto (de preferência pequenas partes). Por outro lado, há pormenores de construção que têm de ser decididos em obra e que não necessitam da intervenção directa do atelier de arquitectura. Note-se que a construção está tipicamente a cargo de um construtor civil, e não do gabinete de arquitectura.
No desenvolvimento disciplinado de software também pode ser assim. Tal como não faz sentido que o arquitecto comece o seu trabalho assentando tijolos no edifício final sem antes o ter projectado, também não faz qualquer sentido uma empresa de desenvolvimento de software começar a resolver um problema escrevendo linhas de código em C++ ou em qualquer outra linguagem.
Mas a tendência do programador noviço é começar por se sentar ao computador, executar o editor, criar um novo ficheiro C++ e escrever:
É fundamental resistir a essa tentação! Os problemas reais são complexos e é necessário usar técnicas de abordagem semelhantes à do arquitecto que permitam lidar com essa complexidade. Ou seja:
#include <iostream>
using namespace std;...
Uma empresa de desenvolvimento de software, quando lhe é encomendado um trabalho, começa por enviar um analista discutir com o cliente quais são as suas reais necessidades. Depois a equipa a cargo do trabalho estuda o problema na sua generalidade e desenha alguns diagramas representativos do problema a resolver. Estes diagramas permitem à equipa voltar a discutir com o cliente e chegar finalmente a um conjunto mais ou menos definitivo de requisitos para o trabalho. A esta primeira fase chama-se análise.
A seguir, a equipa começa a desenhar diagramas com possíveis soluções para o problema. Estes diagramas, para além de permitirem à equipa experimentar rapidamente várias possíveis soluções, são também uma boa forma de comunicação com o cliente e entre os membros da equipa. Uma vez decidida arquitectura geral da solução, a equipa começa a produzir diagramas com um grau de pormenor crescente. Por vezes são desenvolvidos protótipos (pequenos programas) para acertar melhor o funcionamento de determinadas partes da solução ou para experimentar melhor possíveis soluções e interacções com o utilizador do programa. Os protótipos depois de usados são deitados fora. A esta segunda fase chama-se desenho ou concepção.
A fase seguinte é a implementação prática da solução entretanto desenhada. Durante a implementação surgem problemas imprevistos que têm de ser resolvidos, o que pode implicar refazer partes do desenho original (de preferência pequenas partes). Por outro lado, há pormenores de implementação que têm de ser decididos durante a implementação e que não necessitam da intervenção directa dos elementos da equipa que analisaram o problema e desenharam a sua solução. Note-se que a análise do problema a resolver, o desenho da solução para o problema e a implementação são realizadas normalmente por diferentes colaboradores da empresa de desenvolvimento de software, embora trabalhando em equipa.
Resumindo:
Na prática também é comum, e muitas vezes desejável, que as várias fases de resolução do problema (análise, desenho e implementação) se sobreponham parcialmente ou que sejam iteradas várias vezes. Isto quer dizer que não se deve ter a veleidade de pretender que o analista em conjunto com o cliente conseguem estabelecer todos os requisitos do sistema a desenvolver de uma só penada, e sem erros. O desenvolvimento de software é um processo iterativo, onde os sucessivos refinamentos devem poder ter lugar. Também é verdade que nem sempre é possível, ou mesmo desejável, que haja uma tão clara especialização dos elementos da equipa, sendo comum que, ao contrário do que acontece na construção de edifícios, as mesmas pessoas façam simultânea ou alternadamente desenho, implementação e mesmo análise.
Em qualquer dos casos, é muito importante que haja uma comunicação fácil entre todos as pessoas envolvidas no projecto. Isso implica que haja uma linguagem comum a clientes, analistas, arquitectos (desenhadores) e implementadores. Naturalmente que nessa comunicação se pode usar um língua natural, como o português, mas as linguagens naturais têm o problema de poderem ser ambíguas e imprecisas e ainda de não serem práticas para representar relações complexas entre partes de um todo (imagine-se um projecto de arquitectura expresso apenas através de palavras: possível, mas basicamente ilegível). O ideal passa, pois, por usar diagramas, mas diagramas expressos numa linguagem gráfica que seja universal e que precisa.
Diz-se que uma imagem muitas vezes vale mil palavras. Na realidade pode valer muito mais do que isso. É comum ver um programador em concentração profunda (ou completamente perdido) tentando perceber um troço de código escrito por outrem. O mesmo acontece por vezes quando um programador olha para código escrito por ele próprio há uns meses ou anos atrás. As linguagens de programação de alto nível, apesar de estarem bastante mais próximas da linguagem natural do que a linguagem máquina ou o assembly, são desenhadas para comunicar com um compilador e não com humanos. Apesar de ser expectável que os programadores documentem e comentem apropriadamente o seu código e que, sobretudo, escrevam de uma forma tão clara e transparente quanto possível, o código pode ser surpreendentemente difícil de perceber. Todos os aprendizes de programação passam pela experiência de desenvolver o algoritmo de remoção de um nó de uma árvore binária ordenada. Ou pelo menos pela provação de perceberem um algoritmo fornecido por terceiros. Ambas são experiências traumatizantes se não se fizer uso de um diagrama para representar os nós da árvore e respectivos ramos.
Significa isto naturalmente que qualquer programador deve recorrer frequentemente ao papel ou ao quadro branco para desenhar diagramas que lhe permitam perceber melhor um problema ou comunicar mais eficientemente as suas ideias.
A criatividade é uma qualidade importante no desenvolvimento
de
software. Mas é conveniente reservá-la para
o que é fundamental (a solução em si) e não
para a forma como ela é escrita ou apresentada. Assim, é
importante que o código seja escrito usando convenções
comummente aceites, tais como deixar espaços entre operadores e
operandos (e.g., x * y), escrever uma instrução
por linha, usar substantivos (possivelmente adjectivados) para o nome das
classes, começar o nomes das classes por maiúsculas, etc.
Da mesma forma a criatividade é inútil quando aplicada ao aspecto gráfico dos diagramas. Por isso é importante que exista um convenção ou, de preferência, uma norma, que estabeleça os símbolos gráficos a usar para desenhar os diagramas representativos das entidades e respectivas relações de um pedaço de software implementado ou a implementar. É necessária uma linguagem gráfica de modelação. Foi reconhecendo este facto que os "três amigos" (Grady Booch, Ivar Jacobson e James Rumbaugh) desenvolveram a chamada UML (Unified Modelling Language) [2, 4], normalizada posteriormente pelo OMG (Object Management Group) [1].
A UML especifica um conjunto de diagramas, e as respectivas simbologia e semântica que servem para representar graficamente o modelo simplificado que, quando implementado numa linguagem de programação, resolverá o problema em causa. Está implícita nesta observação a noção de que o resultado do desenho devem ser um conjunto de diagramas que, depois de implementados numa linguagem de programação arbitrária (ou em várias), resultarão num pacote de software que resolve o problema em causa. Esta é uma visão simplificada da realidade por duas razões: (a) o desenvolvimento é iterativo e existe realimentação entre as sua várias fases (e.g., a implementação pode identificar problemas de desenho que têm de ser resolvidos) e (b) raramente é possível que um desenho possa ser feito independentemente das ferramentas usadas na implementação. Aliás, é muitas vezes necessário levar em conta por exemplo as linguagens de programação a utilizar durante o desenho da solução, sob risco de se proporem desenhos que ou são inexequíveis ou pelo menos extremamente difíceis de implementar. Apesar destas ressalvas, a UML é uma ferramenta importante de comunicação e não pode ser desprezada.
Existe um conjunto vasto de diagramas em UML:
Nesta disciplina abordar-se-ão apenas os diagramas estáticos e os diagramas de sequência e de actividade. Os diagramas estáticos dividem-se em diagramas de classes e diagramas de objectos. Os diagramas de classes representam as classes existentes nos modelos e as relações entre elas. Estes diagramas são estáticos porque as relações entre as classes não variam durante a execução do programa. Os diagramas de objectos, por outro lado, representam os objectos existentes e as ligações entre eles. Estes diagramas são estáticos porque representam o estado do sistema num dado instante de tempo.
* Procura-se melhor tradução...
Por outro lado, embora estes diagramas se digam "de classes", podem e devem ser usados a um nível de abstracção mais alto, por exemplo para identificar os conceitos e respectivas relações no domínio do problema a resolver, antes mesmo de se começar a desenhar uma solução para esse problema. Assim, embora aqui se use o termo "classe", deve-se levar em conta que as afirmações são também verdadeiras se se substituir esse termo pelo de "conceito". No primeiro caso está-se no domínio da solução, e no segundo no domínio do problema. No primeiro caso os diagramas representam o modelo de análise e no segundo o modelo de desenho.
Os diagramas de classes dizem-se diagramas estáticos, pois, no caso dos modelos de desenho, representam classes e relações que existem durante toda execução do programa: não variam no tempo.
Empregado é:

Se a classe for abstracta o seu nome escreve-se em itálico:

Quando se está a desenhar um diagrama à mão, não é fácil escrever em itálico. Nesse caso acrescenta-se a propriedade {abstract} após o nome da classe:

Note-se que é suposto a UML simplificar a comunicação. Assim, é lícito abusar da linguagem quando isso contribuir para facilitar a comunicação. Esta disciplina aliás, abusa da UML, nomeadamente estendendo a notação dos objectos por forma a poder ser usada para os tipos elementares ou básicos.
Ao longo deste resumo serão apresentadas tabelas que tentam sintetizar as traduções que se podem realizar entre a linguagem natural, a linguagem C++ e a UML. No entanto, é necessário que se perceba que, enquanto a ponte entre linguagem natural e UML ocorre durante a análise do problema e resulta num modelo UML de análise do problema, a ponte entre UML e a linguagem C++ ocorre durante a implementação da solução, sendo nesse caso o modelo UML um modelo de desenho de uma solução para o problema.
Linguagem natural Nome comum: "humano" |
UML Classe:
|
C++ Classe:
|
As instâncias de uma classe, ou seja, os respectivos objectos têm um comportamento (dados pelas suas operações) e um estado (dado pelos seus atributos). Ao conjunto das operações e dos atributos de uma classe chama-se características, e é possível representá-los em UML, dividindo-se em "fatias" a representação das classes. A divisão mais típica é em três fatias: a do topo contém o nome da classe, a intermédia os atributos e da base as operações. Podem-se omitir quaisquer fatias com excepção da fatia do título:

Enquanto em C++ é típico dividir os membros de uma classe em públicos e privados, declarando-se primeiro os públicos pois formam a interface da classe, em UML a divisão estabelece-se entre atributos e operações, podendo existir características com qualquer visibilidade (o mesmo que categoria de acesso) quer na fatias das operações quer na fatia dos atributos. É típico os atributos de uma classe serem privados (aliás é recomendável que o sejam, de acordo com o princípio do encapsulamento) e a maior parte das operações serem públicas. Mas a verdade é que a notação UML não separa claramente características que pertencem à interface (públicas) das que pertencem à implementação (privadas). Isso é deixado ao critério de quem desenha o diagrama. Assim, é muitas vezes boa ideia representar num diagrama apenas as características públicas, deixando as privadas para alturas em que se tem de discutir pormenores de implementação.
Por outro lado, a ordem das fatias também não é a melhor, pois dá ênfase aos atributos face às operações, apesar de a distinção mais interessante que se pode fazer entre classes se basear no comportamento das respectivas instâncias, e não tanto no seu estado. O comportamento das instâncias de uma classe tem a ver essencialmente com as respectivas operações, e não tanto com os respectivos atributos. Estes, normalmente, não passam de um mero pormenor de implementação.
A visibilidade das características de uma classe pode ser representada em UML precedendo-as dos símbolos
Finalmente, as características podem ser (ou ter âmbito) de classe
ou de instância, consoante se apliquem à classe como um todo (ou seja, ao
conjunto de todas as instâncias da classe) ou às suas instâncias particulares. As características com âmbito
de classe representam-se sublinhadas. Por exemplo, na classe abstracta Forma

existe
uma operação nova() que, dado um canal de entrada, devolve um
ponteiro do tipo Forma* para um tipo concreto de Forma.
Esta operação proporciona características de fábrica
(de formas) à classe abstracta Forma. Em C++ a respectiva declaração
seria
class Forma {
public:
...
static Forma* nova(istream& entrada);
...
};
e a definição seria
Forma* Forma::nova(istream& entrada)...
{
}
Uma possível utilização seria
...
ifstream entrada(nome.c_str());
Forma* forma = Forma::nova(entrada);...
Por vezes é necessário adornar um elemento notacional UML com comentários, i.e., texto explicativo que não faz parte do modelo formal. Para isso usam-se notas nas quais se insere qualquer texto. A notação para as notas é simplesmente um rectângulo contendo texto e com o canto superior dobrado:
![]()
É possível ligar uma nota, por uma linha tracejada, a um ou mais elementos notacionais aos quais diz respeito:

Uma noção importante é a de condição invariante de classe. Uma condição invariante é uma restrição imposta aos valores dos respectivos atributos. Em UML, uma restrição representa-se colocando-a numa nota, mas entre chavetas:
![]()
O texto da restrição pode ser escrito em linguagem natural, mas também numa linguagem formal própria, o OCL (Object Constraint Language), que está fora do âmbito desta disciplina. É possível usar restrições para representar pré-condições e condições objectivo de uma operação, como se verá, mas também para representar condições invariantes de classe, usando-se para isso o estereótipo «invariant»:

A lista de parâmetros consiste numa lista de parâmetros separados por vírgulas. Os parâmetros podem ter ou não valores por omissão e têm a seguinte sintaxe:
nome_de_função(lista_de_parâmetros):tipo_de_devoluçãonome_de_procedimento(parâmetros)
O tipo de passagem pode ser:tipo_de_passagem
nome:tipo
tipo_de_passagemnome:tipo= valor_por_omissão
Forma acima.
Uma operação que não modifique o estado do sistema, i.e., que não altere a instância da classe sobre a qual é efectuada nem qualquer outro objecto do sistema, diz-se de inspecção. Pode-se representar uma operação com essas características acrescentando a propriedade {query} à notação apresentada. Por exemplo:

Também é possível classificar como não virtuais as operações. Por exemplo:

Uma forma mais clara de classificar as operações de uma classe é usando os estereótipos «constructor», «destructor», «query» e «update» (para operações modificadoras):

Finalmente, é possível usar restrições para assinalar pré-condições e condições objectivo de operações, fazendo-se uso para isso dos estereótipos «precondition» e «postcondition»:
A cada operação declarada numa classe corresponde forçosamente um método, excepto se a operação for abstracta. A presença numa subclasse da declaração de uma operação com a mesma assinatura de uma operação polimórfica declarada numa superclasse, correspondem a declarações redundantes da mesma operação (que é única) e assinalam o fornecimento de um método para essa operação pela classe em causa.
Pode-se indicar o corpo, ou seja, a implementação de um método colocando o respectivo código (e.g., em C++) numa restrição:

nome:tipo
nome[multiplicidade]:tipo
nome:tipo= valor_inicial
É possível indicar que um determinado atributo não é variável, mas sim constante, usando a propriedade {frozen}. No exemplo

define-se parcialmente uma classe PilhaFixaDe100Int que
representa pilhas de inteiros com capacidade máxima fixa em 100 itens. Na
definição deixa-se claro que:
número_de_itens que é um inteiro
inicializado com zero e que guarda o número de itens em cada instância da
classe;itens que guarda os itens da pilha, em
quantidade de exactamente número_de_itens inteiros; enúmero_máximo_de_itens inteiro,
com valor 100, que é partilhado entre todas as instâncias da classe.A multiplicidade de um atributo é colocada, como se viu, entre parêntese rectos e após a indicação do tipo do respectivo atributo, e pode ter as seguintes formas:
Note-se que a multiplicidade de um atributo se pode apresentar da várias formas. A forma que se apresentou é a mais abstracta de todas. Mas é possível usar formas de apresentação que se aproximam mais da implementação final da classe numa determinada linguagem de programação, apresentando o atributo sem multiplicidade explícita, mas indicando o seu tipo como um dos tipos da linguagem que suporte a multiplicidade desejada. Ou seja, o exemplo acima tem a seguinte apresentação alternativa:

onde se indica directamente que o atributo itens corresponde a uma matriz
clássica do C++ com exactamente número_máximo_de_itens (dos
quais, presume-se, só se usam número_de_itens em cada instante).
Um outro exemplo, porventura mais elucidativo, é o de uma classe FormaComposta
que representa formas que são composições de outras formas (instâncias da
classe Forma). Uma primeira representação, mais abstracta,
acentua apenas o facto de uma FormaComposta ser composta por um
número arbitrário de formas:

Uma representação menos abstracta reflecte já as particularidades da
linguagem C++. A primeira é que em C++ uma forma de guardar um número
arbitrário de itens de um determinado tipo é usar vectores. A segunda é
que, como a classe Forma será provavelmente abstracta e,
sobretudo, porque será o topo de uma hierarquia de classes polimórficas, o
vector usado para guardar as formas deverá ser um vector de ponteiros para a
classe Forma:

Claro está que a forma menos abstracta de representar a classe é directamente em C++:
class FormaComposta: public Forma {
public:
...
private:
vector<Forma*> formas;
...
};
A primeira representação, diz que uma forma composta é composta por um número arbitrário de formas. A segunda, diz que uma forma composta é composta por um vector de ponteiros para formas. A terceira e última, é código C++. Enquanto as duas primeiras são modelos, o primeiro provavelmente um modelo de análise do problema e o segundo um modelo de desenho ou concepção, a última é já a implementação.
Viu-se que, nas classes, mais importante que os atributos, que representam o estado dos respectivos objectos, são as operações, que representam o respectivo comportamento. O comportamento dos objectos normalmente depende do comportamento de outros objectos, da mesma ou de outras classes. Por isso é muito importante ter uma forma de representar os vários tipos de relações que podem existir entre classes.
Existem essencialmente quatro formas de relação entre classes representáveis em UML. Por ordem decrescente da "força de relacionamento" são:
A relação mais forte entre duas classes é a relação de generalização.
Como já se viu, a relação de generalização é apenas uma forma diferente de
ver a relação é um. Se se pode dizer que "qualquer objecto
da classe A é também um objecto da classe B",
então a classe B é uma generalização de A, o que
quer dizer que em C++ a classe A deriva directa ou indirectamente
da classe B, ou seja, B é uma superclasse de A
e A uma subclasse de B.
Em UML a relação de generalização representa-se por uma seta com a ponta
fechada e oca apontando da classe mais especializada para a classe mais
generalizada. Note-se que em UML só é lícito representar graficamente
esta relação entre classes que sejam generalizações ou especializações
directas uma da outra. Por exemplo, uma classe Forma,
representando o conceito de forma no contexto de uma aplicação de desenho, por
exemplo, é uma generalização das classes Círculo, Rectângulo
e forma composta, entre outras:

|
|||
|
Menos forte que a relação de generalização, mas mais forte que todos os
outros tipos de relação, é a relação de amizade entre duas
classes. Esta representa-se usando a notação da dependência, a mais
fraca das associações, como se verá, mas usando o estereótipo «friend».
É de notar que a relação de amizade é direccional, pelo que esta relação
exige a existência de uma seta. A seta aponta para a classe que declara a
amizade e, por isso, permite o acesso irrestrito, pela classe que declara como
amiga, a todos os seus membros privados. Por exemplo, se existir uma
classe ListaDeInt e uma classe (embutida) ListaDeInt::Iterador
que é declarada como amiga da classe ListaDeInt, e por isso tem
acesso aos seus membros privados, a notação a usar é:

Note-se que não existe em UML nenhuma notação para representar
graficamente a relação de embutimento, i.e., a relação que existe
entre uma classe embutida e a classe na qual está embutida. Isso acontece
porque essa relação não é uma relação semântica, e por isso importante do
ponto de vista do modelo, mas meramente sintáctica. A única forma de
representar tal relação é através da qualificação do nome da classe
embutida, tal como no diagrama acima (i.e., ListaDeInt::Iterador).
A relação mais fraca entre classes é a associação. Uma associação também pode ter vários graus de "força de associação". Por ordem decrescente de "força de associação":
As associações normalmente são binárias, i.e., dizem respeito a apenas duas classes. Neste texto lida-se apenas com associações binárias, embora a UML suporte associações de aridade arbitrária.
As associações são abstracções. Dizer que duas classes estão associadas é o mesmo que dizer que os respectivos objectos (ou instâncias) estão de alguma forma ligados. Ou seja, as associações são instanciadas em ligações entre objectos, da mesma forma que as classes são instanciadas em objectos.
Na associação simples não há conceito de posse. Os objectos de cada uma das classes poderão possuir ligações entre si, mas estas ligações não introduzem qualquer noção de posse entre esses objectos. Os tempos de vida dos objectos ligados entre si são, no caso da associação simples, totalmente independentes.
Este tipo de de associação corresponde a relações que podem ser expressas em português através de um verbo que não denote posse (e.g., possui um) nem composição (e.g., compõe-se de um), pois essas expressões correspondem às relações de agregação e composição. Esse verbo pode ser, por exemplo, trabalhar para, por exemplo na expressão trabalha para um.
Muitas vezes este tipo de relação é expresso em português através da expressão "tem um". No entanto, esta expressão é muito ambígua, podendo ser também usada para expressar relações de posse que correspondem á associação de agregação (ter no sentido de possuir) ou à relação de composição (ter no sentido de se compor de qualquer coisa) .
A notação para uma associação é simplesmente uma linha ligando as duas classes associadas. Por exemplo, para representar a relação é chefiado por um ou chefia um entre um empregado e um chefe, pode-se usar uma associação simples:
![]()
É possível adornar esta associação de várias formas. Em primeiro lugar com o nome da associação, que se coloca a meio da linha de associação acompanhada de um pequeno triângulo que indica a direcção em que a associação deve ser lida:

ou então

Também é possível adornar a notação com informação semântica adicional, nomeadamente de multiplicidade. A informação de multiplicidade diz a quantos objectos de uma das classes pode estar associado um objecto da outra classe. A notação usada para a multiplicidade é idêntica à usada para os atributos, e é colocada junto ao extremo da associação cuja multiplicidade se está a indicar. É possível colocar multiplicidades em ambos os extremos da linha de associação. Por exemplo, se se pretender indicar que um empregado pode ter zero ou um chefe, e que um chefe tem um número arbitrário de empregados, pode-se usar:

Os extremos da linha associação também podem ser adornados com o papel que o ou os objectos da classe nesse extremo da linha têm perante cada objecto da classe no outro extremo. Por exemplo:

O papel corresponde normalmente ao atributo usado no código C++ para
representar a associação em causa. Assim, possíveis implementações
das classes Empregado e Chefe seriam:
class Chefe;
class Empregado {
public:
...
private:
...
Chefe* chefe;
};
class Chefe : public Empregado {
public:
...
private:
...
vector<Empregado*> empregados;
};
Assim sendo, é possível indicar a visibilidade dos papéis na classe a que dizem respeito. Supondo que quer os empregados de um chefe quer o chefe de um empregado são representados por atributos privados, o diagrama deverá ser:

É possível indicar a ou as direcções nas quais é possível navegar através da associação. Para isso colocam-se setas no ou nos topos da linha de associação. Caso não se coloque qualquer seta, admite-se geralmente que a navegação se pode fazer em ambos os sentidos, pelo que não é usual encontrarem-se setas em os extremos da linha. Por exemplo, se for possível obter directamente os empregados de um dado chefe, mas não for possível obter directamente o chefe de um dado empregado, o diagrama acima deve ser modificado para:

(Note-se que este diagrama não corresponde ao código C++ acima, pois nele é claro que os empregados possuem forma directa de saber quem é o respectivo chefe.)
Finalmente, note-se que se podem representar no mesmo diagrama vários tipos de relações. Assim, sabendo-se que qualquer chefe é um empregado, teria sido mais claro incluir uma relação de generalização entre as duas classes nos diagramas acima:

Este diagrama pode-se descrever em português como se segue:
Existem duas classes,
ChefeeEmpregado, relacionadas. A classeEmpregadoé abstracta. Qualquer chefe (da classeChefe) é também um empregado (da classeEmpregado). Qualquer chefe chefia um número arbitrário de empregados, que conhece porempregados. Qualquer empregado é chefiado por um ou nenhum chefe, que conhece porchefe. A partir de um chefe é possível obter directamente os seus empregados, mas a partir dos empregados não é possível obter directamente o respectivo chefe.
Fica claro que um diagrama representa as relações entre as classes de uma forma muito mais clara e imediata que simples texto.
|
A associação de agregação representa uma relação parte/todo ou possui um. Ao contrário do que se passa na associação simples, a agregação pode implicar o controlo do tempo de vida de um objecto por outro. Do ponto de vista semântico, a agregação é mais forte que a simples associação, mas mais fraca que a composição. É a relação que existe, por exemplo, entre uma pessoa e a sua aliança: a pessoa possui a aliança, mas não é constituída por ela, mesmo que entre outras partes.
A notação para a relação de agregação é idêntica à da associação simples, embora com um losango vazio colocado do lado da classe que tem a posse. Por exemplo, a relação de posse entre uma empresa e os veículos da sua frota pode ser vista como uma associação de agregação:

Este diagrama pode-se descrever em português como se segue:
Existem duas classes,
EmpresaeVeículo, relacionadas. A classeEmpregadoé abstracta. Qualquer empresa possui um conjunto da veículos, a que chamafrota. Qualquer veículo pertence no máximo a uma empresa.
Em c++. a associação de agregação, tal como a associação simples, representa-se normalmente através ponteiros ou referências. Os ponteiros usam-se quando a ligação de um objecto a outro, correspondente a uma associação entre as classes correspondentes, pode ser alterada durante a vida dos objectos. As referências usam-se quando essa ligação é permanente, mantendo-se durante toda a vida dos objectos em causa.
Quando existir multiplicidade maior do que um, é necessário usar algum tipo
de contentor de ponteiros, por exemplo um vector.
|
A associação de composição é a mais forte de todas as associações. Ela corresponde à relação é composto por um. É a relação que existe, por exemplo, entre uma pessoa e o seu dedo: vão-se os anéis, fiquem os dedos, diz-se...
Numa relação de composição, os tempos de vida dos objectos envolvidos estão relacionados. Os objectos que compõem um objecto são construídos depois de construído esse objecto, ou pelo menos ao mesmo tempo, e são destruídos antes de destruído esse objecto, ou quando muito ao mesmo tempo. Normalmente a construção e destruição dos objectos que compõem o objecto é da sua exclusiva responsabilidade, embora ocasionalmente seja delegada em terceiros.
A notação é idêntica à da agregação, embora neste caso o losango seja
cheio. Por exemplo, a relação que existe entre a classe FormaComposta
(ver Secção 2.1.5) e a classe abstracta Forma
é de composição:

(A propriedade {incomplete} serve para indicar que no diagrama não estão representadas todas as classes que derivam da classe base.)
Numa relação de composição, um objecto só pode ser parte da composição de um outro objecto. Por isso, a multiplicidade colocada do lado do objecto que é composto tem de ser inferior ou igual a um.
Este diagrama pode-se descrever em português como se segue:
Existe uma classe abstracta
Forma. Existe uma classe (concreta)FormaComposta. Qualquer forma composta é composta por um número arbitrário de formas, a que chamaformas. Qualquer forma está no máximo numa forma composta. Há mais classes derivadas da classeForma, mas não são mostradas no diagrama.
A composição é muitas vezes representada através de atributos.
Aliás, os atributos de uma classe em C++ que não sejam ponteiros nem
referências estabelecem uma associação de composição entre o seu tipo e a
classe de que são atributos. Normalmente estes atributos são de tipos
básicos do C++ ou de TAD. Aos atributos de classes propriamente ditas,
particularmente se polimórficas, acede-se normalmente através de
ponteiros. Isso é muito claro no exemplo da FormaComposta,
cujo código C++ pode ser visto na Secção 2.1.5.
Finalmente, é importante referir que a fronteira entre a composição e a agregação é difícil de estabelecer. Há muitos casos em que não é fácil classificar um associação como sendo de composição ou de simples agregação.
|
Viu-se nas tabelas de tradução entre linguagem natural, UML e C++ que o papel de uma associação corresponde muitas vezes a um atributo na classe C++ respectiva. De facto, embora se representem como atributos UML os atributos das classes C++ que são TAD (tipos que põem o ênfase no valor e na igualdade), representam-se como papeis de associações UML os atributos das classes C++ que são classes propriamente ditas (que põem o ênfase na existência e na identidade).
Tal como a descrição do tipo de peças de Lego existentes e dos seus possíveis encaixes não mostra o aspecto final da construção a realizar, também os diagramas de classes, com a declaração de todas as classes e suas relações, são insuficientes para mostrar como funcionará o sistema depois de implementado. É necessário dizer que peças são necessárias exactamente e qual a sua disposição na construção final. Os diagramas de objectos têm essa finalidade: mostrar como as instâncias das classes, ou seja, os objectos, se ligam entre si no sistema em execução. No entanto, ao contrário das construções em Lego, que são estáticas, um programa consiste num conjunto de objectos que interagem, podendo ser construídos e destruídos de acordo com as necessidades, e podendo as respectivas ligações variar ao longo do tempo. Se os diagramas de classes são fundamentais para declarar que tipos de entidades e respectivas relações existem no sistema, e se os diagramas de objectos são importantes para mostrar o conjunto de objectos e respectivas ligações existentes num dado instante de tempo, são necessários diagramas adicionais que permitam representar a dinâmica do sistema. Esses diagramas serão assunto da próxima secção. Nesta secção apresenta-se brevemente a notação UML para os diagramas de objectos.
Os diagramas de objectos representam, portanto, os objectos e respectivas ligações existentes no programa (ou existentes na realidade que se pretende modelar) num determinado instante de tempo. Os objectos correspondem a instâncias de classes. As ligações entre os objectos correspondem a instâncias das associações entre classes. É pelo facto de os diagramas de objectos dizerem respeito ao estado do programa num dado instante de tempo que se dizem diagramas estáticos.
Os diagramas de objectos são muito úteis, por exemplo, para representar a estrutura dos dados existentes num programa. A sua notação é muito simples e é apresentada nas secções que se seguem:
Os objectos representam-se através de
Os objectos ou instâncias de uma dada classe são representadas de uma forma simples: um rectângulo
contendo no seu topo o nome do objecto, seguido de dois pontos e do nome da classe,
sendo todos estes elementos sublinhados, para distinguir claramente a notação
dos objectos da notação usada para as classes. Por exemplo, a representação
de um objecto chamado zé da classe Humano é:
![]()
Durante a execução de um programa é usual existirem objectos ou instâncias sem nome associado. É o caso das instâncias não declaradas em C++ (instâncias dinâmicas ou temporárias). É possível, por isso, omitir o nome do objecto da notação:
![]()
Da mesma forma, ocasionalmente a classe de um dado objecto pode-se inferir a partir do contexto. Nesse caso a notação pode ser simplificada:
![]()
Em qualquer dos casos pode-se usar a propriedade {frozen} para indicar que o objecto não pode mudar de valor, i.e., que é constante:
![]()
O estado de um objecto é representável através de um comportamento adicional, no qual se listam os atributos da classe respectiva seguidos do respectivo valor. Por exemplo:

|
|||
|
Um objecto está ligado a outro se lhe conhecer o paradeiro. As ligações são instâncias das associações entre as classes dos objectos respectivos. Podem, por isso, ser ligações simples, de agregação ou de composição, embora esse facto seja raramente mostrado nos diagramas de objectos. Em C++, a ligação entre um objecto e outro é tipicamente representada em C++:
Em qualquer dos casos, se a multiplicidade a associação respectiva for maior do que um, a ligação corresponde a um item de um contentor que é atributo do primeiro objecto.
A notação usada para as ligações binárias (as ligações com maiores aridades estão fora do âmbito deste texto) é simplesmente uma linha colocada entre os objectos ligados. Esta linha pode ter os adornos da associação correspondente, embora isso seja pouco usual, se se exceptuar a navegabilidade. A multiplicidade é sempre omitida numa ligação, pois faz sentido apenas no caso da correspondente associação.
O exemplo abaixo mostra o diagrama de objectos de uma forma composta por quatro outras formas, das quais uma é outra forma composta por três outras formas:
Capítulos 1, 4, e 6 (partes) de [3].
(Continua no Resumo da Aula 10.)
[1] "OMG Unified Modeling Language Specification", versão 1.4, Setembro de 2001. http://www.omg.org/cgi-bin/doc?formal/01-09-67
[2] James Rumbaugh, Ivar Jacobson e Grady Booch, "The Unified Modeling Language Reference Manual", Addison-Wesley, Reading, Massachusetts, 1999.
[3] Martin Fowler e Kendall Scott, "UML Distilled: A Brief Guide to the Standard Object Modeling Language", 2ª edição, Addison-Wesley, Reading, Massachusetts, 1999.
[4] Grady Booch, James Rumbaugh e Ivar Jacobson, "The Unified Modeling Language User Guide", Addison-Wesley, Reading, Massachusetts, 1999.