14. Herança

Suponha agora que pretende representar jogos cujo critério de terminação não é necessariamente o mesmo que definimos na classe Jogo. Por exemplo, queremos poder representar jogos que terminam de uma das seguintes formas:

Tal como podemos ter critérios de terminação diferentes também podemos ter, associado a cada um deles, uma forma específica de determinar o(s) vencedor(es).

Como podemos alcançar isto?

Uma solução de que NÃO gostamos

Uma abordagem que "funciona" mas que é fraca do ponto de vista da qualidade do software, é associar a cada objeto do tipo Jogo mais um atributo que define o tipo de jogo, no que diz respeito a terminação e cálculo de vencedor.

Podemos usar um enumerado, por exemplo, para definir os vários tipos de jogo que queremos representar:

Usamos agora o enumerado Terminacao e modificamos a classe Jogo onde necessário:

Tivemos que adicionar três novos atributos: o tipo de terminação, o número máximo de jogadas e o número de jogadas já feitas. Estes dois últimos são necessários para os casos em que o critério de terminação depende (também) do número de jogadas já feitas.

No construtor tivemos que adicionar dois novos parâmetros para obter os valores para o tipo de terminação do novo jogo e o número máximo de jogadas (quando se aplica). Repare que na pré-condição não impomos qualquer restrição nem ao valor do objetivo nem ao número máximo de jogadas, pois em alguns casos os seus valores não são relevantes e podem ser quaisquer.

Finalmente, alterámos o método terminou para acondicionar os vários tipos de critérios de terminação. Criámos também dois métodos auxiliar privatequantosAlcancaram e algumExatamente– que calculam o número de jogadores em jogo que alcançaram o objetivo e se algum jogador tem o valor da pontuação igual ao objetivo, respetivamente, pois esses valores são necessários para decidir a terminação.

 

Pergunta: Porque é que não gostamos desta solução?

Resposta: Porque não é extensível, ou seja, é difícil de manter e fazer evoluir.

Esta solução está extremamente dependente das formas de terminação existentes. Durante a "vida" do nosso software, sempre que quisermos acrescentar uma nova forma de terminação temos que alterar o código da classe Jogo e, eventualmente, o código de classes cliente desta (por exemplo, quando temos que adicionar novos parâmetros no construtor).

Isso é negativo em vários aspetos:

Além de tudo isto, temos também parâmetros do construtor e atributos de instância que só são relevantes para alguns tipos de terminação, o que não faz muito sentido.

 

Agora sim, vamos começar

Nos capítulos anteriores, as classes Jogador e Jogo foram sendo desenvolvidas com o intuito de ilustrar uma série de conceitos novos em Programação Orientada a Objetos.

Novos pormenores acerca desses conceitos foram sendo introduzidos a pouco e pouco, e só agora temos uma visão mais geral e completa do que pretendemos representar.

Porque é preciso saber o que representar antes de decidir como representar, vamos agora repensar a representação dos objetos e dos seus comportamentos, de uma forma mais estruturada.

A nova classe Jogo

Com o que já sabemos acerca dos jogos que queremos representar, podemos concluir que o seguinte se aplica a qualquer um:

É a partir desta informação que vamos agora construir a classe Jogo; de seguida vamos aprender a representar as variantes de que falámos anteriormente (e outras que ainda não estamos a prever) como especializações diferentes de um mesmo conceito – o Jogo.

Comecemos por criar a nossa classe Jogo, com tudo o que é comum a todos os jogos que queremos representar.

Tenha em atenção os seguintes pontos:

 

Antes de resolvermos este problema da definição do método terminou na classe Jogo, vamos ver como implementar os vários tipos de jogos que pretendemos – através da Herança – de forma a obter software que é extensível.

 

 


 

Anterior: 13.5. O interface genérico List

Seguinte: 14.1. Uma subclasse de Jogo