Lab 05 cho người mới: kế thừa và đa hình

Bài quan trọng nhất môn học. Hai cụm độc lập: điểm học phần (trừu tượng → kế thừa → ghi đè) và bưu phẩm (đa hình quản lý chung). Hiểu được "một danh sách, nhiều cách tính" là qua môn.

Bài 1. DiemHP — lớp trừu tượng là bản cam kết

Đề nói gì

Viết khung tính điểm dùng chung cho mọi loại học phần (Lý thuyết, Thực hành, Đồ án). Khung giữ tên + tín chỉ, để trống đúng một chỗ tinhDiem() cho lớp con điền.

Từ mới

Ý tưởng

  1. Cha giữ phần chung: tên, tín chỉ + kiểm tra 1..4, toString().
  2. Cha khai báo tinhDiem() trừu tượng — "đứa con nào cũng phải có".
  3. toString() của cha gọi sẵn tinhDiem() để con hưởng.

Code mổ xẻ

public abstract class DiemHP {
    private String tenHP;
    private int soTinChi;
    public DiemHP(String tenHP, int soTinChi) {
        this.tenHP = tenHP;
        setSoTinChi(soTinChi);
    }
    public abstract double tinhDiem();   // cam kết, không có thân
    @Override
    public String toString() {
        double d = Math.round(tinhDiem() * 100) / 100.0;
        return tenHP + " (" + soTinChi + " TC): " + d;
    }
}

Lớp trừu tượng vẫn có constructor — nó dựng phần chung khi new lớp con. toString() gọi tinhDiem(): lúc chạy với object con, Java tự gọi bản của con. Đa hình gói trong 1 dòng, ngấm được chỗ này là hiểu nửa bài.

Bẫy hay gặp

Tự luyện

  1. Giải thích vì sao toString() của cha in đúng điểm của con.
  2. Thêm thuộc tính giangVien vào cha để cả 3 con dùng chung.

Bài 2. Ba lớp con — ghi đè là viết lại theo ý mình

Đề nói gì

HPLyThuyet (cc0.1 + gk0.2 + ck*0.7), HPThucHanh (trung bình 3 đầu điểm), HPDoAn (trung bình điểm hội đồng, rỗng thì 0).

Từ mới

Ý tưởng

Mỗi con: constructor gọi super + nhận thêm dữ liệu riêng, setter kiểm tra 0..10, tinhDiem() viết ruột riêng.

Code mổ xẻ

public class HPLyThuyet extends DiemHP {
    private double chuyenCan, giuaKy, cuoiKy;
    public HPLyThuyet(String tenHP, int soTinChi,
                      double chuyenCan, double giuaKy, double cuoiKy) {
        super(tenHP, soTinChi);       // đúc phần cha TRƯỚC, dòng đầu
        setChuyenCan(chuyenCan);      // rồi mới lo phần riêng
        setGiuaKy(giuaKy);
        setCuoiKy(cuoiKy);
    }
    @Override
    public double tinhDiem() {
        return chuyenCan * 0.1 + giuaKy * 0.2 + cuoiKy * 0.7;
    }
}

HPDoAn nhận mảng double[]: trung bình cộng, nhưng chặn null và rỗng trả 0 trước — mảng người ta truyền vào có thể rỗng.

Chạy tay (bạn An)

LT Java (9, 8, 7, 3TC): 0.9 + 1.6 + 4.9 = 7.4. TH Java (8, 9, 7, 2TC): 24 / 3 = 8.0. ĐTB = (7.4*3 + 8.0*2) / 5 = 38.2 / 5 = 7.64. Khớp output mẫu. Bạn Bình: TH (6+7+8)/3 = 7.0 (2TC), LT 1.0+1.8+5.6 = 8.4 (3TC), ĐTB (14 + 25.2)/5 = 7.84. Bạn Chi tự tính ra 7.21.

Bẫy hay gặp

Tự luyện

  1. Thêm HPNgoaiKhoa (điểm = điểm rèn luyện nhập vào).
  2. Thêm kiểm tra: đầu điểm thực hành cũng chỉ nhận 0..10 (đã có, giải thích vì sao cần).

Bài 3. Mảng đa hình — một danh sách, nhiều cách tính

Đề nói gì

Quản lý 3 sinh viên, mỗi người 2 học phần đủ loại. In từng người, rồi cộng tổng điểm 6 học phần = 47.07.

Từ mới

Ý tưởng

  1. Dựng 6 học phần, biến khai báo kiểu DiemHP cho thấy tính chung.
  2. Mỗi sinh viên giữ 2 học phần, ĐTB trọng số như Lab 03.
  3. Gom 6 học phần vào mảng DiemHP[], một vòng cộng hết.

Code mổ xẻ

DiemHP lt1 = new HPLyThuyet("Lap trinh Java", 3, 9, 8, 7); // khai cha, đúc con
DiemHP[] cacHP = {lt1, th1, th2, lt2, lt3, th3};           // lẫn lộn vẫn chung 1 mảng
double tong = 0;
for (int i = 0; i < cacHP.length; i++)
    tong += cacHP[i].tinhDiem();   // đứa nào tính theo ruột đứa nấy

Tổng = 7.4 + 8.0 + 7.0 + 8.4 + 6.6 + 9.667 = 47.07. Thêm loại học phần thứ tư sau này: vòng lặp này không sửa một chữ — đó là lý do người ta dùng đa hình thay vì if kiểm tra loại.

Bẫy hay gặp

Tự luyện

  1. In riêng tổng điểm Lý thuyết và tổng điểm Thực hành từ mảng chung.
  2. Tìm học phần điểm cao nhất trong mảng (mẫu giữ-max Lab 02).

Bài 4. Bưu phẩm — đa hình áp vào bài toán thật

Đề nói gì

BuuPham (mã + cước + loại), Thu (thường/bảo đảm), BuuKien. ThungThu chứa tối đa N bưu phẩm: thêm, liệt kê, liệt kê theo loại, tổng tiền, tổng theo loại, đếm.

Từ mới

Ý tưởng

  1. Cha BuuPham trừu tượng với 2 phương thức trừu tượng.
  2. Thu thêm cờ baoDam, BuuKien giá cố định.
  3. ThungThu là danh sách BuuPham[] + soLuong (mẫu Lab 03, 04).

Code mổ xẻ

public class Thu extends BuuPham {
    private boolean baoDam;
    public Thu(String ma, boolean baoDam) {
        super(ma);
        this.baoDam = baoDam;
    }
    @Override
    public double phiGui() { return baoDam ? 2000 : 800; }
    @Override
    public String loai() { return baoDam ? "Thu bao dam" : "Thu thuong"; }
}
public double tongTien() {
    double t = 0;
    for (int i = 0; i < soLuong; i++) t += ds[i].phiGui(); // mỗi loại tự tính
    return t;
}

toString() của cha (ma + loai + cước) cũng đa hình: in Thu ra "Thu bao dam", in BuuKien ra "Buu kien" mà cha không hề biết có 2 loại con.

Chạy tay

8 bưu phẩm: 3 thường 3*800 = 2400, 2 bảo đảm 2*2000 = 4000, 3 bưu kiện 3*5000 = 15000. Tổng 21400.0, số lượng 8. Khớp output mẫu từng dòng. lietKeTheoLoai("Thu bao dam") duyệt và chỉ in đứa nào loai().equals(...) khớp.

Bẫy hay gặp

Tự luyện

  1. Thêm BuuKienNhanh (cước gấp đôi bưu kiện thường).
  2. Thêm loaiDatNhat(): loại nào đóng góp nhiều tiền nhất.

Phụ lục OOP: trace đa hình từng bước

Cây kế thừa của bài điểm

            DiemHP (abstract: tên, tín chỉ, tinhDiem?)
           /        |          \
HPLyThuyet    HPThucHanh    HPDoAn
(cc,gk,ck)    (db1,db2,db3) (mảng hội đồng)

Mũi tên đọc là "là một": HPLyThuyet là một DiemHP. Code nào cần DiemHP đều nhận được cả 3 con — đó là lý do SinhVien constructor nhận DiemHP hp1, DiemHP hp2 mà truyền Lý thuyết/Thực hành vào vẫn chạy.

Trace 6 phần tử ra 47.07: đứa nào tính kiểu nấy

Mảng cacHP khai báo kiểu cha, object thật đủ loại. Cùng một dòng cacHP[i].tinhDiem(), Java nhìn object thật để chọn ruột (gọi là gắn kết động):

Phần tử Object thật Ruột được chạy Giá trị
lt1 Lý thuyết cc*0.1+gk*0.2+ck*0.7 7.4
th1 Thực hành trung bình 3 đầu điểm 8.0
th2 Thực hành trung bình 3 đầu điểm 7.0
lt2 Lý thuyết trọng số 8.4
lt3 Lý thuyết trọng số 6.6
th3 Thực hành trung bình 3 đầu điểm 9.667

Tổng 47.07. Không một chữ if kiểm tra loại nào ở vòng lặp — loại bỏ if theo loại chính là thước đo đa hình: càng ít if (x instanceof ...), thiết kế càng đúng.

Chuỗi constructor: cha dựng trước con

new HPLyThuyet(...) chạy: DiemHP(...) dựng tên + tín chỉ (có kiểm tra 1..4) → về con nhận 3 đầu điểm (có kiểm tra 0..10). Luật của cha áp cho cả 3 con mà không viết lại — đó là ý nghĩa super dòng đầu: phần chung dựng trước, phần riêng dựng sau. Đặt super dòng 2 là lỗi biên dịch, Java không cho con chạy trước khi cha xong.

Bưu phẩm: cùng một kịch bản, sân khấu khác

Cây BuuPham → Thu / BuuKien giống hệt cây điểm: cha trừu tượng (phiGui?, loai?), con điền ruột, ThungThu đựng mảng cha. tongTien() duyệt và gọi phiGui() — 8 object, 3 ruột khác nhau, 1 vòng lặp duy nhất. Học một mẫu mà giải được hai bài: đó là lúc OOP bắt đầu có lãi.