Java OO & Functional/Tư duy trừu tượng hoá — trụ cột quan trọng nhất của OOP
1/38
Bài 1 / 38~14 phútKế thừa & Đa hìnhMiễn phí lượt xem

Tư duy trừu tượng hoá — trụ cột quan trọng nhất của OOP

Abstraction là gì, 4 trụ cột OOP và vai trò trừu tượng hoá. Quy trình 5 bước xác định abstraction từ bài toán, 3 level of abstraction, và bộ công cụ Java (interface, abstract class, sealed, record) để hiện thực chúng.

TL;DR: Abstraction là quá trình bỏ chi tiết không quan trọng, giữ điểm chung quan trọng giữa nhiều thực thể, để xử lý chúng đồng nhất — và là trụ cột duy nhất của OOP thuộc về tư duy thay vì cú pháp. Bài này đưa quy trình 5 bước xác định abstraction từ bài toán (liệt kê thực thể, tìm điểm chung theo ngữ cảnh, loại detail, verify bằng use case, evolve theo requirements), 3 level trừu tượng (concrete class, abstract class, interface) và bộ công cụ Java tương ứng. Insight quan trọng nhất: abstraction phụ thuộc ngữ cảnh — cùng một đối tượng có nhiều abstraction đúng, tuỳ use case bạn đang phục vụ.

Bạn làm app quản lý thư viện. Yêu cầu ban đầu: cho mượn sách. Đơn giản — viết class Book với title, author, isbn, method borrow() / return(). 50 dòng code, xong.

Tuần sau, sếp yêu cầu thêm: cho mượn tạp chí (có issue, year), DVD (duration, region), audiobook (duration, narrator), ebook (fileSize, format). Mỗi loại có rule riêng — sách hạn mượn 14 ngày, DVD 7 ngày, ebook unlimited.

Làm thế nào?

Cách 1 — mỗi loại 1 class + copy logic borrow/return cho từng cái:

class Book    { borrow() { ... 50 dong ... } }
class Magazine { borrow() { ... 50 dong copy ... } }
class DVD     { borrow() { ... 50 dong copy ... } }
class Audiobook { ... }
class Ebook    { ... }

5 class × 50 dòng copy = 250 dòng duplicate logic. Fix bug một chỗ thì phải sửa lặp lại ở cả 5 chỗ. Thêm loại mới lại copy lần thứ 6.

Cách 2 — nhìn ra điểm chung, trừu tượng hoá:

Tất cả 5 loại đều là "thứ có thể cho mượn". Chung: có identifier, có người đang mượn, có ngày mượn, có ngày trả, có rule "được mượn bao lâu". Khác: loại cụ thể, rule chi tiết.

abstract class LibraryItem {
    String id;
    User borrower;
    LocalDate borrowedAt;

    abstract int borrowDurationDays();   // Moi con override

    void borrow(User u) {
        // Logic chung: check available, set borrower, set date
    }
    void returnItem() { ... }
}

class Book extends LibraryItem {
    int borrowDurationDays() { return 14; }
}
class DVD extends LibraryItem {
    int borrowDurationDays() { return 7; }
}
class Ebook extends LibraryItem {
    int borrowDurationDays() { return Integer.MAX_VALUE; }
}

50 dòng logic chung viết 1 lần trong LibraryItem. Mỗi con chỉ tả điểm khác biệt (borrowDurationDays). Thêm loại mới: 1 class ngắn extends + override.

Đây là abstraction — kỹ năng cốt lõi của OOP, và của software engineering nói chung.

Bài này không dạy syntax — syntax (abstract class, interface) ở các bài kế. Bài này dạy cách nghĩ:

  • 4 trụ cột OOP — abstraction đóng vai gì.
  • Abstraction là gì (không phải "giấu chi tiết" như Wikipedia nói).
  • Cách xác định abstraction từ bài toán — process cụ thể 5 bước.
  • 3 level of abstraction và khi chọn mức nào.
  • Java cung cấp gì để hiện thực abstraction.

Còn câu hỏi ngược lại — khi nào KHÔNG nên trừu tượng, và cách nhận diện abstraction sai — là chủ đề của bài 02 ngay sau bài này.

1. 4 trụ cột OOP — bức tranh tổng

OOP (Object-Oriented Programming) đứng trên 4 trụ cột:

Trụ cộtÝ nghĩaJava hiện thực
EncapsulationGiấu chi tiết implementation, expose APIprivate/public, getter/setter
InheritanceClass con kế thừa class chaextends
PolymorphismMột interface, nhiều implementationMethod override, dynamic dispatch
AbstractionTư duy tổng quát hoá, giữ "cái gì" bỏ "làm thế nào"abstract class, interface

3 cột đầu là cơ chế (mechanism) — Java cung cấp keyword cụ thể. Cột 4 — Abstraction — là tư duy (thinking) — không có keyword, nhưng xuyên suốt mọi thiết kế tốt.

Quan hệ giữa chúng:

  • Abstraction là gốc — quyết định "mô hình gì".
  • Encapsulation là kỹ thuật che detail theo abstraction đã chọn.
  • Inheritance là phương tiện share code giữa các class cùng abstraction.
  • Polymorphism là khả năng thay implementation dựa trên abstraction.

Thiết kế OOP tốt bắt đầu bằng abstraction tốt. Abstraction sai thì cả 3 cột còn lại phải gắng gượng bù đắp — code càng ngày càng phức tạp.

2. Abstraction là gì

Định nghĩa thường gặp — chưa đủ

Sách giáo khoa: "Abstraction là giấu chi tiết implementation, chỉ expose interface".

Định nghĩa này đúng nhưng thiếu 70%. Nó mô tả kết quả, không mô tả quá trìnhý nghĩa cốt lõi.

Định nghĩa đầy đủ

Abstraction = quá trình bỏ đi chi tiết không quan trọng cho ngữ cảnh, giữ lại điểm chung quan trọng giữa nhiều thứ, để làm việc với chúng đồng nhất.

3 phần quan trọng:

  • Bỏ đi chi tiết: không mô tả mọi thứ — chọn lọc.
  • Giữ điểm chung: tìm common denominator giữa nhiều case cụ thể.
  • Đồng nhất: sau abstraction, code xử lý các case thông qua một interface chung.

Ví dụ đời thường

Cùng một chiếc ô tô, ba hệ thống khác nhau cần ba tập thuộc tính khác nhau — và thứ bị bỏ ở hệ thống này lại là trung tâm ở hệ thống kia:

Một chiếc ô tô toả xuống ba ngữ cảnh trạm thu phí, gara và cảnh sát giao thông, mỗi ngữ cảnh giữ một tập thuộc tính khác nhau

Ở trạm thu phí, abstraction là Vehicle(type, weight, plateNumber) — động cơ và màu sơn irrelevant. Ở gara thì chính mã máy mới là thứ không bỏ được: RepairableVehicle(model, engineCode, lastService). Còn với cảnh sát giao thông là IdentifiableVehicle(plate, owner, insuranceExpiry).

3 abstraction khác nhau cho cùng 1 đối tượng, cả ba đều đúng. Abstraction phụ thuộc ngữ cảnh — chọn theo use case hiện tại, không theo bản thân đối tượng.

Tầm quan trọng: giảm complexity

Não người xử lý được ~7 khái niệm cùng lúc (Miller's magic number). Nếu mỗi API làm việc với Sedan, SUV, Truck, Motorbike, Bicycle, Bus, Van... dev phải nhớ 7 class, mỗi class có quirks riêng. Thay bằng abstraction Vehicle, giờ chỉ 1 concept.

Code:

// Khong abstract
void chargeToll(Sedan s) { ... }
void chargeToll(SUV s) { ... }
void chargeToll(Truck t) { ... }
// ... 5 more overloads

Trừu tượng hoá lại, toàn bộ overload gom về một method duy nhất:

// Abstract
void chargeToll(Vehicle v) {
    int fee = v.type().baseFee() + v.weight() * 1000;
    ...
}

API caller không cần biết xe cụ thể. Thêm loại xe mới: implement Vehicle, chargeToll chạy không sửa.

3. Cách xác định abstraction từ bài toán

Đây là kỹ năng khó nhất — không có công thức cứng. Nhưng có quy trình định hướng:

Bước 1 — liệt kê các thực thể cụ thể

Ví dụ app quản lý tài chính cá nhân. Thực thể:

  • Ví tiền mặt
  • Thẻ tín dụng Visa
  • Thẻ tín dụng Mastercard
  • Tài khoản ngân hàng Techcombank
  • Tài khoản ngân hàng Vietcombank
  • Ví MoMo
  • Ví ZaloPay
  • Bitcoin wallet

8 thực thể. Nếu viết 8 class riêng, bạn nhân 8 lần boilerplate.

Bước 2 — tìm điểm chung theo ngữ cảnh

Ngữ cảnh: app xem số dư, chuyển tiền, xem lịch sử. Điểm chung:

  • Tất cả đều có số dư (balance).
  • Tất cả đều chuyển tiền đi (debit) hoặc nhận tiền (credit).
  • Tất cả đều có lịch sử giao dịch (transactions).

Đó là Account — abstraction đúng cho ngữ cảnh này.

interface Account {
    BigDecimal balance();
    void debit(BigDecimal amount);
    void credit(BigDecimal amount);
    List<Transaction> transactions();
}

Bước 3 — loại bỏ detail không thuộc ngữ cảnh

Trong abstraction Account, bỏ:

  • Loại thẻ Visa/Mastercard — app không quan tâm network.
  • Ngân hàng Techcombank vs Vietcombank — cùng là "bank account".
  • Bitcoin address — app coin wallet như account thông thường.

Detail này thuộc về implementation của class con:

class BankAccount implements Account {
    String bankName;
    String accountNumber;
    // logic cu the cho bank
}

class CryptoWallet implements Account {
    String walletAddress;
    String blockchain;
    // logic cu the cho crypto
}

class EWallet implements Account {
    String provider;   // "MoMo", "ZaloPay"
    // logic cu the cho e-wallet
}

Bước 4 — verify bằng use case

Test abstraction có đúng không: viết code dùng nó không biết implementation cụ thể.

public BigDecimal totalBalance(List<Account> accounts) {
    return accounts.stream()
        .map(Account::balance)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Method này chạy với bất kỳ combination nào: ví + thẻ + crypto + e-wallet — abstraction đã cover hết.

Nếu phải viết if (account instanceof BankAccount) ... else if (...), abstraction chưa đủ hoặc sai hướng.

Bước 5 — chấp nhận sửa abstraction khi yêu cầu thay đổi

Requirements đổi: thêm feature "chuyển khoản quốc tế" chỉ áp dụng bank account. Abstraction Account không có method này.

Không nên thêm internationalTransfer() vào Account chung — không phải tất cả đều có. Giải pháp:

  • Tạo sub-abstraction: interface InternationalTransferable extends Account.
  • BankAccount implements Account, InternationalTransferable.
  • Code muốn international transfer cast / check instanceof.

Đây gọi là Interface Segregation Principle (ISP) — SOLID bài sau. Abstraction không phải "viết 1 lần cho mãi mãi" — evolve với requirements.

4. 3 level of abstraction

Abstraction có nhiều tầng, từ thấp (gần concrete) đến cao (trừu tượng nặng):

Level 1 — Concrete class (không abstract)

class ArrayList<E> {
    Object[] data;
    int size;
    void add(E e) { ... }
}

Impl cụ thể, có state và logic đầy đủ. Instantiate trực tiếp.

Level 2 — Abstract class

abstract class AbstractList<E> implements List<E> {
    // Logic chung cho moi List: equals, hashCode, iterator
    public boolean equals(Object o) { ... }

    // Method abstract - con bat buoc implement
    public abstract E get(int index);
    public abstract int size();
}

một phần logic, một phần để lại cho con. ArrayList, LinkedList, Vector đều extends AbstractList để share logic common.

Dùng khi: có điểm chung code (không chỉ contract) giữa các class con.

Level 3 — Interface (pure contract)

interface List<E> {
    void add(E e);
    E get(int index);
    int size();
    // ... khong co logic, chi contract
}

Chỉ là hợp đồng — khai báo method, không implementation (trước Java 8 — hiện có default method nhưng tinh thần giữ là contract-focused).

Dùng khi: nhiều implementation không có chung code, chỉ chung semantic.

Chọn level nào

Câu hỏi chốt là có logic chung giữa các con hay không: không có thì interface (pure contract); có thì abstract class để viết logic đó một lần — trừ khi ô extends đã bị dùng cho việc khác, khi đó quay về interface với default method hoặc composition.

Quy tắc thực tế:

  • Interface trước. Nếu impl class thực sự share logic, refactor sang abstract class sau.
  • Abstract class khi có 30-70% code chung không viết 2 lần.
  • Concrete class khi không cần abstraction (chỉ 1 loại, không dự kiến mở rộng).

Java 8+ có default method trong interface, nên interface cũng chứa được logic. Ranh giới abstract class vs interface mờ hơn. Nhưng abstract class vẫn có ưu thế: field instance, constructor, protected helper.

5. Java cung cấp gì để hiện thực abstraction

Bộ công cụ xếp theo đúng ba mức ở phần trên, từ trừu tượng thuần tuý xuống cụ thể: interface (chỉ mô tả "can-do", không state, implement được nhiều cái) → abstract class (mô tả "is-a", giữ được state và logic chung, chỉ một cha) → concrete class. Dùng abstract class khi abstraction có state chung và template method cho con customize; dùng interface cho capability-based abstraction (Runnable, Comparable, Iterable — "thứ nào chạy được", "thứ nào so sánh được", "thứ nào duyệt được"). Ranh giới thiết kế giữa chúng đi sâu ở bài 05, 06 và 07 của module này.

Các công cụ nâng cao (Java 16+)

  • sealed class / sealed interface (Java 17+): Giới hạn danh sách con được phép thừa kế nhằm tạo ra các phân cấp trừu tượng đóng kín (Closed Hierarchy). Chúng ta sẽ tìm hiểu chi tiết ở bài 07.
  • record (final ở Java 16): Lớp dữ liệu bất biến (Immutable), giúp thiết kế nhanh các cấu trúc dữ liệu phẳng hay Value Object trong lập trình hướng đối tượng.
  • Generic <T>: Abstraction trên kiểu — class/method tổng quát over types (sẽ học kỹ ở Module 3).

6. 📚 Deep Dive

📚 Deep Dive — sách và paper nên đọc

Sách:

  • "Clean Code" - Robert C. Martin Ch.6 Objects and Data Structures, Ch.10 Classes — abstraction principles.
  • "Domain-Driven Design" - Eric Evans — abstraction ở level business domain.

Paper / essay:

Ghi chú: Abstraction là skill phát triển qua năm — không học từ 1 bài. Mỗi code review, mỗi refactor là cơ hội rèn. Rule: khi viết class mới, tự hỏi "abstraction tôi đang tạo là gì? Ngữ cảnh nào? Có ≥2 implementation không?".

7. Tóm tắt

  • 4 trụ cột OOP: Encapsulation, Inheritance, Polymorphism, Abstraction. Abstraction là tư duy, 3 cột kia là cơ chế.
  • Abstraction = bỏ chi tiết không quan trọng, giữ điểm chung quan trọng, xử lý đồng nhất.
  • Abstraction phụ thuộc ngữ cảnh — cùng đối tượng có nhiều abstraction khác nhau tuỳ use case.
  • Quy trình xác định abstraction: (1) liệt kê thực thể, (2) tìm điểm chung theo ngữ cảnh, (3) loại detail, (4) verify bằng use case, (5) evolve khi requirements đổi.
  • 3 level: concrete class → abstract class (logic chung) → interface (pure contract). Dùng interface trước, refactor abstract class khi thực sự chia sẻ code.
  • Java tools: abstract class, interface, generic <T>, record (Java 16) và phân cấp đóng kín sealed (Java 17).
  • Abstraction đúng là tiền đề cho mọi thiết kế mở rộng được — bài 02 sẽ chỉ cách nhận diện abstraction sai.

8. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Vì sao abstraction là trụ cột quan trọng nhất trong 4 trụ cột OOP?
  2. Q2
    Vì sao nói abstraction 'phụ thuộc ngữ cảnh'? Cho ví dụ một đối tượng có 2 abstraction khác nhau đều đúng.
  3. Q3
    Cho yêu cầu: app quản lý media của 1 studio gồm movie, podcast, short-form video (TikTok style). Abstraction nào phù hợp nếu mục đích app là 'upload, gán metadata, publish'?
  4. Q4
    Khi nào nên dùng interface, khi nào nên dùng abstract class?
  5. Q5
    Bước 'verify bằng use case' (bước 4) kiểm tra điều gì, và dấu hiệu nào cho thấy abstraction chưa đúng?

Bài tiếp theo: Nhận diện abstraction sai — over-engineering trap và cách sửa

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Ôn phỏng vấn

Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Nhận diện abstraction sai — over-engineering trap và cách sửa