Notenrechner (Neue OOP-Version von einer früheren)

Dec 19 2020

Ich versuche eine OOP-Denkweise zu entwickeln. Ich hatte gehofft, dass jemand so freundlich sein würde, einen Teil seiner wertvollen Zeit für die Überprüfung meines OOP-Programms für den Notenrechner zu sparen. Wie immer würde ich gerne wissen, was ich gut gemacht habe, was ich verbessern sollte und welche Vorschläge ich machen könnte, was ich vielleicht verbessern könnte? Ich habe übrigens eine Klasse namens Class. Ich sollte es wahrscheinlich mit "cls" voranstellen, um nicht zu verwirren. Behandle dieses Programm so, wie es eingegeben werden soll, ich habe es nicht fehlerhaft überprüft. Der Zweck dieses Programms ist die Entwicklung in 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();
   
}

Antworten

6 Edward Dec 19 2020 at 09:35

Hier sind einige Dinge, die Ihnen helfen sollen, Ihr Programm zu verbessern.

Mischen Sie E / A nicht mit Datenoperationen

Im Allgemeinen ist es keine gute Idee , ein std::getlineund std::coutinnerhalb einer Datenklasse wie zu haben Class. Es macht es schwieriger, die Klasse wiederzuverwenden. Bessere Praxis ist es, die Daten zu halten ( Student, Classusw.) getrennt von immer Eingaben von Benutzern. Das Entwurfsmuster Model-View-Controller ist häufig für solche Programme nützlich. Verwenden Sie so etwas wie eine ConsoleMenuKlasse wie in dieser Antwort .

Verwenden Sie die Bewegungssemantik, wo dies praktikabel ist

In der AddStudentFunktion haben wir diese Zeile:

students.push_back({ name, scoreGot });

Besser wäre es, eine zu verwenden, emplace_backdie dem Compiler mitteilt, dass er nicht konstruieren und kopieren muss, sondern dass es sicher ist, das Objekt an Ort und Stelle zu konstruieren.

Überdenken Sie die Klassenschnittstelle

Die ClassKlasse hat diese Mitgliedsfunktion:

std::vector<Student>& accessStudents() { return students; }

Das ist eine schlechte Idee. Es gibt einen Verweis auf ein internes Klassenmitglied zurück. Überlegen Sie, was passiert, wenn die ClassInstanz gelöscht wird, aber eine externe Entität immer noch einen Verweis auf Daten enthält, die nicht mehr vorhanden sind. Der einzige Ort, an dem es verwendet wird, ist innerhalb GradeCalculator::averageGrade()und GradeCalculator::DislayResult()das ist ein starker Indikator dafür, dass etwas in der Klassenschnittstelle nicht stimmt. Ich würde vorschlagen, die averageGrade()Funktion zu einer ClassMitgliedsfunktion zu machen.

Verwenden Sie Standardeinstellungen, wenn dies sinnvoll ist

Der StudentKonstruktor ist im Wesentlichen derselbe wie der, der vom Compiler generiert worden wäre. Um die Fehlerwahrscheinlichkeit zu verringern, kann sie einfach beseitigt werden.

Denken Sie an den Benutzer

Es gibt keine offensichtliche Möglichkeit, das Programm ordnungsgemäß zu beenden. Ich würde vorschlagen, einen Menüpunkt dafür hinzuzufügen. Es besteht auch die Möglichkeit, dass jeder, der das Programm tatsächlich verwendet, die gesamte Klasse von Schülern eingeben und dann die Punktzahlen für die gesamte Klasse berechnen möchte. Die wiederholte Auswahl von "1. Schüler hinzufügen ..." ist etwas mühsam. Besser ist es, wenn das Programm Eingaben sammelt, bis ein leerer Name oder "Beenden" eingegeben wurde, und dann automatisch die Ergebnisse anzeigt. Besser noch wäre es, Eingaben aus einer Textdatei zuzulassen.