Speed 1. Este guião é sobre herança. O exercício consiste, no essencial, em definir uma classe de base e duas classes derivadas dela. Vamos ainda usar algumas classes cuja implementação já é fornecida e que também usam a herança e fazer algumas experiências com elas. No final deste enunciado encontrará um diagrama de classes UML relativo à solução pretendida. (Se não sabe de que estamos a falar quando dizemos “herança” em programação, reveja rapidamente as aulas, consulte os acetatos, os livros ou mesmo a internet. Este guião não vai explicar o que é a herança). Nesta altura do campeonato já não é aceitável que o seu código não esteja comentado de acordo com as convenções do Javadoc. Repare que no Eclipse pode gerar o esqueleto dos comentários seleccionado o botão direito do rato e clicando na opção “Source/Add Comment” ou Alt+Shift+J. Deve preencher os comentários ANTES de fazer submissões ao Mooshak porque depois de aceite é tarde demais … 2. A classe de base é a classe Speedometer (velocímetro) cujos objectos podem ser usados para modelar os velocímetros que encontramos nos automóveis e noutros veículos. Um veículo ou acelera (para simplificar vamos apenas considerar que será de forma uniforme), ou mantém a velocidade constante, ou trava (também de forma uniforme). Para sabermos a velocidade que o velocímetro deve afixar, precisamos de conhecer a duração (intervalo de tempo, por exemplo, 3 segundos ou duas horas, vinte minutos e quarenta e nove segundos, expressa na classe em segundos) de cada período de aceleração e travagem. 3. Comece então um novo projecto Speed no seu workspace e declare a classe Speedometer, com as seguintes funções: Speed, que dá a velocidade corrente, Travel, com um argumento de tipo double representando o intervalo de tempo em que o veículo mantém a velocidade corrente, Duration, que retorna a duração da viagem, desde o início (isto é, desde que o objecto foi inicializado), Accelerate, com dois argumentos, a aceleração positiva e a duração do movimento uniformemente acelerado, Brake, para travar, simétrica do anterior, com aceleração negativa, mas representada por um argumento positivo na chamada da função, Stop, para parar, com um argumento positivo representando a aceleração negativa até à imobilização do veículo. Declare ainda uma função Display capaz de mostrar a velocidade corrente e a duração da viagem, uma função SetSpeedUnits para seleccionar as unidades com as quais a velocidade é mostrada na função Display e uma função SpeedUnits para devolver o valor convencional que representa as unidades que estão em vigor. Saiba que uma milha são 1609,3 metros e declare já a respectiva constante estática (usando o classificador “final”). O argumento da função SetSpeedUnits, bem como o valor de retorno da função SpeedUnits, devem ser de tipo String e os valores reconhecidos são “m/s”, “km/h” e “mph” (metros por segundo, quilómetros por hora, milhas por hora, respectivamente). A função Display mostra a velocidade em formato de ponto fixo, com três casas decimais, usando as unidades correntemente seleccionadas, seguida de um espaço, da cadeia que indica as unidades, de outro espaço e da duração da viagem. A duração da viagem vem no formato h:mm:ss.mmm, por exemplo 2:03:45.540 representa duas horas, três minutos, quarenta e cinco segundos e quinhentos e quarenta milésimos de segundo. Quer dizer, os minutos e os segundos ocupam sempre duas posições, mesmo que sejam menos do que 10, e os milésimos de segundo usam sempre três algarismos. 4. Que membros de dados fazem falta? Sugiro um para a velocidade corrente, outro para a duração da viagem, outro ainda para a velocidade máxima que o veículo pode atingir e finalmente um para registar as tais unidades da velocidade mostrada com a função Display. Não confunda as unidades da função Display com as unidades usadas nos argumentos das funções e nos cálculos. Estas serão sempre as do sistema internacional, ou seja, a duração é em segundos, a velocidade em metros por segundo e a aceleração em metros por segundo quadrado. 5. Nesta classe declare pelo menos um construtor com um argumento para a velocidade máxima. De resto, o veículo inicialmente está parado, a duração é nula e a função Display usa metros por segundo. Note que a velocidade máxima é constante para cada velocímetro, no sentido de que não há maneira de a modificar após ter sido definida no construtor, mas pode variar de velocímetro para velocímetro. 6. Já está? Antes de programarmos os corpos das funções da classe Speedometer, refresquemos a nossa física. O problema envolve movimento uniformemente acelerado e por isso vamos ter de recorrer às conhecidas leis da cinemática: d = v0t + ½ at2 e v = v0 + at, onde d é a distância percorrida, v0 é a velocidade inicial, a é a aceleração, t é a duração do movimento e v é a velocidade final. (Lembra-se? Não me diga que não!) 7. Passemos já à tarefa A que resolve o seguinte problema: temos uma partícula parada e aplicamos-lhe uma sequência de forças, cada uma durante um intervalo de tempo. Onde é que a partícula vai parar? Ou, melhor, em que posição estará a partícula depois da aplicação dessas forças e qual a sua velocidade nesse instante? (Sim, porque em geral a partícula não estará parada, pois não?) Claro que aqui nos baseamos na fórmula F = ma, onde F é a força, m é a massa e a é a aceleração. Na primeira linha do ficheiro de dados vem a posição inicial (coordenadas x e y) e a massa da partícula, tudo números reais. Seguem-se linhas em número indeterminado, cada uma com três números reais: a força aplicada, a direcção da força em graus (zero graus é o sentido do semi-eixo positivo dos x e os graus crescem no sentido contrário ao dos ponteiros do relógio) e a duração da aplicação da força em segundos. A aplicação da força a uma partícula deverá ser modelada com a função ApplyForce com os três argumentos citados (intensidade da força, direcção e duração). Eis um exemplo: 1.0 2.0 10.0 20.0 0.0 3.0 30.0 90.0 1.0 10.0 225.0 4.0 8. O correspondente ficheiro de resultados tem duas linhas, cada uma com dois números reais escritos em notação de ponto fixo com duas casas decimais e separados por um espaço. Na primeira linha vêm as coordenadas x e y da partícula e na segunda as componentes x e y da velocidade, tudo calculado para o instante em que a última força cessa de ser aplicada. Com o exemplo acima, o resultado deve ser o seguinte. 34.34 9.84 3.17 0.17 9. Nesta tarefa, deve usar as classes Particle e Point fornecidas junto com este enunciado (pode encontrar o diagrama UML correspondente em baixo também). Estas classes não estão na biblioteca que o Mooshak linca com o seu programa. Por isso, tem de as submeter no seu ficheiro zip. Repare que a força aplicada a uma partícula num espaço a duas dimensões pode ser decomposta em duas componentes, uma segundo o eixo dos xx e outra dos yy, usando para isso as funções trigonométricas do seno e co-seno (atenção que estas funções esperam que os argumentos estejam em radianos e não em graus, pelo que é possível que lhe dê jeito usar a constante Math.PI). Sabendo a força e a massa sabe-se a aceleração segundo cada um dos eixos e, por isso basta aplicar as leis da cinética separadamente para calcular as posições finais e as velocidades finais segundo esses mesmos eixos. Para resolver este passo crie a classe MovingParticle, que herda de Particle, mas que, para além de saber a sua posição, sabe também a sua velocidade. Nesta nova classe crie a função ApplyForce atrás citada. Se este passo A lhe parecer demasiado complicado, passe ao seguinte e volte aqui depois de acabar o passo C. É que uma boa solução para o problema A é a inclusão de dois tacógrafos como variáveis da classe MovingParticle, um para o movimento segundo o eixo dos xx e outro segundo o dos yy. Mas para isso tem de construir o tacógrafo primeiro, não é? ☺ 10. Quanto à precisão dos cálculos, como os resultados são escritos com apenas duas casas decimais, nunca haverá problemas de falta de precisão, a não ser se obtivermos um valor muito próximo de zero, mas inferior, situação esta em que apareceria “-0.00”. Pois bem, o seu programa não deve nunca escrever -0.00. (Esta observação aplica-se, com as devidas adaptações, no resto deste guião.) Nos ficheiros oficiais no Mooshak todos os zeros de tipo double são escritos sem sinal, garantidamente. 11. Regressemos à nossa classe Speedometer, para programar as funções. Não se esqueça de declarar a constante estática com o valor adequado. Ao programar, considere que a velocidade não pode ser negativa nem maior do que a velocidade máxima. Acelerar ou travar para fora desses limites não tem efeito. 12. A tarefa B é a função de teste da classe Speedometer. A primeira linha do ficheiro de dados contém um número real, a velocidade máxima. Seguem-se linhas de comando que começam por “a”, “b”, “t”, “s”, “u” ou “d”, para testar as funções Accelerate, Brake, Travel, Stop, SetSpeedUnits e Display, respectivamente. As linhas que começam por “a” e “b” contêm mais dois números reais positivos que representam a aceleração e a duração da aceleração. No caso do comando “a” o veículo acelera; no caso do comando “b” o veículo trava. (Já sabemos que na travagem a aceleração é negativa, mas é aqui dada em valor absoluto.) As linhas que começam por “t” contêm mais um número real que representa a duração do intervalo de tempo durante o qual o veículo segue com velocidade constante. As linhas que começam por “s” contêm mais um número real que representa a aceleração dessa travagem, que continua até o veículo se imobilizar. As linhas que começam por “u” têm ainda uma cadeia “m/s”, “km/h” ou “mph” com o significado anteriormente explicado registam que a partir de agora a função Display usa as unidades indicadas. As linhas começadas por “d” não têm mais nada. Em resposta ao comando “d”, o programa invoca a função Display para escrever na consola, numa linha, uma mensagem com a velocidade e a distância percorrida, no formato descrito anteriormente. Já agora crie uma outra chamada DisplayLn que faz o mesmo mas salta para a linha seguinte (à semelhança do println). Eis um ficheiro de teste: 30 a 1 20 d t 5 a 3 5 d t 1000 b 2 1 t 3000 d u km/h d s 4 d 13. Note que os valores numéricos neste ficheiro de entrada são números reais (embora neste pequeno exemplo sejam todos inteiros) e, por isso, devem ser lidos para variáveis double. O correspondente ficheiro de resultados é o seguinte: 20.000 m/s 0:00:20.000 30.000 m/s 0:00:30.000 28.000 m/s 1:07:11.000 100.800 km/h 1:07:11.000 0.000 km/h 1:07:18.000 14. Pronto, a classe de base já está. 15. Um velocímetro é um instrumento muito útil, mas ficava ainda melhor se tivesse um totalizador de distância percorrida. Pois bem, não vamos mexer na nossa classe Speedometer, que já está pronta e testada, vamos sim declarar uma classe derivada, isto é, uma nova classe que herda de Speedometer e que acrescenta esta funcionalidade de totalizar a distância. 16. Declare então uma classe Tachograph, derivada de Speedometer, com três novas funções: Distance, que calcula a distância percorrida desde o início ou desde a mais recente reiniciação do totalizador, AverageSpeed, que dá a velocidade média desde a mais recente reiniciação do totalizador (ou desde o início, se o totalizador nunca tiver sido reiniciado), e Reset, que reinicia o totalizador, colocando-o a zero. A classe derivada redefine também as funções herdadas Travel, Accelerate, Brake e Stop, pois essas funções precisam agora de actualizar a distância percorrida. A função Display também deve ser redefinida, de maneira a mostrar também a distância percorrida e a velocidade média, nas unidades correspondentes às da velocidade. A distância percorrida e a velocidade média são escritas com uma casa decimal e com a indicação das unidades utilizadas. Assim a mensagem escrita pela função Display contém, para além do que já é apresentado por Speedometer.Display(), a distância percorrida, as unidades da distância percorrida (“m”, “km” ou “miles”), a velocidade média e as unidades da velocidade. Entra cada dois destes elementos de informação vem um espaço. 17. Programe agora as três novas funções, as funções redefinidas e o construtor. Em relação à velocidade média, o caso em que a duração é zero (imediatamente no início ou após um Reset) dá convencionalmente velocidade média zero. Como exemplo, observe a programação da função Travel, sendo distance_ o membro de dados da classe Tachograph que representa a distância percorrida: void Travel (double duration) { super.Travel (duration); distance_ += Speed() * duration; } 18. A tarefa C é uma função de teste para a classe Tacograph. O funcionamento é análogo ao da função para a tarefa B, mas há mais um comando, “r”, que reinicia o totalizador. Todos os outros comandos são como antes, mas agora o comando “d” serve para invocar a função Display redefinida. Uma vez mais tenha atenção que os valores numéricos no ficheiro de entrada são números reais, embora no exemplo que se segue só apareçam inteiros: 30 a 2 10 d t 100 d b 3 2 r t 100 t 50 d u mph d 19. O correspondente ficheiro de resultados é o seguinte: 20.000 20.000 14.000 31.318 m/s m/s m/s mph 0:00:10.000 0:01:50.000 0:04:22.000 0:04:22.000 100.0 m 10.0 m/s 2100.0 m 19.1 m/s 2100.0 m 14.0 m/s 1.3 miles 31.3 mph 20. Como há tantos acidentes causados por excesso de velocidade e como nós, entretidos a conduzir, às vezes nem reparamos que vamos depressa demais, está em estudo um novo modelo de velocímetro que apita de cada vez que o veículo ultrapassa uma dada velocidade e que conta o número de vezes que isso acontece. Assim, no fim de uma viagem podemos perguntar ao velocímetro: quantas infracções cometi? Programe então uma nova classe SafetyTachograph, derivada de Tachograph, capaz de suportar essa funcionalidade. A classe deve ter uma função Reset para reiniciar a zero o contador de infracções a meio da viagem e pelo menos outra denominada Infractions que indica o número de infracções cometidas desde que o contador foi reiniciado (ou desde o início, se o totalizador nunca tiver sido reiniciado). A velocidade máxima e a velocidade de segurança devem ser estabelecidas no construtor. Repare que para se contabilizar uma nova infracção, é necessário que se regresse a uma situação de legalidade (em que a velocidade não exceda o limite de segurança) antes de a cometer. 21. A tarefa D é uma função para exercitar a classe SafetyTachograph. Aceita um ficheiro de comandos como o das outras tarefas, mas com ligeiras diferenças. A primeira linha contém agora dois números reais: a velocidade máxima (como antes) e a velocidade de segurança. Há um novo comando, o comando “i”, sem mais nada na linha, que invoca a função que reinicia o contador. Os comandos “a”, “b” e “s” são processados como antes (não se esqueça que podemos ter valores reais e não só inteiros). Os restantes são ignorados. No final a função escreve uma linha com um número inteiro: o número de infracções de excesso de velocidade registadas no velocímetro de segurança (desde o início da viagem ou desde a última vez que o contador de infracções foi reiniciado). Considere o seguinte ficheiro de dados, como exemplo: 50 15 a 3 4 t 5 a 1 2 t 5 a 2 1 i b 2 1 a 2 1 t 5 b 2 1 t 5 a 3 2 b 5 1 t 4 a 2 1 b 2 2 t 6 a 3 2 s 5 22. O correspondente ficheiro de resultados, só com uma linha, é o seguinte: 4 23. Conduza sempre com cuidado. E programe sempre com cuidado.