Chuyển tới nội dung chính

Đề HK2 2024-2025

Thành phố Hồ Chí Minh trong những năm gần đây đã trở thành một trung tâm gia công phần mềm có uy tín trên thế giới, cạnh tranh sòng phẳng với các đối thủ lớn trên khắp năm châu bao gồm cả Trung Quốc lẫn Ấn Độ. Chúng ta không chỉ có lợi thế chỉ phí gia công phù hợp mà còn được các đối tác đánh giá cao ở trình độ, thái độ cầu thị và tinh thần làm việc chăm chỉ của đội ngũ phát triển phần mềm. Trọng tâm của sự phát triển này chính là sự hoạt động hiệu quả của các công ty gia công phần mềm - trong đó có công ty Global Tech với đội ngũ kỹ sư lên đến hàng ngàn người. GlobalTech nhận thực hiện các hợp đồng gia công phần mềm trên nhiều nền tảng công nghệ khác nhau như cloud (C#, Java, PHP, NodeJS, Python, Go,...), mobile APP development (Android/iOS), IoT,... cho các đối tác lớn trên toàn cầu trong các lĩnh vực khác FinTech, eCommerce, Education, Medical và Entertainment.

Hiện nay, Global Tech có các hình thức hợp đồng sau với đối tác:

(a) Hợp đồng gia công trọn gói: Global Tech sẽ được đối tác trả một số tiền gọi là giá trị hợp đồng để gia công một phần hoặc toàn bộ một dự án phần mềm trong một khoản thời gian dự kiến - gọi là thời gian gia công - tính theo đơn vị ngày (đã được 2 bên thống nhất khi ký kết hợp đồng).

Với dạng hợp đồng này, lợi nhuận của GlobalTech được tính như sau:

Lợi nhuận == Giá trị hợp đồng - (((( Tổng lương ngày của nhân sự ++ chi phí hỗ trợ chung)) ×\times Thời gian gia công)) - Chi phí khảo sát ban đầu

Tuy nhiên, nếu vì lý do nào đó, thời gian gia công bị trễ hơn từ 15 đến dưới 90 ngày so với thời gian dự kiến thì đối tác sẽ trừ phạt 10% giá trị hợp đồng. Trễ từ 90 ngày trở lên sẽ bị phạt 30% giá trị hợp đồng.

(b) Hợp đồng nghiên cứu phát triển: Global Tech sẽ cùng đối tác nghiên cứu, phát triển và chuyển giao một sản phẩm mới trong đó đối tác giữ vai trò thiết kế sản phẩm, giám sát thi công, GlobalTech sẽ cung cấp nguồn nhân lực kỹ thuật (kỹ sư phát triển phần mềm & kiểm tra chất lượng) và đội ngũ quản lý kỹ thuật (project manager) làm việc tại TP.HCM. Đối tác sẽ chỉ trả cho GlobalTech theo chi phí nhân lực.

Lợi nhuận == ((Chi phí nhân sự ngày do đối tác trả - Tổng lương ngày của nhân sự - chi phí hỗ trợ chung)) ×\times Thời gian gia công - Chi phí khảo sát ban đầu

(c) Hợp đồng tư vấn tại chỗ: là một dạng đặc biệt của hợp đồng gia công trọn gói, trong đó Global Tech sẽ cử một nhóm nhỏ các chuyên gia giàu kinh nghiệm trong một lĩnh vực nào đó -thường tối đa không quá 3 người - sang làm việc trực tiếp với đối tác tại văn phòng của đối tác ở nước ngoài trong một khoảng thời gian để tư vấn cho đối tác. Lợi nhuận trong trường hợp này giống như hợp đồng gia công trọn gói nhưng trừ thêm chi phí sinh hoạt cho các nhân viên đi công tác (như khách sạn, ăn uống, di chuyển) trong thời gian tư vấn.

Lợi nhuận == Lợi nhuận theo cách tính của hợp đồng gia công trọn gói - Tổng phí sinh hoạt bình quân mỗi ngày ×\times số ngày công tác

Lưu ý: Số ngày công tác chính là thời gian gia công

Mọi hợp đồng đều có các thông tin chung như tên công ty đối tác, tên phần mềm hoặc dự án gia công cho đối tác, ngày hoàn thành dự án dự kiến (ngày/tháng/năm), lĩnh vực của phần mềm, nền tảng kỹ thuật chính của hợp đồng, chi phí hỗ trợ chung, chi phí khảo sát ban đầu.

Công ty Global Tech cần có phần mềm đáp ứng các yêu cầu sau:

a. Nhập vào từ bàn phím danh sách các hợp đồng gia công của Global Tech (tối đa 1000).

b. Tính tổng lợi nhuận của Global Tech từ danh sách hợp đồng đã nhập.

c. Tìm loại hợp đồng có lợi nhuận cao nhất trong 3 ba loại: gia công trọn gói, nghiên cứu phát triển và tư vấn tại chỗ.

  1. Áp dụng kiến thức lập trình hướng đối tượng (đặc biệt là 2 đặc tính kế thừa và đa hình) hãy thiết kế (vẽ) sơ đồ chi tiết các lớp đối tượng cho phần mềm trên (1 điểm)
  2. Sử dụng ngôn ngữ lập trình C++, định nghĩa các lớp trong sơ đồ trên để quản lý các yêu cầu của phần mềm (1 điểm)
  3. Sử dụng ngôn ngữ lập trình C++, định nghĩa các phương thức để thực hiện 3 yêu cầu a, b, c (3 điểm)

Phân tích đề

Bước 1: Xác định các lớp

Khi đọc đề, ta dễ dàng nhận thấy cần phải có lớp Hợp đồngDanh sách hợp đồng. Bên cạnh đó là các lớp cho 3 loại hợp đồng và lớp Ngày để lưu thông tin về ngày.

Chỉ có Hợp đồng trọn góiHợp đồng phát triển kế thừa từ Hợp đồng, còn Hợp động tại chỗ là dạng đặt biệt của hợp đồng trọn gói nên Hợp động tại chỗ sẽ kế thừa Hợp đồng trọn gói. Đồng thời, lớp Hợp đồng sẽ là lớp trừu tượng.

Bước 2: Thiết kế thuộc tính

Mặc dù đề có liệt kê các thuộc tính chung, nhưng không đủ. Vẫn còn các thuộc tính chung khác trên các công thức tính lợi nhuận. Nên ta phải một lần nữa đọc kỹ lại công thức, xem thuộc tính nào đã được liệt kê và không được liệt kê

  • Thời gian gia công
  • Tổng lương ngày nhân sự

Hợp đồng trọn gói có thêm thuộc tính Giá trị hợp đồngNgày hoàn thành thực tế (để tính tiền phạt thêm vào lợi nhuận)

Hợp đồng phát triển có thêm thuộc tính Chi phí nhân sự do đối tác trả

Hợp đồng tại chỗ có thêm thuộc tính Phí sinh hoạt mỗi ngày

Bên cạnh đó, để thực hiện yêu cầu b, c, ta cần thêm thuộc tính public Loại hợp đồng với enum gồm CHUNG và 3 loại hợp động

Bước 3: Thiết kế phương thức

Các lớp Hợp đồng sẽ có 2 phương thức thức là Nhap()LoiNhuan(). Phương thức LoiNhuan() ở lớp Hợp đồng cha là phương thức thuần ảo.

Danh sách hợp đồng sẽ có 3 phương thức ứng với 3 yêu cầu là Nhap(), TongLoiNhuan()LoiNhuanCaoNhat()

Lớp Ngày là lớp phụ, ta chỉ cần khai báo các phương thức mà các lớp chính trong bài cần sử dụng là nhập (sử dụng operator>> để viết nhanh hơn ở các phương thức Nhap()) và operator- để tính thời gian trễ cho lớp hợp đồng trọn gói.

Sơ đồ thiết kế lớp

Lời giải tham khảo

#include <vector>
#include <string>
#include <iostream>
using namespace std;
// I. Khai báo đầu đủ các lớp, thuộc tính, phương thức, định nghĩa trên 1 dòng các phương thức đơn giản,... và hàm main()
class cNgay {
int Ngay,
Thang,
Nam;
public:
friend istream& operator>>(istream&, cNgay&);
int operator-(cNgay);
};

class cHopDong {
protected:
string TenDoiTac,
TenPhanMem,
LinhVuc,
NenTangKyThuat;
cNgay NgayHoanThanhDuKien;
float ChiPhiChung,
ChiPhiBanDau;
// Dưới đây là những thuộc tính mà phần thông tin chung trong đề không đề cập, nhưng dựa vào công thức tính lợi nhuận mà đưa thành thông tin chung
int ThoiGianGiaCong;
float LuongNhanSu;
public:
enum LOAI {
CHUNG, TRON_GOI, PHAT_TRIEN, TAI_CHO
};
LOAI LoaiHopDong;
cHopDong() { LoaiHopDong = CHUNG; }
virtual float LoiNhuan() = 0;
virtual void Nhap();
};

class cHopDongTronGoi : public cHopDong {
float GiaTri;
cNgay NgayHoanThanh;
public:
cHopDongTronGoi() { LoaiHopDong = TRON_GOI; }
float LoiNhuan();
void Nhap();
};

class cHopDongPhatTrien : public cHopDong {
float ChiPhiNhanSu;
public:
cHopDongPhatTrien() { LoaiHopDong = PHAT_TRIEN; }
float LoiNhuan();
void Nhap();
};

class cHopDongTaiCho : public cHopDongTronGoi {
float PhiSinhHoat;
public:
cHopDongTaiCho() : PhiSinhHoat(0) { LoaiHopDong = TAI_CHO; }
float LoiNhuan();
void Nhap();
};

class cDSHopDong {
vector<cHopDong*> DS;
public:
void Nhap();
float TongLoiNhuan(cHopDong::LOAI loai = cHopDong::CHUNG);
string LoiNhuanCaoNhat();
};

int main() {
cDSHopDong GlobalTech;
cout << "a. Nhap danh sach hop dong:" << endl;
GlobalTech.Nhap();
cout << "b. Tong loi nhuan: "
<< GlobalTech.TongLoiNhuan();
cout << "c. Loai hop dong co loi nhuan cao nhat: "
<< GlobalTech.LoiNhuanCaoNhat();
}
// Khi viết các class nên chừa khoảng 3 - 4 hàng trống để trong quá trình viết phương thức, nếu nhận thấy các thuộc tính, phương thức cần thêm thì có thể thêm vào
//II. Định nghĩa các phương thức quan trọng trước để kịp thời gian, các phương thức không quan trọng nhưng dài (như nhập, xuất) có thể viết sau

void cDSHopDong::Nhap()
{
cout << "So luong: "; int n; cin >> n;
for (int i = 0; i < n; i++) {
cHopDong* HD;
cout << "Loai hop dong (1, 2, 3): "; int type; cin >> type;
switch (type)
{
case 1: HD = new cHopDongTronGoi; break;
case 2: HD = new cHopDongPhatTrien; break;
case 3: HD = new cHopDongTaiCho; break;
default: HD = new cHopDongTronGoi;
}
HD->Nhap();
DS.push_back(HD);
}
}

float cDSHopDong::TongLoiNhuan(cHopDong::LOAI loai)
{
if (loai == cHopDong::CHUNG) {
float sum = 0;
for (cHopDong* HD : DS) {
sum += HD->LoiNhuan();
}
return sum;
}
else {
float sum = 0;
for (cHopDong* HD : DS) {
if (HD->LoaiHopDong == loai) {
sum += HD->LoiNhuan();
}
}
return sum;
}
}

string cDSHopDong::LoiNhuanCaoNhat()
{
float TronGoi = TongLoiNhuan(cHopDong::TRON_GOI);
float PhatTrien = TongLoiNhuan(cHopDong::PHAT_TRIEN);
float TaiCho = TongLoiNhuan(cHopDong::TAI_CHO);
float MAX = max(TronGoi, max(PhatTrien, TaiCho));
if (MAX == TronGoi) return "Tron goi";
if (MAX == PhatTrien) return "Phat Trien";
else return "Tai Cho";
}

float cHopDongTronGoi::LoiNhuan()
{
float LOI_NHUAN = GiaTri - ((LuongNhanSu + ChiPhiChung) * ThoiGianGiaCong) - ChiPhiBanDau;
int THOI_GIAN_TRE = NgayHoanThanh - NgayHoanThanhDuKien;
if (THOI_GIAN_TRE >= 15
&& THOI_GIAN_TRE < 90) {
LOI_NHUAN += GiaTri * 0.1f;
}
else if (THOI_GIAN_TRE >= 90) {
LOI_NHUAN += GiaTri * 0.3f;
}
return LOI_NHUAN;
}


float cHopDongPhatTrien::LoiNhuan()
{
return (ChiPhiNhanSu - LuongNhanSu - ChiPhiChung) * ThoiGianGiaCong - ChiPhiBanDau;
}


float cHopDongTaiCho::LoiNhuan()
{
return cHopDongTronGoi::LoiNhuan() - PhiSinhHoat * ThoiGianGiaCong;
}

//III. Các hàm nhập/xuất/ít liên quan đến yêu cầu

istream& operator>>(istream& is, cNgay& a)
{
is >> a.Ngay >> a.Thang >> a.Nam;
return is;
}

void cHopDong::Nhap()
{
cin >> TenDoiTac
>> TenPhanMem
>> NgayHoanThanhDuKien
>> LinhVuc
>> NenTangKyThuat
>> ChiPhiChung
>> ChiPhiBanDau
>> ThoiGianGiaCong
>> LuongNhanSu;
}

void cHopDongTronGoi::Nhap()
{
cHopDong::Nhap();
cin >> GiaTri
>> NgayHoanThanh;
}

void cHopDongPhatTrien::Nhap()
{
cHopDong::Nhap();
cin >> ChiPhiNhanSu;
}

void cHopDongTaiCho::Nhap()
{
cHopDongTronGoi::Nhap();
cin >> PhiSinhHoat;
}

int cNgay::operator-(cNgay a) // Không viết cũng được
{
int dayOfMonth[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

auto isLeap = [](int year) {// Hàm trong hàm
return (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0));
};

int days1 = Ngay;
for (int i = 1; i < Thang; ++i) {
days1 += dayOfMonth[i];
if (i == 2 && isLeap(Nam)) days1 += 1;
}
for (int y = 1; y < Nam; ++y) {
days1 += 365 + isLeap(y);
}

int days2 = a.Ngay;
for (int i = 1; i < a.Thang; ++i) {
days2 += dayOfMonth[i];
if (i == 2 && isLeap(a.Nam)) days2 += 1;
}
for (int y = 1; y < a.Nam; ++y) {
days2 += 365 + isLeap(y);
}

return days1 - days2;
}