• Nenhum resultado encontrado

Construção do modelo de interações

7.5.2 Coesão e acoplamento

De acordo com a seção anterior, quando definimos uma mensagem no modelo de interações, estamos atribuindo uma responsabilidade a uma classe. Por conta disso, podemos entender a modelagem de interações como um processo cujo objetivo final é decompor as responsabilidades do sistema e alocá-las a classes. Dado um conjunto de N responsabilidades de um sistema, uma possibilidade é criar uma única classe no sistema para assumir todas as N responsabilidades. Outra ideia é criar N classes no sistema, sendo atribuída a cada uma delas uma das N responsabilidades. Certamente as duas alternativas anteriores são absurdas do ponto de vista prático. Mas, entre esses dois extremos, há muitas maneiras possíveis de alocar responsabilidades. Como podemos saber quais delas são melhores que outras?

Conforme já mencionamos no Capítulo 5, a tarefa de identificação de classes e de alocação de responsabilidades é bastante complexa, para a qual não há uma única maneira de realizar. O bom resultado dessa tarefa depende, entre outros fatores, da experiência do modelador e da correta aplicação de alguns princípios básicos de desenvolvimento de software. A coesão e o acoplamento são dois desses princípios. O acoplamento e a coesão servem como guias para a correta atribuição de responsabilidades a classes. A seguir, descrevemos esses importantes princípios de projeto orientado a objetos.

A coesão é uma medida do quão fortemente relacionadas e focalizadas são as responsabilidades de uma classe. Durante o projeto das classes de um SSOO, é extremamente importante que o projetista procure construir soluções com alta coesão. Isso equivale a fazer com que as responsabilidades atribuídas a cada classe sejam altamente relacionadas entre si. Em outras palavras, o projetista precisa definir classes de tal forma que cada uma delas tenha alta coesão, para que capture uma única abstração e, por consequência, seja mais reutilizável.

Um sintoma típico de uma classe com baixa coesão (o que é ruim para a qualidade de um sistema) é o fato de a mesma apresentar dois ou mais grupos de atributos (ou de operações), sendo que há alto grau de correlação dentro de cada grupo, mas baixo grau de correlação entre os grupos. Essa característica é um indício de que, em um projeto de melhor qualidade, deveria haver duas ou mais classes, cada uma contendo os grupos de atributos (ou de operações) com alta correlação entre si.

Além de ser menos reutilizáveis, classes com baixa coesão normalmente são mais complexas do que deveriam ser. Por isso, tais classes são menos inteligíveis e de manutenção (modificação) mais complicada.

Como exemplo, considere a Figura 7-22, que apresenta uma possível versão de projeto da classe

Turma, na qual há cinco operações (detalhes acerca da sintaxe da UML para representação de

operações são apresentados na Seção 8.3). Considere que duas dessas operações (haChoqueHorarios e inscrever) têm a ver com as responsabilidades intrínsecas ao conceito de turma: uma turma deve ser

responsável (1) por registrar a inscrição de um aluno e (2) por verificar se possui choque de horários com outra turma. Por outro lado, considere que as três demais operações (inserir, excluir e atualizar) foram

criadas para permitir refletir em um mecanismo de armazenamento persistente as alterações feitas sobre um objeto dessa classe. Por exemplo, quando um objeto Turma é criado e precisa ser

armazenado em um mecanismo de persistência, uma mensagem pode ser enviada a ele para ativar a operação inserir. Repare que os propósitos desses dois grupos de operações são diferentes. No

primeiro grupo, o propósito é permitir que a classe cumpra com responsabilidades relativas ao domínio ao qual pertence. No segundo, a meta é permitir a movimentação de dados acerca de uma turma de e para um mecanismo de armazenamento. Dentro de cada grupo, a similaridade (ou

correlação) entre operações é maior do que a obtida quando comparamos duas operações pertencentes a grupos distintos. O resultado é que a classe Turma assim projetada contém dois grupos

de operações com pouca relação entre si, exceto pelo fato de serem operações aplicáveis ao mesmo conceito do domínio do problema (p. ex., uma turma). Esse é um sintoma de que a coesão da classe

Turma não está em um nível adequado. Uma possível solução neste caso é refazer o projeto, dessa vez

com a criação de duas classes, situação em que cada uma delas passa a conter um dos grupos de operações. Como consequência dessa mudança no projeto, obtemos duas classes, cada qual com coesão maior do que a classe Turma projetada originalmente.

Figura 7-22: Exemplo de classe com coesão inadequada (i.e., coesão baixa)

N a Seção 5.4.3.2, descrevemos o padrão tático do DDD denominado Serviço de Domínio. Mencionamos este padrão novamente por conta de sua relação com o princípio da coesão. De fato, o que normalmente leva um modelador a decidir por utilizado um Serviço de Domínio é justamente evitar que as classes cujos objetos estão envolvidos em uma determinada interação assumam uma responsabilidade que eventualmente resultaria na diminuição da coesão da solução de projeto. Uma alternativa é atribuir a um Serviço do Domínio essa responsabilidade que não se encaixa (i.e., não é adequada) nas classes envolvidas.

O acoplamento é uma medida do quão fortemente uma classe está conectada a outras classes, tem conhecimento ou depende das mesmas. Uma classe com acoplamento fraco (baixo) não depende de muitas outras, o que é bom para a qualidade do projeto. Por outro lado, uma classe com acoplamento forte é menos inteligível isoladamente e menos reutilizável, exatamente por depender de várias outras classes. Além disso, uma classe com alto acoplamento é mais sensível a mudanças, quando é necessário modificar as classes da qual ela depende.

Pelo exposto anteriormente, podemos ver que a criação de modelos de projeto com alta coesão e baixo acoplamento deve ser um objetivo de qualquer projetista. Mas o que os princípios de coesão e acoplamento têm a ver com a modelagem de interações?

Na modelagem de interações, quando definimos uma mensagem, estamos criando uma dependência entre os objetos envolvidos. Isso é o mesmo que dizer que estamos aumentando o acoplamento entre os objetos em questão. Portanto, é necessário que o modelador fique atento para definir apenas mensagens que são realmente necessárias. Podemos analisar esse aspecto também sob a perspectiva do modelo de classes, da seguinte forma. Sempre que possível, devemos evitar o envio de mensagens que implique a criação de associações redundantes no modelo de classes. Isso porque a adição de uma associação entre duas classes aumenta o acoplamento entre as mesmas.

7.5.3

preciso evitar também a definição de módulos que têm baixa coesão.

Outro princípio de projeto que pode ser entendido como uma variante do princípio do acoplamento é conhecido como Lei de Demeter (tradução para Law of Demeter, LoD), também chamado de princípio do conhecimento mínimo. A Lei de Demeter impõe certas restrições acerca de quais são os objetos para os quais devem ser enviadas mensagens na implementação de uma operação definida em certa classe. Mais especificamente, esse princípio indica que, dentro de um método M contido em uma classe C, uma mensagem N somente deve ser enviada aos seguintes objetos:

(a) ao próprio objeto remetente da mensagem N;

(b) a um objeto recebido como parâmetro do método M; (d) a um objeto criado dentro do método;

(e) a um elemento de uma coleção que é atributo da classe.

A intenção da LoD é manter o acoplamento em um nível aceitável e também evitar que um objeto tenha conhecimento das associações eventualmente existentes entre outros objetos. Por exemplo, considere que existam duas classes associadas, Venda e Pagamento, e que outro objeto necessita saber o

valor do pagamento de uma venda. Uma alternativa é fazer com que esse objeto envie uma mensagem para o objeto Venda para conhecer seu objeto Pagamento associado e, em seguida, envie uma mensagem

para Pagamento para saber seu valor. Uma alternativa melhor, segundo a Lei de Demeter, é fazer com

que o objeto Venda responda diretamente o valor do pagamento. O objeto Venda fica, então,

responsável por consultar seu pagamento e retornar o valor correspondente. Nessa última alternativa, o objeto que precisa desse valor somente necessita ter conhecimento da classe Venda.

Para finalizar esta seção, é importante notar que as medidas de coesão e de acoplamento aplicam- se também a diferentes níveis de abstração e artefatos de software. Nesta seção, descrevemos o seu uso no contexto das classes. Entretanto, essas medidas podem também ser aplicadas a pacotes (de classes), componentes e camadas de software, conforme discutimos no Capítulo 10, quando descrevemos aspectos relativos à arquitetura de um SSOO. A descrição desses princípios também continua no Capítulo 8, no qual apresentamos detalhes sobre a modelagem de classes na etapa de projeto. Também no Capítulo 8, apresentamos outros princípios de projeto igualmente úteis na modelagem de interações.

Encapsulamento

Outro princípio de projeto que podemos utilizar durante a construção do modelo de interações para atribuir responsabilidades de forma correta é o encapsulamento. Descrevemos este princípio de uma perspectiva mais conceitual na Seção 1.2.3. Quando o interpretamos na perspectiva do código-fonte de um SSOO, esse princípio recomenda que uma unidade de software oculte, por meio de uma interface, detalhes de implementação do seu estado e do seu comportamento. Qualquer acesso a informações internas da unidade deve ser realizado pela sua interface, que é composta por operações.

O princípio do encapsulamento está de acordo com o critério de decomposição de um sistema em partes menores denominado. Esse critério recebe o nome de ocultamento da informação (tradução para Information Hiding), e foi proposto por David Parnas em 1972. Sua definição é fornecida a seguir (PARNAS, 1972):

Todo módulo […] é caracterizado pelo seu conhecimento de uma decisão de projeto que ele esconde de todos os outros [módulos]. Sua interface ou definição deve ser tal que revele o mínimo possível sobre seu funcionamento interno.

Nessa definição, David Parnas usou a expressão “módulo” porque, na época, o paradigma dominante era o da programação estruturada. Entretanto, com o passar do tempo, constatou-se que essa definição é igualmente aplicável a classes na orientação a objetos, assim como também vale para camadas de software, ou mesmo para subsistemas.

Uma vantagem de uma unidade de software cujo projeto esteja aderente ao princípio do encapsulamento é que a implementação dessa unidade pode ser alterada, sem que seus clientes necessitem de alterações. Como consequência, projetos aderentes ao princípio do encapsulamento possuem boa manutenibilidade, além de serem mais extensíveis e reusáveis.

Na modelagem de interações, surgem situações em que o projetista deve fazer uso do princípio do encapsulamento para atribuir responsabilidades corretamente. A seguir, apresentamos como exemplo duas dessas situações e a forma adequada de modelagem.

Como primeiro exemplo, considere a Figura 7-23, que apresenta um fragmento de diagrama de sequência. Nesse diagrama, um objeto da classe RealizarInscricaoControlador recebe uma mensagem para

registrar a inscrição de um aluno em uma turma. A questão aqui é de que forma esse objeto envia mensagens subsequentes para seus colaboradores de forma que possa cumprir com essa responsabilidade. A seguir, apresentamos duas soluções de projeto alternativas para essa questão e analisamos a diferença entre elas à luz do princípio do encapsulamento.

Figura 7-23: Mensagem enviada para controlador de caso de uso.

A Figura 7-24 apresenta uma primeira solução. Repare que nessa solução o objeto

RealizarInscricaoControlador obtém uma referência para o objeto Turma (mensagem 1.1), cria um objeto Inscricao (mensagem 1.2) e o associa ao objeto Turma (mensagem 1.3). Essa solução não está de acordo

com o princípio do encapsulamento. Para entender porque, repare que o objeto RealizarInscricao- Controlador desnecessariamente tem conhecimento da existência do objeto Inscricao, visto que a classe Turma já possui uma associação com Inscricao (veja a Figura 5-49) e poderia assumir a

Figura 7-24: Interação que viola o princípio do encapsulamento.

Agora considere a Figura 7-25. Diferente da solução anterior, o objeto RealizarInscricaoControlador

repassa para o objeto Turma a responsabilidade de criação do objeto Inscrição. O efeito dessa mudança

é que a forma pela qual o objeto Inscricao é criado não precisa ser conhecida pelo objeto RealizarInscricaoControlador; essa decisão fica escondida (ou encapsulada) na classe Turma. Repare que o

acoplamento dessa segunda solução é também menor, visto que a classe RealizarInscricaoControlador não

tem conhecimento da existência da classe Inscricao. Em geral, a aplicação adequada do princípio do

encapsulamento leva a soluções de projeto com níveis de acoplamento mais baixos.

Figura 7-25: Interação aderente ao princípio do encapsulamento.

A segunda situação de modelagem que apresentamos é aquela na qual um objeto deve ser criado a partir de informações coletadas externamente ao sistema. Essas informações podem, por exemplo, ser fornecidas por um ator em um caso de uso. Na realização de um caso de uso, é comum atribuir a responsabilidade de criar objetos de entidade a um objeto de controle, que recebe os dados necessários à instanciação a partir de objetos de fronteira. A Figura 7-26 ilustra essa situação, na qual um objeto da classe RealizacaoPedidosServico envia a mensagem adicionarItemPedido para o objeto Pedido.

7.5.4

7.5.4.1

O primeiro é um objeto de controle, e o segundo, do domínio. Repare que a mensagem

adicionarItemPedido repassa informações coletadas pelo controlador

Outra situação de modelagem na qual uma responsabilidade de criação deve ser alocada é aquela que envolve relacionamentos de agregação ou composição (Seção 5.2.2.7). Nesse caso, o objeto- todo normalmente deve ser assumir a responsabilidade para criar suas partes. Portanto, em uma agregação (ou composição), é mais adequado que o objeto todo crie suas partes quando requisitado por outros objetos. A Figura 7-26 também ilustra essa situação, na qual um objeto da classe Pedido

envia uma mensagem de criação para dar origem a um objeto ItemPedido. Veja também a Figura 5-39,

que apresenta o diagrama de classes em que essas duas classes estão envolvidas.

Figura 7-26: Em um relacionamento todo-parte, o todo é normalmente responsável por criar suas partes.

Para finalizar a discussão aqui apresentada sobre o princípio do encapsulamento, vamos relacioná-lo ao padrão tático Agregado, descrito na Seção 5.4.3.2 no contexto da modelagem de interações. De fato, os agregados são manifestações da aplicação correta do princípio do encapsulamento, uma vez que, para interagir com objetos contidos em um agregado, é necessário enviar uma mensagem para sua raiz. Se voltarmos nossa atenção para o diagrama de comunicação apresentado na Figura 7-26, vamos perceber que a classe Pedido é a raiz do agregado que envolve essa própria classe, além das

classes ItemPedido e Produto. Repare também que o objeto da classe RealizacaoPedidosServico envia uma

mensagem para a raiz, que por sua vez trata de interagir com os demais objetos do agregado.