[HCMUS] Đề thi cuối kì I năm 2025-2026 môn LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG, chương trình Chuẩn (có đáp án)

Admin

 

ĐỀ THI CUỐI KÌ LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG

Lưu ý: Đáp án được soạn bởi Tổ Tin học TTKT Uni


Câu 1

a) Vấn đề hình thoi trong đa kế thừa. Vẽ hình minh họa và giải thích vấn đề.

b) Lớp trừu tượng (abstract class) là gì? Sự khác biệt lớn nhất giữa lớp trừu tượng (abstract class) và lớp cụ thể (concrete class) là gì?

Câu 2

a) Xây dựng một lớp Animal gồm hai thuộc tính: namepopulation. Cài đặt một constructor, phải đảm bảo population >= 0. Nếu population < 0 thì throw lỗi.

b) Xây dựng lớp EndangeredAnimal kế thừa từ lớp Animal:

• Có thêm thuộc tính riêng: Độ hiếm. Có 3 mức độ: Vulnerable, EndangeredCritically Endangered.

• Có hàm kiểm tra (true/false) có phải Critically Endangered hay không.

c) Nạp chồng operator < cho EndangeredAnimal. Loài nào ít cá thể hơn thì sẽ coi là nhỏ hơn. Viết hàm sort cho một vector<EndangeredAnimal>, sử dụng operator < vừa cài đặt.

Câu 3

Một công ty muốn xây dựng hệ thống lưu trữ. Ban đầu chỉ lưu trữ trong máy nhưng sau đó muốn lưu trữ trên Cloud (Google Cloud Service và Amazon Web S3). Mục tiêu là xử lí việc lưu trữ phức tạp bằng một hàm rất nhanh dạng:

Uploader uploader; uploader.upload("input.txt");

Cho sẵn bốn lớp và không được sửa nội dung các lớp này (không được sửa, tuy nhiên có thể thay đổi quan hệ thiết kế nếu cần):

Các lớp cho sẵn

1) Class LocalStorage

class LocalStorage {
public:
// Luu filename, return ve dia chi duong dan toi file do
std::string save(std::string filename);
};

2) Class GCSClient

class GCSClient {
public:
// Upload file data len, api_key duoc cap
// Return ve id cua file sau khi upload
std::string upload(std::string api_key, std::vector<uint8_t> data);
};

3) Class S3Client

class S3Client {
public:
// bucket_id la ten folder, api_key la api duoc cap
// Upload file data len, return ve id cua file sau khi upload
std::string upload(std::string bucket_id,
std::string api_key,
std::vector<uint8_t> data);
};

4) Class File

class File {
public:
// Ho tro doc file va chuyen sang dang du lieu can thiet
// Cach xai: auto data = File::read("input.txt");
static std::vector<uint8_t> read(std::string filename);
};

Yêu cầu

a) Sử dụng các kĩ thuật lập trình hướng đối tượng, vẽ UML lời giải.

b) Cài đặt hàm Uploader::upload() và các hàm lưu trữ cụ thể.

c) Hiện tại, công ty muốn lưu lại các config lưu trữ dưới dạng key=value trong một file storage.conf như sau:

provider=S3
buck_id=abc
api_key=123456abcxyz

Nếu làm như thế thì các class sẽ thay đổi như thế nào?

• Vẽ UML thay đổi.

• Tóm tắt thay đổi.


— HẾT —



ĐÁP ÁN VÀ LỜI GIẢI CHI TIẾT

Câu 1

a) Vấn đề hình thoi trong đa kế thừa:

Vấn đề hình thoi (Diamond Problem) xảy ra trong ngôn ngữ lập trình hỗ trợ đa kế thừa (như C++) khi một lớp con kế thừa từ hai lớp cơ sở, và cả hai lớp cơ sở đó lại cùng kế thừa từ một lớp cha chung.

Capi Demy: Hình minh hoạ


Giải thích: Khi lớp D gọi một phương thức hoặc truy cập một thuộc tính được định nghĩa trong lớp A, trình biên dịch sẽ không biết phải sử dụng bản sao của A từ nhánh B hay từ nhánh C. Điều này gây ra sự mơ hồ (ambiguity) và lãng phí bộ nhớ do dữ liệu của lớp A được sao chép hai lần.

b) Lớp trừu tượng (abstract class) và sự khác biệt:

Lớp trừu tượng (Abstract class): Là một lớp chứa ít nhất một phương thức thuần ảo (pure virtual function). Cú pháp phương thức thuần ảo: virtual void func() = 0;.

Sự khác biệt lớn nhất: Chúng ta không thể khởi tạo (instantiate) một đối tượng từ lớp trừu tượng (chỉ có thể dùng làm lớp cơ sở hoặc con trỏ/tham chiếu). Ngược lại, lớp cụ thể (concrete class) có thể khởi tạo đối tượng một cách bình thường.

Câu 2

a) Xây dựng lớp Animal:

#include <iostream>
#include <string>
#include <stdexcept>
#include <vector>
#include <algorithm>


class Animal {
protected:
std::string name;
int population;

public:
Animal(std::string n, int pop) : name(n), population(pop) {
if (population < 0) {
throw std::invalid_argument("Population must be >= 0");
}
}

code
Code
download
content_copy
expand_less
virtual ~Animal() {} 

int getPopulation() const { return population; }

};

b) Xây dựng lớp EndangeredAnimal:

class EndangeredAnimal : public Animal {
public:
enum Level { Vulnerable, Endangered, CriticallyEndangered };


private:
Level rarity;

public:
EndangeredAnimal(std::string n, int pop, Level r)
: Animal(n, pop), rarity(r) {}

code
Code
download
content_copy
expand_less
bool isCriticallyEndangered() const {
    return rarity == Level::CriticallyEndangered;
}

// Getter cho rarity để dùng ở câu c nếu cần
Level getRarity() const { return rarity; }

};

c) Nạp chồng operator \(<\) và hàm sort:

Theo đề bài, loài nào ít cá thể hơn (population nhỏ hơn) thì coi là nhỏ hơn.

// Nạp chồng toán tử < bên trong hoặc bên ngoài class
bool operator<(const EndangeredAnimal& a, const EndangeredAnimal& b) {
return a.getPopulation() < b.getPopulation();
}


// Hàm sort cho vector
void sortAnimals(std::vector& list) {
std::sort(list.begin(), list.end());
// Tự động dùng operator < đã định nghĩa ở trên
}

Câu 3

Phân tích thiết kế:

Đây là bài toán áp dụng Adapter Pattern (Mẫu thiết kế Thích ứng) kết hợp với tư tưởng của Strategy Pattern. Các lớp LocalStorage, GCSClient, S3Client có giao diện khác nhau (tên hàm, tham số khác nhau). Class Uploader cần một giao diện thống nhất để xử lý.

a) Vẽ UML lời giải:

Chúng ta cần tạo một Interface chung (lớp trừu tượng), ví dụ tên là IStorageService. Sau đó tạo các lớp Adapter thực thi Interface này và bao đóng các class có sẵn.

[Hình ảnh UML Class Diagram: IStorageService (interface) <|-- LocalStorageAdapter, GCSAdapter, S3Adapter. Uploader chứa (composition) một con trỏ IStorageService]

b) Cài đặt hàm Uploader::upload() và các hàm lưu trữ cụ thể:

Bước 1: Định nghĩa Interface chung

// Interface chung cho việc lưu trữ
class IStorageService {
public:
virtual void saveFile(std::string filename) = 0;
virtual ~IStorageService() {}
};

Bước 2: Viết các lớp Adapter để bọc các class có sẵn

// Adapter cho LocalStorage
class LocalStorageAdapter : public IStorageService {
LocalStorage storage;
public:
void saveFile(std::string filename) override {
storage.save(filename);
std::cout << "Saved to LocalStorage\n";
}
};


// Adapter cho GCSClient
class GCSAdapter : public IStorageService {
GCSClient client;
std::string apiKey;
public:
GCSAdapter(std::string key) : apiKey(key) {}
void saveFile(std::string filename) override {
// Cần đọc file ra binary trước theo yêu cầu của GCSClient
auto data = File::read(filename);
client.upload(apiKey, data);
std::cout << "Uploaded to GCS\n";
}
};

// Adapter cho S3Client
class S3Adapter : public IStorageService {
S3Client client;
std::string bucketId;
std::string apiKey;
public:
S3Adapter(std::string bucket, std::string key)
: bucketId(bucket), apiKey(key) {}

code
Code
download
content_copy
expand_less
void saveFile(std::string filename) override {
    auto data = File::read(filename);
    client.upload(bucketId, apiKey, data);
    std::cout << "Uploaded to S3\n";
}

};

Bước 3: Cài đặt lớp Uploader

class Uploader {
IStorageService* service;
public:
// Inject dependency qua constructor
Uploader(IStorageService* s) : service(s) {}

code
Code
download
content_copy
expand_less
void upload(std::string filename) {
    if (service) {
        service->saveFile(filename);
    }
}

};

Lưu ý: Trong hàm main, sinh viên sẽ khởi tạo Uploader với Adapter cụ thể mong muốn.

c) Thay đổi cấu hình qua file storage.conf:

Nếu muốn cấu hình động dựa trên file, chúng ta không thể "hard-code" (gán cứng) loại Adapter trong code. Class Uploader hoặc một class quản lý khởi tạo cần áp dụng Factory Pattern (Mẫu thiết kế Nhà máy) để đọc file config và sinh ra đối tượng Adapter tương ứng.

• Vẽ UML thay đổi:

Thêm một lớp StorageFactory. Lớp này có phương thức createStorageService(string configFile) trả về con trỏ IStorageService*.

StorageFactory (createStorageService) ----> LocalStorageAdapter/GCSAdapter/S3Adapter

• Tóm tắt thay đổi:

1. Thêm lớp Factory: Tạo class StorageFactory có nhiệm vụ đọc file storage.conf, phân tích dòng provider=....

2. Xử lý logic tạo đối tượng:

- Nếu provider=Local: Factory trả về new LocalStorageAdapter().

- Nếu provider=S3: Factory đọc tiếp buck_id, api_key từ file và trả về new S3Adapter(buck_id, api_key).

- Tương tự cho GCS.

3. Sửa đổi Client: Nơi sử dụng Uploader sẽ không new trực tiếp Adapter mà gọi qua Factory.

Post a Comment

Chúng tôi rất vui khi bạn muốn đóng góp ý kiến. Để đảm bảo môi trường trao đổi lành mạnh, vui lòng tuân thủ các quy định sau:

1. Sử dụng tiếng Việt có dấu đầy đủ, tránh viết tắt.
2. Bình luận sẽ được kiểm duyệt trước khi công khai.
3. Tôn trọng người khác và đóng góp ý kiến xây dựng.
4. Tuân thủ chính sách của Google và TTKT.

Cảm ơn bạn đã đồng hành cùng chúng tôi!

CẢNH BÁO

Gần đây, chúng tôi phát hiện nội dung bị chụp màn hình và chia sẻ trái phép. TTKT khuyến cáo bạn không nên chụp màn hình mà hãy chia sẻ link đến bài viết để tôn trọng tác giả và tránh bị vô hiệu hóa tài khoản.

Yêu cầu Đăng nhập

Để tiếp tục sử dụng, vui lòng đăng nhập.