Thực hiện chương trình tính điểm bằng kỹ thuật kết hợp
Dưới đây là triển khai lớp GradeCalc sử dụng phương pháp kết hợp (composition), trong đó dữ liệu điểm số được lưu trữ bên trong thông qua một thành viên kiểu std::vector<int>.
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
class GradeCalc {
public:
explicit GradeCalc(const std::string& courseName)
: course_name{courseName}, is_dirty{true} {
counts.fill(0);
rates.fill(0.0);
}
void input(int n) {
if (n < 0) {
std::cerr << "Dữ liệu không hợp lệ! Số lượng sinh viên không thể âm.\n";
return;
}
grades.reserve(n);
int score;
for (int i = 0; i < n; ++i) {
std::cin >> score;
while (score < 0 || score > 100) {
std::cerr << "Điểm không hợp lệ! Vui lòng nhập lại (0-100): ";
std::cin >> score;
}
grades.push_back(score);
}
is_dirty = true;
}
void output() const {
for (const auto& g : grades)
std::cout << g << ' ';
std::cout << '\n';
}
void sortAscending() {
std::sort(grades.begin(), grades.end());
}
void sortDescending() {
std::sort(grades.begin(), grades.end(), std::greater<>{});
}
int getMin() const {
if (grades.empty()) return -1;
return *std::min_element(grades.begin(), grades.end());
}
int getMax() const {
if (grades.empty()) return -1;
return *std::max_element(grades.begin(), grades.end());
}
double getAverage() const {
if (grades.empty()) return 0.0;
return static_cast<double>(std::accumulate(grades.begin(), grades.end(), 0)) / grades.size();
}
void showSummary() {
if (is_dirty) updateStatistics();
std::cout << "Môn học:\t" << course_name << "\n";
std::cout << "ĐTB:\t" << std::fixed << std::setprecision(2) << getAverage() << "\n";
std::cout << "Cao nhất:\t" << getMax() << "\n";
std::cout << "Thấp nhất:\t" << getMin() << "\n";
std::array<std::string, 5> ranges{"[0,60)", "[60,70)", "[70,80)", "[80,90)", "[90,100]"};
for (int i = 4; i >= 0; --i) {
std::cout << ranges[i] << ":\t" << counts[i] << " người\t"
<< std::setprecision(2) << (rates[i] * 100.0) << "%\n";
}
}
private:
void updateStatistics() {
if (grades.empty()) return;
counts.fill(0);
rates.fill(0.0);
for (const auto& g : grades) {
if (g < 60) ++counts[0];
else if (g < 70) ++counts[1];
else if (g < 80) ++counts[2];
else if (g < 90) ++counts[3];
else ++counts[4];
}
for (size_t i = 0; i < rates.size(); ++i) {
rates[i] = static_cast<double>(counts[i]) / grades.size();
}
is_dirty = false;
}
private:
std::string course_name;
std::vector<int> grades;
std::array<int, 5> counts;
std::array<double, 5> rates;
bool is_dirty;
};
Mục đích của cờ is_dirty là tối ưu hiệu suất — chỉ cập nhật thống kê khi dữ liệu điểm thay đổi, tránh thực hiện tính toán dư thừa.
Triển khai kế thừa tư nhân từ std::vector
Lớp GradeCalc cũng có thể được thiết kế bằng cách kế thừa tư nhân từ std::vector<int>, biến chính nó thành một container đặc biệt hóa:
class GradeCalc : private std::vector<int> {
public:
explicit GradeCalc(const std::string& name) : course_name{name}, is_dirty{true} {
counts.fill(0); rates.fill(0.0);
}
using std::vector<int>::empty;
using std::vector<int>::size;
using std::vector<int>::begin;
using std::vector<int>::end;
void input(int n) {
if (n <= 0) return;
this->reserve(n);
int score;
for (int i = 0; i < n; ++i) {
std::cin >> score;
while (score < 0 || score > 100) {
std::cerr << "Sai định dạng điểm!\n"; std::cin >> score;
}
this->push_back(score);
}
is_dirty = true;
}
void output() const {
for (auto it = this->cbegin(); it != this->cend(); ++it)
std::cout << *it << ' ';
std::cout << '\n';
}
// Các hàm khác tương tự như trên, dùng this->size(), this->begin(), v.v.
};
Kế thừa tư nhân cho phép truy cập các hàm thành viên của vector bên trong lớp dẫn xuất, nhưng che giấu chúng khỏi mã bên ngoài — tăng tính đóng gói.
Ứng dụng đa hình: Hệ thống vẽ đồ họa
Hệ thống canvas sử dụng đa hình để xử lý nhiều loại hình vẽ khác nhau thông qua một giao diện chung:
enum class ShapeType { Circle, Triangle, Rectangle };
class Drawable {
public:
virtual void render() const = 0;
virtual ~Drawable() = default;
};
class Circle : public Drawable {
public:
void render() const override {
std::cout << "Vẽ hình tròn...\n";
}
};
class Canvas {
public:
void addShape(const std::string& type) {
auto shape = createShape(type);
if (shape) shapes.push_back(shape);
}
void renderAll() const {
for (const auto& s : shapes) s->render();
}
~Canvas() {
for (auto* ptr : shapes) delete ptr;
}
private:
std::vector<Drawable*> shapes;
};
Tính năng đa hình phụ thuộc vào hàm ảo (virtual function) và con trỏ/địa chỉ tham chiếu tới đối tượng — đảm bảo gọi đúng phiên bản hàm tại thời điểm chạy.
Thiết kế hệ thống quản lý đồ chơi theo mẫu Factory
Xây dựng hệ thống mô phỏng nhà máy sản xuất đồ chơi thông minh, áp dụng kế thừa và đa hình:
class Toy {
public:
Toy(std::string n, std::string t, double p, std::string m, std::string b, std::string a)
: name{std::move(n)}, type{std::move(t)}, price{p},
material{std::move(m)}, brand{std::move(b)}, ageGroup{std::move(a)} {}
virtual void performSpecialFeature() const = 0;
virtual void displayInfo() const;
virtual ~Toy() = default;
protected:
std::string name, type, material, brand, ageGroup;
double price;
};
class RobotDog : public Toy {
public:
using Toy::Toy;
void performSpecialFeature() const override {
std::cout << name << " nói: Xin chào! Tôi có thể trò chuyện cùng bạn.\n";
}
};
class MusicGuitar : public Toy {
public:
using Toy::Toy;
void performSpecialFeature() const override {
std::cout << name << " đang phát nhạc tự động...\n";
}
};
class ToyFactory {
public:
void add(Toy* toy) {
if (toy) inventory.push_back(toy);
}
void showAllFeatures() const {
for (const auto* toy : inventory)
toy->performSpecialFeature();
}
~ToyFactory() {
for (auto* toy : inventory) delete toy;
}
private:
std::vector<Toy*> inventory;
};
So sánh kết hợp và kế thừa
- Kết hợp: Lớp chứa các đối tượng khác như thành phần bên trong — phù hợp khi quan hệ là "có một" (has-a). Ưu điểm: đóng gói tốt, linh hoạt thay đổi cấu trúc nội bộ.
- Kế thừa: Lớp mở rộng hành vi của lớp cơ sở — phù hợp với quan hệ "là một" (is-a). Ưu điểm: tận dụng lại mã, hỗ trợ đa hình.
Trong trường hợp máy tính điểm, kết hợp là lựa chọn tự nhiên hơn vì GradeCalc không phải là một vector, mà chỉ sử dụng vector để lưu trữ dữ liệu.