Calculateur de notes (nouvelle version de POO d'une précédente)
J'essaye de développer une façon de penser POO. J'espérais que quelqu'un serait assez aimable pour épargner une partie de son temps précieux pour revoir mon programme POO de calculatrice de notes. Comme toujours, j'aimerais savoir ce que j'ai bien fait, ce que je devrais améliorer et des suggestions sur la façon dont je pourrais peut-être améliorer ce que j'ai? Au fait, j'ai une classe appelée Classe. Je devrais probablement le préfixer avec "cls" pour ne pas confondre. Traitez ce programme comme il est censé être entré, je ne l'ai pas vérifié par erreur. Le but de ce programme est de se développer en POO.
// 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();
}
Réponses
Voici quelques éléments pour vous aider à améliorer votre programme.
Ne mélangez pas les E / S avec les opérations de données
En règle générale, avoir un std::getline
et std::cout
dans une classe de données telle que Class
n'est pas une bonne idée. Cela rend plus difficile la réutilisation de la classe. Une meilleure pratique consiste à conserver les données ( Student
, Class
, etc.) séparée de recevoir les commentaires des utilisateurs. Le modèle de conception Model-View-Controller est souvent utile pour des programmes comme celui-ci. Pensez à utiliser quelque chose comme une ConsoleMenu
classe comme dans cette réponse .
Utilisez la sémantique de déplacement lorsque cela est pratique
Dans la AddStudent
fonction, nous avons cette ligne:
students.push_back({ name, scoreGot });
Mieux vaut utiliser emplace_backce qui indique au compilateur qu'il n'a pas besoin de construire et de copier, mais qu'il est sûr de construire l'objet en place.
Rethink the class interface
The Class
class has this member function:
std::vector<Student>& accessStudents() { return students; }
This is a bad idea. It returns a reference to an internal class member. Think what happens if the Class
instance is deleted but some external entity is still holding a reference to data that no longer exists. The only place it's used is within GradeCalculator::averageGrade()
and GradeCalculator::DislayResult()
so that's a strong indicator that something is not right in the class interface. I'd suggest making the averageGrade()
function a Class
member function.
Use defaults when sensible
The Student
constructor is essentially the same as that which would have been generated by the compiler. To reduce the chance of error, it can be simply eliminated.
Think about the user
There is no obvious way to gracefully exit the program. I'd suggest adding a menu item for that. Also, chances are that anyone actually using the program would want to enter the entire class of students and then calculate the scores for the whole class. Having to repeatedly select "1. Add student..." is a bit tedious. Better might be to have the program gather input until an empty name or maybe "quit" was entered, and then automatically show the scores. Better still would be to allow input from a text file.