Notas de alunos
Parte 1.
Roberval, professor de português da rede pública, está aprendendo a programar. Para facilitar o gerenciamento das notas de seus estudantes, ele optou por implementar um programa orientado a objeto, em TypeScript, que possibilita cadastrar três notas para cada aluno, bem como obter a média alcançada por cada um desses estudantes. Apresente um código que poderia ser desenvolvido por Roberval para resolver este problema.
Uma forma de resolver envolve considerar que media
é um atributo derivado, ou seja, deve ser computado sempre que for requisitado. Neste caso, uma implementação adequada seria:
export class Aluno{
private _nome: string;
private _rga: string;
private _nota1: number = 0;
private _nota2: number = 0;
private _nota3: number = 0;
constructor(nome: string, rga: string){
this._nome = nome;
this._rga = rga;
}
get nome(): string{
return this._nome;
}
get rga(): string{
return this._rga;
}
get nota1(): number{
return this._nota1;
}
get nota2(): number{
return this._nota2;
}
get nota3(): number{
return this._nota3;
}
set nota1(nota: number){
this._nota1 = nota;
}
set nota2(nota: number){
this._nota2 = nota;
}
set nota3(nota: number){
this._nota3 = nota;
}
get media(){
return (this._nota1 + this._nota2 + this._nota3) / 3;
}
}
let pedro:Aluno = new Aluno("Pedro Silva", "1234234332");
pedro.nota1 = 7;
pedro.nota2 = 5;
pedro.nota3 = 7.5;
console.log(`Média de ${pedro.nome}: ${pedro.media}`);
Note que, no caso acima, não implementamos setters para nome
e rga
. Isso significa que esses dados não são alteráveis após a criação do objeto por elementos externos à classe.
Uma segunda possibilidade, agora considerando o armazenamento de média, seria:
export class Aluno{
private _nome: string;
private _rga: string;
private _nota1: number = 0;
private _nota2: number = 0;
private _nota3: number = 0;
private _media: number = 0;
constructor(nome: string, rga: string){
this._nome = nome;
this._rga = rga;
}
get nome(): string{
return this._nome;
}
get rga(): string{
return this._rga;
}
get nota1(): number{
return this._nota1;
}
get nota2(): number{
return this._nota2;
}
get nota3(): number{
return this._nota3;
}
set nota1(nota: number){
this._nota1 = nota;
}
set nota2(nota: number){
this._nota2 = nota;
}
set nota3(nota: number){
this._nota3 = nota;
}
get media(){
this._media = (this._nota1 + this._nota2 + this._nota3) / 3;
return this._media;
}
}
let pedro:Aluno = new Aluno("Pedro Silva", "1234234332");
pedro.nota1 = 7;
pedro.nota2 = 5;
pedro.nota3 = 7.5;
console.log(`Média de ${pedro.nome}: ${pedro.media}`);
A implementação acima é bastante interessante porque, no método get media()
, atualiza-se o valor do atributo privado _media
e, depois, esse atributo é retornado. Consegue observar alguma limitação com relação à essa estratégia?
🧠 Teste seus conhecimentos
Qual afirmação corretamente descreve um aspecto inerente à implementação anterior?
- A
O valor de média deve ser sempre acessado pelo método get, não pelo atributo em si, para garantir a validade do código
- B
O programa funcionará adequadamente independentemente da abordagem usada para acessar o valor da média, mesmo com futuras implementações de novos métodos.
- C
Os atributos deveriam ser públicos.
- D
A implementação apresentada não cumpre o critério de encapsulamento.
- E
Nenhuma das alternativas anteriores está correta.
Se você respondeu à pergunta anterior, já deve ter observado que, se um novo método for implementado nesta classe, existe a possibilidade de um equívoco ser ocasionado. Veja, por exemplo, o seguinte método:
export class Aluno{
/* todos os outros atributos e métodos apresentados anteriormente */
public consultarAprovacao(): boolean{
return this._media >= 5 ? true : false;
}
}
Agora, imagine que esse método seja chamado para o objeto pedro
. Qual será a saída do método na chamada a seguir?
let pedro:Aluno = new Aluno("Pedro Silva", "1234234332");
pedro.nota1 = 7;
pedro.nota2 = 5;
pedro.nota3 = 7.5;
console.log(pedro.consultarAprovacao());
Se você respondeu que a saída será false
, você acertou. Agora, vamos consultar uma segunda implementação possível para o método:
export class Aluno{
/* todos os outros atributos e métodos apresentados anteriormente */
public consultarAprovacao(): boolean{
return this.media >= 5 ? true : false;
}
}
let pedro:Aluno = new Aluno("Pedro Silva", "1234234332");
pedro.nota1 = 7;
pedro.nota2 = 5;
pedro.nota3 = 7.5;
E neste caso, a saída será false
também? Na verdade, não.
Isso ocorre porque, embora os dois trechos pareçam conter a mesma implementação, há uma sutil diferença: no primeiro caso, acessamos diretamente o valor do atributo media
. Se o método get media()
nunca foi chamado, o valor o atributo _media
nunca foi alterado (lembre-se que ele foi inicializado com 0
).
No segundo caso, o resultado é true
porque acessamos o valor da média não diretamente pelo atributo (_media
), mas sim pelo método get media()
. Ou seja: na nossa implementação, teríamos sempre que nos lembramos de acessar o valor pelo método, não pelo atributo.
Apesar de parecer uma desvantagem, essa é a abordagem mais interessante: mesmo na própria classe, é fortemente recomendado que acessemos os valores dos atributos não diretamente, mas sempre pelo intermédio do
get
equivalente. O mesmo se aplica ao acesso aos valores que, idealmente, deve ser feito peloset
do atributo.
De outro modo, poderíamos seguir por uma terceira abordagem:
export class Aluno {
private _nome: string;
private _rga: string;
private _nota1: number = 0;
private _nota2: number = 0;
private _nota3: number = 0;
private _media: number = 0;
constructor(nome: string, rga: string) {
this._nome = nome;
this._rga = rga;
}
get nome() : string {
return this._nome;
}
get rga() : string {
return this._rga;
}
get nota1() : number {
return this._nota1;
}
get nota2() : number {
return this._nota2;
}
get nota3() : number {
return this._nota3;
}
set nota1(nota: number) {
this._nota1 = nota;
this.calcularMedia();
}
set nota2(nota: number) {
this._nota2 = nota;
this.calcularMedia();
}
set nota3(nota: number) {
this._nota3 = nota;
this.calcularMedia();
}
private calcularMedia() : void {
this._media = (this._nota1 + this._nota2 + this._nota3) / 3;
}
get media() {
return this._media;
}
public consultarAprovacao() : boolean {
return this._media >= 5 ? true: false;
}
}
let pedro: Aluno = new Aluno("Pedro Silva", "1234234332");
pedro.nota1 = 7;
pedro.nota2 = 5;
pedro.nota3 = 7;
console.log(pedro.consultarAprovacao());
Agora, sempre que alteramos o valor de uma nota, atualizamos também o valor da média. Observe que:
Os métodos
setters
não devem incorporar muitas tarefas. Nesse caso específico, a atualização da média é admissível, já que este é um atributo computado, que deve ser atualizado sempre a partir dos valores denota1
,nota2
enota3
.
Apesar disso, essa abordagem de implementação ainda é menos vantajosa que a anterior.
Enunciado 2
Roberval, da atividade anterior, agora deseja também encontrar o aluno que obteve a menor média. Ele quer saber o nome e o número de matrícula desse estudante. Como ele pode implementar essa funcionalidade?
let AlunoComMenorMedia = alunos.reduce((menor, atual) => {
return atual.media < menor.media ? atual : menor;
});
console.log(AlunoComMenorMedia)
Enunciado 3
Agora, o incansável professor Roberval deseja listar todos os alunos que estão reprovados, considerando como critério para reprovação a média inferior a 5,0.
let alunosMediaMenor5 = alunos.filter((aluno) => aluno.media < 5);
console.log(alunosMediaMenor5);