99 Ngày Java — Ngày 16: Abstract class
Ngày 15 khép lại với một câu hỏi thiết kế: làm sao buộc mọi lớp con phải override một method mà lớp cha không thể tự cài đặt? NhanVien xác định được rằng mọi nhân viên đều có lương, nhưng công thức tính lương cụ thể thì chỉ từng lớp con nắm rõ — để lớp cha trả về một giá trị mặc định vô nghĩa là một khiếm khuyết thiết kế tiềm ẩn. Java giải quyết bằng abstract class — lớp trừu tượng: lớp cha khai báo những gì bắt buộc phải có, và ủy quyền phần cài đặt cụ thể cho lớp con.

Abstract class & abstract method
public abstract class NhanVien { // abstract: không thể khởi tạo trực tiếp
protected String ten;
protected double luongCoBan;
public NhanVien(String ten, double luongCoBan) { // vẫn có constructor —
this.ten = ten; // lớp con gọi qua super(...)
this.luongCoBan = luongCoBan;
}
public abstract double tinhLuong(); // abstract method: chỉ khai báo chữ ký,
// không có thân — lớp con BẮT BUỘC override
public String gioiThieu() { // method thường: cài đặt sẵn, dùng chung
return "Tôi là " + ten;
}
}
NhanVien nv = new NhanVien("An", 10_000_000);
// ✗ lỗi biên dịch: NhanVien is abstract; cannot be instantiated
NhanVien nv = new LapTrinhVien("An", 20_000_000, "Java"); // ✓ mẫu quen thuộc từ Ngày 15Abstract method chỉ có chữ ký, không có thân — compiler buộc lớp con override; lớp con nào chưa cài đặt đủ thì bản thân nó cũng phải khai báo
abstract.Abstract class vẫn có đầy đủ constructor, field và method thường — nó là lớp cha chưa hoàn chỉnh một cách có chủ đích, không phải một lớp rỗng.
Kết hợp với Ngày 15: dynamic dispatch giờ được compiler bảo chứng — mọi đối tượng thực tế đứng sau biến
NhanVienchắc chắn có bảntinhLuong()riêng, không còn khả năng rơi vào cài đặt mặc định vô nghĩa của lớp cha.
Khi nào nên dùng abstract class
// Lớp cha "thường" — override chỉ là khuyến nghị:
public class NhanVien {
public double tinhLuong() { return 0; } // giá trị giả — lớp con quên override
} // vẫn biên dịch, sai chỉ lộ ra lúc runtime
// Abstract class — quy ước trở thành ràng buộc biên dịch:
public abstract class NhanVien {
public abstract double tinhLuong(); // quên override → lỗi biên dịch ngay
}
// Nguyên tắc chung: phát hiện lỗi càng sớm càng tốt —
// lỗi compile-time (lúc build) luôn rẻ hơn lỗi runtime (lúc hệ thống đang chạy)Dùng khi các lớp con cùng một gốc "là một" (Ngày 14) và chia sẻ code lẫn trạng thái chung — constructor, field, các method đã cài đặt — nhưng tồn tại một vài bước mà mỗi lớp bắt buộc phải tự định nghĩa.
So với lớp cha thường: lớp cha thường có thể bị khởi tạo một cách vô nghĩa, và override chỉ là quy ước; abstract class chuyển quy ước đó thành ràng buộc do compiler thực thi.
JDK áp dụng đúng mô hình này:
InputStream,AbstractList,HttpServlet— đều là abstract class đảm nhận phần xử lý chung và để lại một số method cho lớp con cài đặt.
Template method — lớp cha giữ trình tự, lớp con điền chi tiết
public abstract class BaoCao {
// TEMPLATE METHOD: khung xử lý cố định — final để lớp con không thay đổi trình tự
public final String xuatBaoCao() {
return taoTieuDe() + "\n" + taoNoiDung() + "\n" + taoChanTrang();
}
protected String taoTieuDe() { return "=== BÁO CÁO ==="; } // có cài đặt mặc định,
// được phép override
protected abstract String taoNoiDung(); // bước bắt buộc — mỗi loại báo cáo tự cài đặt
protected String taoChanTrang() { return "-- Mọt Dev --"; }
}
public class BaoCaoDoanhThu extends BaoCao {
@Override
protected String taoNoiDung() { return "Doanh thu tháng này: 120 triệu"; }
}
new BaoCaoDoanhThu().xuatBaoCao(); // khung của lớp cha + nội dung của lớp con
// Lớp cha kiểm soát TRÌNH TỰ, lớp con cung cấp CHI TIẾT — tài liệu gọi đây là
// Hollywood Principle: "don't call us, we'll call you"Đây là design pattern đầu tiên của series — về bản chất chỉ là abstract class được vận dụng có kỷ luật: một method
finalđịnh nghĩa khung xử lý, các bước abstract dành cho lớp con.finaltrênxuatBaoCao()là quyết định có chủ đích: lớp con được thay thế từng bước, không được thay đổi trình tự — cấu trúc báo cáo vì vậy luôn thống nhất trên toàn hệ thống.Bạn đã sử dụng mẫu này ở series Spring mà chưa gọi tên: vòng đời bean (Ngày 07) hay
JdbcTemplate— framework giữ khung xử lý, code của bạn cài đặt các bước cụ thể.
Abstract class và interface — định vị trước Ngày 17
// Tiêu chí Abstract class Interface (Ngày 17)
// ----------------------------------------------------------------------------
// Quan hệ mô hình hóa "là một" (is-a) "có khả năng" (capability)
// Field trạng thái ✓ có, kèm constructor ✗ chỉ có hằng số
// Code cài đặt sẵn ✓ method thường ✓ default method (có giới hạn)
// Giới hạn kế thừa extends 1 lớp cha duy nhất implements nhiều interface
//
// Quy tắc chọn nhanh:
// Cần CHIA SẺ code + trạng thái cho một họ lớp cùng gốc → abstract class
// Chỉ cần MÔ TẢ hợp đồng cho các lớp không cùng gốc → interfaceAbstract class mô hình hóa quan hệ "là một" và mang được field, constructor, code dùng chung — nhưng mỗi lớp chỉ
extendsđược một lớp cha duy nhất.Interface mô tả năng lực (hợp đồng thuần túy) — một lớp
implementsđược nhiều interface cùng lúc. Chi tiết về default method, functional interface sẽ được trình bày ở Ngày 17.
Bài tập nhỏ
Chuyển
NhanViencủa Ngày 14–15 thành abstract class vớitinhLuong()abstract — xác nhậnnew NhanVien(...)không còn biên dịch được, trong khi vòng lặp tính quỹ lương của Ngày 15 vẫn chạy nguyên vẹn.Thêm lớp
ThucTapSinhnhưng không overridetinhLuong()— đọc và ghi nhớ thông báo lỗi biên dịch.Cài đặt
BaoCaotheo mẫu template method và thêmBaoCaoNhanSu— sau đó thử bỏfinalkhỏixuatBaoCao()và override nó ở lớp con để thấy rủi ro khi trình tự bị thay đổi.Đọc tài liệu
InputStreamtrong JDK: method nào abstract, method nào là khung dùng chung? Đối chiếu với nội dung hôm nay.
Kết luận
Abstract class hoàn thiện bức tranh kế thừa: lớp cha vừa chia sẻ code chung vừa ép buộc lớp con cài đặt phần việc riêng, và template method nâng điều đó thành khung xử lý cố định — trình tự thuộc về lớp cha, chi tiết thuộc về lớp con. Tuy nhiên abstract class vẫn chịu một giới hạn cốt lõi: mỗi lớp chỉ có một lớp cha. Khi một lớp cần cam kết nhiều hợp đồng cùng lúc, ta cần công cụ khác — Ngày 17: interface, thành phần linh hoạt nhất của hệ thống kiểu trong Java. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


