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