Calculadora de notas (nova versão OOP de uma anterior)
Estou tentando desenvolver uma maneira OOP de pensar. Eu esperava que alguém fosse gentil o suficiente para gastar algum de seu valioso tempo para revisar meu programa Calculadora de notas OOP. Como sempre, gostaria de saber o que fiz bem, o que devo melhorar e alguma sugestão de como talvez pudesse melhorar o que tenho. A propósito, tenho uma classe chamada Classe. Eu provavelmente deveria prefixá-lo com "cls" para não confundir. Trate este programa como deve ser inserido, não o verifiquei por erro. O objetivo deste programa é desenvolver em OOP.
// Task 1.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
#include <string>
class TestPaper
{
public:
int m_scoreOutOf;
bool checkBoundary(int value, int boundary) {
if (value < 0 || value > boundary) {
std::cout << "Score must be between " << " 0 and " << boundary << ". Please try again.\n";
return false;
}
return true;
}
};
class Student {
private:
std::string m_name;
int m_scoreGot;
public:
Student(std::string name, int scoreGot)
:m_name(name), m_scoreGot(scoreGot){}
std::string getName() const { return m_name; }
int getScoreGot() const { return m_scoreGot; }
};
class Class {
private:
std::vector<Student>students;
public:
void AddStudent(TestPaper& testPaper) {
std::string name = "";
int scoreGot = 0;
std::cout << "Enter student name: ";
std::getline(std::cin >> std::ws, name);
do
{
std::cout << "\nWhat did " << name << " score?\nEnter a score between 0 and "
<< testPaper.m_scoreOutOf << ": ";
std::cin >> scoreGot;
} while (testPaper.checkBoundary(scoreGot, testPaper.m_scoreOutOf) == false);
students.push_back({ name, scoreGot });
}
std::vector<Student>& accessStudents() { return students; }
};
class GradeCalculator {
TestPaper m_testPaper;
Class m_ClassOfStudents;
public:
GradeCalculator(TestPaper testPaper, Class classOfStudents) :m_testPaper(testPaper), m_ClassOfStudents(classOfStudents) {}
void DisplayMenu() {
std::cout << "\n1. Add student and their grade\n";
std::cout << "2. Calculate class score\n";
std::cout << "3. Modify testpaper (haven't implemented this yet)\n";
}
double averageGrade() {
auto sum = std::transform_reduce(m_ClassOfStudents.accessStudents().begin(), m_ClassOfStudents.accessStudents().end(), 0.0, std::plus<>(),
[&](auto& student) { return calculateGradePercentage(student); });
return sum / m_ClassOfStudents.accessStudents().size();
}
double calculateGradePercentage(Student &student)
{
return static_cast<double>(student.getScoreGot()) / static_cast<double>(m_testPaper.m_scoreOutOf) * 100;
}
void DisplayResult() {
for (auto& student : m_ClassOfStudents.accessStudents()) {
std::cout << "Percentage scores are: \n";
std::cout << student.getName() << ": " << calculateGradePercentage(student) << "%\n";
}
std::cout << "Average grade perecentage: " << averageGrade() << "%\n";
}
void runProgram() {
int menuChoice = 0;
while (true)
{
DisplayMenu();
std::cout << "\nEnter a choice from the menu: ";
std::cin >> menuChoice;
switch (menuChoice)
{
case 1:
m_ClassOfStudents.AddStudent(m_testPaper);
break;
case 2:
DisplayResult();
break;
default:
std::cout << "Invalid choice!\n";
}
}
}
};
int main()
{
TestPaper testPaper({ 20 });
Class classOfStudents;
GradeCalculator calculator(testPaper, classOfStudents);
calculator.runProgram();
}
Respostas
Aqui estão algumas coisas para ajudá-lo a melhorar seu programa.
Não misture E / S com operações de dados
De modo geral, ter um std::getline
e std::cout
dentro de uma classe de dados como Class
não é uma boa ideia. Isso torna mais difícil reutilizar a classe. Melhor prática é manter os dados ( Student
, Class
, etc.) separar recebendo entrada de usuários. O padrão de projeto Model-View-Controller é freqüentemente útil para programas como este. Considere usar algo como uma ConsoleMenu
classe nesta resposta .
Use a semântica de movimento onde for prático
Na AddStudent
função, temos esta linha:
students.push_back({ name, scoreGot });
Melhor seria usar o emplace_backque informa ao compilador que ele não precisa construir e copiar, mas que é seguro construir o objeto no local.
Repense a interface da classe
A Class
classe tem esta função de membro:
std::vector<Student>& accessStudents() { return students; }
Esta é uma má ideia. Ele retorna uma referência a um membro da classe interna. Pense no que acontecerá se a Class
instância for excluída, mas alguma entidade externa ainda estiver mantendo uma referência aos dados que não existem mais. O único lugar em que é usado é dentro GradeCalculator::averageGrade()
e, GradeCalculator::DislayResult()
portanto, é um forte indicador de que algo não está certo na interface da classe. Eu sugiro tornar a averageGrade()
função uma Class
função de membro.
Use padrões quando for sensato
O Student
construtor é essencialmente o mesmo que seria gerado pelo compilador. Para reduzir a chance de erro, ele pode ser simplesmente eliminado.
Pense no usuário
Não existe uma maneira óbvia de sair do programa normalmente. Eu sugiro adicionar um item de menu para isso. Além disso, é provável que qualquer pessoa que esteja realmente usando o programa queira inserir toda a classe de alunos e, em seguida, calcular as pontuações de toda a classe. Ter que selecionar repetidamente "1. Adicionar aluno ..." é um pouco tedioso. Melhor pode ser fazer com que o programa reúna dados até que um nome vazio ou talvez "quit" seja inserido e, em seguida, mostre automaticamente as pontuações. Melhor ainda seria permitir a entrada de um arquivo de texto.