Backend

99 Ngày Java — Ngày 91: SOLID

SSite Admin
26 tháng 10, 2026 3 phút đọc 6 lượt xem
99 Ngày Java — Ngày 91: SOLID

SOLID là năm nguyên tắc giúp code hướng đối tượng dễ thay đổi: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation và Dependency Inversion. Chúng không phải luật phải tuân theo mọi lúc, mà là công cụ chẩn đoán: khi một thay đổi nhỏ buộc bạn sửa nhiều nơi, thường có một nguyên tắc đang bị vi phạm.

SOLID

1. Single Responsibility: một lý do để thay đổi

Một lớp nên chỉ có một lý do để thay đổi, tức là phục vụ một nhóm người ra yêu cầu. Lớp vừa tính lương, vừa định dạng báo cáo, vừa ghi database sẽ bị sửa khi kế toán, bộ phận báo cáo hoặc DBA đổi ý, và mỗi lần sửa có thể làm hỏng hai phần còn lại. Tách theo lý do thay đổi, không tách theo số dòng: một lớp dài nhưng chỉ làm một việc vẫn ổn.

// before: three reasons to change in one class
class Invoice {
    Money total() { ... }
    String toPdf() { ... }
    void save(Connection connection) { ... }
}

// after
record Invoice(List<Line> lines) { Money total() { ... } }
class InvoicePdfRenderer { byte[] render(Invoice invoice) { ... } }
class InvoiceRepository { void save(Invoice invoice) { ... } }

2. Open/Closed và Liskov Substitution

Open/Closed: thêm hành vi mới bằng cách thêm code mới, không sửa code đang chạy tốt. Một chuỗi if theo loại phải sửa mỗi khi có loại mới là dấu hiệu rõ nhất; thay bằng interface và mỗi loại một lớp cài đặt. Liskov Substitution: ở đâu dùng được kiểu cha thì phải dùng được kiểu con mà không bất ngờ. Lớp con ném UnsupportedOperationException cho một phương thức của cha, hoặc siết chặt điều kiện đầu vào, là vi phạm.

interface ShippingFee { Money feeFor(Order order); }

class StandardShipping implements ShippingFee {
    public Money feeFor(Order order) { return Money.of(30_000); }
}
class ExpressShipping implements ShippingFee {
    public Money feeFor(Order order) { return Money.of(60_000); }
}
// A new shipping type is a new class; existing classes stay untouched.

3. Interface Segregation và Dependency Inversion

Interface Segregation: client không nên bị buộc phụ thuộc vào phương thức nó không dùng. Một interface lớn làm mọi lớp cài đặt phải viết phương thức rỗng, và mọi client bị ảnh hưởng khi một phần không liên quan thay đổi. Dependency Inversion: logic nghiệp vụ phụ thuộc vào abstraction do chính nó định nghĩa, còn chi tiết như database hay cổng thanh toán cài đặt abstraction đó. Trong Java, điều này thường là constructor injection với một interface.

interface PaymentGateway { Receipt charge(String orderId, Money amount); }

class CheckoutService {
    private final PaymentGateway gateway;          // abstraction
    CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
}

class StripeGateway implements PaymentGateway { ... } // detail

4. Code smell và dùng SOLID có chừng mực

Vài tín hiệu đáng chú ý: lớp có tên chứa Manager hoặc Util và cứ lớn dần; một thay đổi nghiệp vụ nhỏ chạm vào nhiều file; chuỗi if hoặc switch theo loại lặp lại ở nhiều nơi; lớp con phải vô hiệu hóa hành vi của cha; test cần dựng cả database để kiểm một phép tính. Ngược lại, áp dụng SOLID trước khi có nhu cầu thật tạo ra tầng tầng interface chỉ có một cài đặt.

  • Chờ lần thay đổi thứ hai rồi mới trừu tượng hóa theo hướng thay đổi đó.

  • Ưu tiên composition hơn kế thừa; kế thừa chỉ khi quan hệ là-một thật sự đúng với hành vi.

  • Interface nhỏ đặt cạnh client dùng nó, không đặt cạnh lớp cài đặt.

  • Sealed type và switch đầy đủ là một cách khác để mở rộng an toàn khi tập loại do bạn kiểm soát.

SOLID trả lời một câu hỏi: khi yêu cầu đổi, phải sửa bao nhiêu chỗ. Ngày 92 bắt đầu loạt design pattern với hai mẫu tạo đối tượng: Singleton và Factory.

Tài liệu đối chiếu

  • https://en.wikipedia.org/wiki/SOLID

  • https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html

S

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.

Bài viết liên quan

99 Ngày Spring — Ngày 91: Config và Secrets cho Production

Cấu hình theo 12-factor, thứ tự ưu tiên của property, @ConfigurationProperties có kiểm tra và cách quản lý secret an toàn.

26 thg 10, 20263 phút5
99 Ngày Spring — Ngày 90: Docker Compose cho Dev

compose.yaml cho Postgres, Redis và Kafka, spring-boot-docker-compose tự khởi động và tự nối kết, cùng ranh giới với Testcontainers.

25 thg 10, 20262 phút4
99 Ngày Java — Ngày 90: Tổ chức Project và Clean Code

Package theo feature, đặt tên nói lên ý định, hàm nhỏ với guard clause và những nguyên tắc clean code dùng được ngay.

25 thg 10, 20263 phút4