Система оценок университетов (ООП)

Я продолжаю свое развитие и понимание ООП (объектно-ориентированного программирования) и буду признателен за отзывы об этой новой консольной программе, которую я создал. Основные аспекты, которые я хотел бы знать: хорошо ли я использовал ООП, могу ли я улучшить методы, продемонстрированные в этой программе? Я сделал что-нибудь плохое? Я понимаю, что должен загружать данные из текстового файла, но это не суть этой программы. Для быстрого запуска программы я поместил образцы данных, которые генерируются в конструкторе «Университет». Я также понимаю, что это, вероятно, не лучшее имя для называть мой класс. Я основывал эту программу на следующем сценарии. Большое спасибо всем, кто дает мне отзывы!
введите описание изображения здесь

// Progress of Students Calculator.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <map>
class User 
{
    static int currentID;
    int m_ID;
    std::string m_Name;
public:
    User(std::string name) : m_Name{ name }, m_ID {currentID++}{
}
    int ID() const { return m_ID; }
    std::string name() const { return m_Name; }
};
int User::currentID = 1;
class Subject {
    static int currentID;
    int m_ID;
    std::string m_Name;
public:
    Subject(std::string name) :m_ID{ currentID++ }, m_Name{ name }{}
    int ID()const { return m_ID; }
    std::string name()const { return m_Name; }
};
int Subject::currentID = 1;
class Department {
    static int currentID;
    int m_ID;
    std::string m_Name;
public:
    Department( std::string name) :m_ID{ currentID++ }, m_Name{ name }{}
    int ID()const { return m_ID; }
    std::string name()const { return m_Name; }
};
int Department::currentID = 1;

class Lecturer : public User {
    int m_SubjectID;
    int m_DepartmentID;
public:
    Lecturer(std::string name, int subjectID, int departmentID) 
        :User(name), m_SubjectID(subjectID), m_DepartmentID(departmentID) {}
    int subjectID()const { return m_SubjectID; }
    int departmentID()const { return m_DepartmentID; }
};

class Student : public User {
    int m_ClassID;
    int m_Result;
public:
    Student(std::string name, int classID, int result)
        :User(name), m_ClassID(classID), m_Result(result) {}
    int classID() const { return m_ClassID; }
    int result() const { return m_Result; }
   
};

class Class 
{
    static int currentID;
    int m_ID;
    int m_LecturerID;
    int m_Size;
    int m_TotalScore;

public:
    Class(int lecturerID) 
        :m_ID{ currentID++ }, m_LecturerID{ lecturerID }, m_Size{ 0 }, m_TotalScore{0}
    {
    }
    int ID()const { return m_ID; }
    int lecturer_ID() const { return m_LecturerID; }
    int size() const { return m_Size; }
    void increment_size() { m_Size++; }
    int total_score() const { return m_TotalScore; }
    void total_score(int result) { m_TotalScore += result; }
};
int Class::currentID = 1;

    class University {
    private:
        std::vector<Department>objDepartments;
        std::vector<Subject>objSubjects;
        std::vector<Lecturer>objLecturers;
        std::vector<Student>objStudents;
        std::vector<Class>objClasses;

        auto find_lecturer_by_id(int ID) {

            return std::find_if(objLecturers.begin(), objLecturers.end(), [&](Lecturer lecturer) {return lecturer.ID() == ID; });
        }
        auto find_subject_by_id(int ID) {

            return std::find_if(objSubjects.begin(), objSubjects.end(), [&](Subject subject) {return subject.ID() == ID; });
        }
        auto find_department_by_id(int ID) {
            return std::find_if(objDepartments.begin(), objDepartments.end(), [&](Department department) {return department.ID() == ID; });
        }
        auto find_class_by_id(int ID) {
            return std::find_if(objClasses.begin(), objClasses.end(), [&](Class objClass) {return objClass.ID() == ID; });
        }
    public:
        University() {
            objSubjects.emplace_back(Subject{"Programming" });
            objSubjects.emplace_back(Subject{"Web App Development" });
            objSubjects.emplace_back(Subject{"Networking" });
            objDepartments.emplace_back(Department{"Winter Gardens" });
            objDepartments.emplace_back(Department{"South West Skills Campus" });
            objLecturers.emplace_back(Lecturer{"Sean Shearing", 1, 1 });
            objClasses.emplace_back(Class{ 1 });
            objClasses.emplace_back(Class{ 1 });

            //Test data
            add_student("Jack Cole", 1, 56);
            add_student("Sam Cole", 1, 78);
            add_student("Devon Phillips", 1, 91);
            add_student("Thomas Saunders", 1, 54);
            add_student("Jack Kimmins", 2, 58);
            add_student("Oli Stahmer", 2, 91);
            add_student("George Bradley", 2, 78);   
            add_student("Joshua Price", 2, 54);                
        }

        auto best_performing_class() {
            return std::max_element(objClasses.begin(), objClasses.end(), [&](const Class& lhs, const Class& rhs)
                {
                    return lhs.total_score() < rhs.total_score();
                });
        }
        auto worst_performing_class() {
            return std::min_element(objClasses.begin(), objClasses.end(), [&](const Class& lhs, const Class& rhs)
                {
                    return lhs.total_score() < rhs.total_score();
                });
        }
        double average(std::vector<Student>& students) {
            
            auto sum = std::transform_reduce(students.begin(), students.end(), 0.0, std::plus<>(),
                [](auto& student) { return student.result(); });
            return sum / objStudents.size();
        }
        auto find_students_in_class(int classID) {
            std::vector<Student>students;
            for (const Student& objStudent : objStudents) {

                if (objStudent.classID() == classID) {
                    students.push_back({ objStudent });
                }
            }
            return students;
        }

        int class_median_score(int class_id) {
            std::vector<int>scores;
            auto students = find_students_in_class(class_id);
            for (Student& student : students) {
                scores.push_back(student.result());
            }
            std::sort(scores.begin(), scores.end()); 
            if (scores.size() % 2 == 0) 
            {
                return (scores.at(scores.size() / 2  - 1) + scores.at(scores.size()/2)) / 2;                        
            }
            return scores.at(scores.size()/2);
        }
        void view_class_students(int classID) {
            auto students = find_students_in_class(classID);
            display_students(students);
            
        }
        void display_students(std::vector<Student>& objStudents) {
            for (const Student& objStudent : objStudents) {
                std::cout << "ID: " << objStudent.ID() << " | ";
                std::cout << "Name: " << objStudent.name() << " | ";
                std::cout << "Score: " << objStudent.result() << "n";
            }
        }
        auto highest_mark_by_student(std::vector<Student>& objStudents) {
            return std::max_element(objStudents.begin(), objStudents.end(), [&](const Student& lhs, const Student& rhs)
                {
                    return lhs.result() < rhs.result();
                });
        }
        auto lowest_mark_by_student(std::vector<Student>& objStudents) {
            return std::min_element(objStudents.begin(), objStudents.end(), [&](const Student& lhs, const Student& rhs)
                {
                    return lhs.result() < rhs.result();
                });
        }
       
        void class_report(int class_id) 
        {
            auto students = find_students_in_class(class_id);
            auto highestScorer = highest_mark_by_student(students);
            auto lowestScorer = lowest_mark_by_student(students);
            std::cout << "Highest scorer: Student ID: " << highestScorer->ID() << " | Name: " << highestScorer->name() << " | score: " << highestScorer->result() <<"n";         
            std::cout << "Lowest scorer: Student ID: " << lowestScorer->ID() << " | Name: " << lowestScorer->name() << " | score: " << lowestScorer->result() << "n";
            std::cout << "Median score: " << class_median_score(class_id) << "n";
            std::cout << "Average score: " << average(students) << "n";
        }

        void overall_classes_report() {
            auto bestClass = best_performing_class();
            auto worstClass = worst_performing_class();
            std::cout << "nBest Performing Class(s)...n";
            std::cout << "ID: " << bestClass->ID() << " | Lecturer: " << find_lecturer_by_id(bestClass->lecturer_ID())->name() << " | score: " << bestClass->total_score() << "n";
            std::cout << "n";
            std::cout << "Worst Performing Class(s)...n";
            std::cout << "ID: " << worstClass->ID() << " | Lecturer: " << find_lecturer_by_id(worstClass->lecturer_ID())->name() << " | score: "<< worstClass->total_score() << "n";
            std::cout << "n";
        }
       
        void display_classes() {
            for (const Class& objClass : objClasses)
            {
                auto lecturer = find_lecturer_by_id(objClass.lecturer_ID());
                auto subject = find_subject_by_id(objClass.lecturer_ID());
                auto department = find_department_by_id(lecturer->departmentID());
                auto students = find_students_in_class(objClass.ID());
               
                std::cout << "Class ID: " << objClass.ID() << "n";
                std::cout << "Class Size: " << objClass.size() << "n";
                std::cout << "Lecturer: " << lecturer->name() << "n";
                std::cout << "Subject: " << subject->name() << "n";
                std::cout << "Department: " << department->name() << "nn";     
            }
        }
  
        bool is_class_id_valid(int class_id) {
            if (auto it = std::find_if(objClasses.begin(), objClasses.end(), [&](auto &objClass) {
                return class_id == objClass.ID();
                }) == objClasses.end()) {
                return false;
            }
            return true;
        }

        bool add_student(std::string name, int classID, int result) {

            if (is_class_id_valid(classID)) {
                objStudents.emplace_back(Student{ name, classID, result });
                find_class_by_id(classID)->total_score(result);
                find_class_by_id(classID)->increment_size();
                return true;
            }
            return false;
        }
    };

int main()
{
    University objUniversity;

    //viewing class 1
    std::cout << "View class students...n";
    objUniversity.view_class_students(1);

    std::cout << "Class report...n";
    objUniversity.class_report(1);

    //viewing class 2
    std::cout << "View class students...n";
    objUniversity.view_class_students(2);

    std::cout << "Class report...n";
    objUniversity.class_report(2);

    std::cout << "Overall classes report...n";
    objUniversity.overall_classes_report();
    
    //Run via menu system
    //menu_options(objUniversity);
}

1 ответ
1

то есть

Похоже, что в спецификации нет никаких требований для Subject или Department быть более чем простым std::stringс.


идентификаторы обработки:

Есть много повторяющегося кода для обработки идентификаторов. Мы могли бы включить это в класс примерно следующего вида (на самом деле не тестировался):

template<class T>
class ID
{
public:
    
    ID():
        m_ID(newID()) { }
    
    ID(ID const&) = default;
    ID& operator=(ID const&) = default;
    ID(ID&&) = default;
    ID& operator=(ID&&) = default;
    
private:
    
    static int newID()
    {
        static int id = 0;
        return id++;
    }
    
    friend bool operator==(ID a, ID b) { return a.m_ID == b.m_ID; }
    friend bool operator!=(ID a, ID b) { return !(a == b); }
    
    int m_ID;
};

А затем используйте это как:

struct Lecturer
{
    ID<Lecturer> m_ID;
    ...
};

struct Student
{
    ID<Student> m_ID;
    ...
};

С аргументом шаблона мы получаем безопасность типов: мы не можем передать идентификатор преподавателя функции, ожидающей идентификатор студента (и наоборот), потому что они имеют разные типы. Аналогичным образом operator== и operator!= позволяют сравнивать идентификаторы одного типа, но не идентификаторы разных типов.

Обратите внимание, что мы получаем отдельный счетчик для каждого типа.

Мы могли бы добавить operator>> при желании.

(Обратите внимание, что здесь нет необходимости в общем базовом классе. Различные типы не связаны между собой, и нам нет необходимости хранить их вместе или обрабатывать их одним интерфейсом.)


исправить спецификацию!

Есть пара вещей, которые я бы поставил под вопрос по поводу дизайна:

  • Действительно ли студенты учатся только в одном классе?
  • Почему в классе есть переменная размера?

Я бы, вероятно, удалил идентификатор класса и переменную результата из ученика и сохранил std::vector студенческих билетов и их результатов в классе.


исправить ошибки:

    auto best_performing_class() {
        return std::max_element(objClasses.begin(), objClasses.end(), [&](const Class& lhs, const Class& rhs)
            {
                return lhs.total_score() < rhs.total_score();
            });
    }

    auto worst_performing_class() {
        return std::min_element(objClasses.begin(), objClasses.end(), [&](const Class& lhs, const Class& rhs)
            {
                return lhs.total_score() < rhs.total_score();
            });
    }

В них следует использовать средний балл, а не общий балл. (Маленький класс с высокими баллами должен быть лучше, чем большой класс с низкими баллами).


исправить ошибку, сделайте самое простое:

    double average(std::vector<Student>& students) {
        
        auto sum = std::transform_reduce(students.begin(), students.end(), 0.0, std::plus<>(),
            [](auto& student) { return student.result(); });
        return sum / objStudents.size();
    }

Я не думаю, что ты хочешь objStudents там.

Простой цикл был бы намного понятнее:

auto sum = 0;
for (auto const& student : students)
    sum += student.result();

return (double)sum / (double)students.size();

явно обрабатывать крайние случаи, используйте правильный тип:

        if (scores.size() % 2 == 0) 
        {
            return (scores.at(scores.size() / 2  - 1) + scores.at(scores.size()/2)) / 2;                        
        }
        return scores.at(scores.size()/2);

Что если scores пусто? Если предварительным условием для этой функции является то, что scores не пусто, мы могли бы показать это явно с помощью assert(!scores.empty()), или же if (scores.empty()) throw std::runtime_error("...");

Вероятно, нам следует вернуть double здесь тоже для более точного результата.


const правильность:

Все функции расчета / печати должны быть отмечены const, поскольку они не изменяют данные членов класса (но также смотрите о статических функциях ниже).

Любые переменные, переданные по ссылке, где нам не нужно изменять переменную, также должны быть отмечены const.

например:

double average(const std::vector<Student>& students) const { ...

предпочитаю static для вспомогательных функций:

Вместо того, чтобы напрямую обращаться к данным членов класса во вспомогательной функции, часто проще сделать функции static. Это делает входные данные явными и очевидными и помогает предотвратить путаницу, например objStudents против students выше:

static double average(const std::vector<Student>& students) { ...

Теперь мы не можем получить доступ к данным класса внутри функции — только к указанным аргументам.

Большинство расчетных функций было бы лучше, если бы static функции.


  • Замечательный обзор! Спасибо огромное! Для меня это был огромный опыт обучения! Да, спецификация немного шаткая, но я делал то, о чем она просила. Я собираюсь получить удовольствие от перенастройки этой программы! Я бесконечно благодарен. Позже у меня могут возникнуть вопросы.

    — Джордж Остин Брэдли

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *