NỘI DUNG

Hosting tốc độ cao Vietnix - tốc độ tải trang trung bình dưới 1 giây
VPS siêu tốc Vietnix - trải nghiệm mượt mà, ổn định
24/04/2024
Lượt xem

SOLID là gì? Lợi ích và cách sử dụng SOLID đơn giản nhất

24/04/2024
15 phút đọc
Lượt xem

Đánh giá

5/5 - (133 bình chọn)

Trong lĩnh vực lập trình, việc xây dựng những phần mềm ổn định, dễ bảo trì và mở rộng luôn là mục tiêu mà các nhà phát triển theo đuổi. Để đạt được mục tiêu này, bộ nguyên tắc SOLID đóng vai trò vô cùng quan trọng. Bài viết dưới đây sẽ cung cấp cho bạn một cái nhìn tổng quan về SOLID là gì, giải thích cụ thể từng nguyên tắc cũng như hướng dẫn cách áp dụng chúng vào lập trình hiệu quả.

SOLID là gì?

SOLID là viết tắt của 5 nguyên tắc thiết kế hướng đối tượng do Robert C. Martin và Michael Feathers đề xuất. Nhờ áp dụng SOLID, lập trình viên có thể viết ra những đoạn code dễ đọc, dễ hiểu và dễ bảo trì.

Solid là viết tắt của 5 nguyên tắc thiết kế hướng đối tượng
Solid là viết tắt của 5 nguyên tắc thiết kế hướng đối tượng

5 nguyên tắc trong SOLID bao gồm:

  • Single Responsibility Principle (SRP)  – Nguyên tắc trách nhiệm đơn lẻ.
  • Open/Closed Principle (OCP) – Nguyên tắc mở/đóng.
  • Liskov Substitution Principle (LSP) – Nguyên tắc thay thế Liskov.
  • Interface Segregation Principle (ISP) – Nguyên tắc phân tách giao diện.
  • Dependency Inversion Principle (DIP) –  Nguyên tắc đảo ngược phụ thuộc.

Tổng quan về SOLID

Lập trình hướng đối tượng (Object Oriented Programming – OOP) là một trong những phương pháp lập trình phổ biến nhất hiện nay. Nhờ những đặc điểm nổi bật như tính trừu tượng, đóng gói, kế thừa và đa hình, OOP giúp lập trình viên giải quyết các vấn đề thực tế một cách hiệu quả:

  • Tính trừu tượng (Abstraction): Cho phép tạo ra các lớp mô phỏng các đối tượng thực tế trong thế giới thật.
  • Tính đóng gói (Encapsulation):  Bảo vệ dữ liệu bên trong lớp và chỉ cung cấp các phương thức truy cập an toàn.
  • Tính kế thừa (Inheritance): Tái sử dụng code hiệu quả bằng cách cho phép các lớp kế thừa tính năng từ các lớp cha.
  • Tính đa hình (Polymorphism): Cho phép thực hiện một hành động theo nhiều cách khác nhau tùy thuộc vào loại đối tượng cụ thể.
OOP là một trong những phương pháp lập trình phổ biến
OOP là một trong những phương pháp lập trình phổ biến

Tuy nhiên, việc kết hợp hiệu quả các tính chất này không phải điều dễ dàng. Nguyên tắc SOLID ra đời nhằm giải quyết vấn đề này, giúp các lập trình viên viết mã OOP một cách hiệu quả hơn.

Lợi ích của SOLID

Khi áp dụng SOLID vào viết code, lập trình viên sẽ nhận được nhiều lợi ích như:

  • Giảm thiểu sự phức tạp: SOLID giúp chia nhỏ phần mềm thành các thành phần độc lập, mỗi thành phần đảm nhiệm một chức năng riêng biệt. Nhờ vậy, code trở nên dễ đọc, dễ hiểu và dễ dàng sửa đổi hơn.
  • Dễ dàng bảo trì và mở rộng: Khi cần thay đổi, bạn chỉ cần tác động vào một phần nhỏ thay vì ảnh hưởng đến toàn bộ hệ thống. Nhờ vậy, việc bảo trì và mở rộng phần mềm trở nên đơn giản và hiệu quả hơn, đặc biệt là đối với các dự án lớn.
  • Tăng tính linh hoạt và khả năng tái sử dụng: Các thành phần được thiết kế linh hoạt, có thể dễ dàng áp dụng vào các dự án khác nhau, nhờ đó tiết kiệm thời gian và chi phí cho việc phát triển phần mềm.

5 nguyên lý SOLID và cách sử dụng

Dù bạn đang muốn tìm hiểu SOLID trong Java hay SOLID trong C# thì đều cần nắm vững 5 nguyên lý chính sau:

Single responsibility principle

Nguyên tắc cốt lõi: Mỗi lớp chỉ nên đảm nhận một nhiệm vụ cụ thể.

Single responsibility là nguyên tắc đầu tiên, ứng với chữ “S” trong bộ nguyên tắc SOLID, nhấn mạnh rằng mỗi lớp chỉ nên đảm nhận một trách nhiệm duy nhất. Việc gán quá nhiều nhiệm vụ cho một lớp sẽ khiến lớp này trở nên phức tạp, khó hiểu và khó bảo trì. Trong ngành IT, yêu cầu thay đổi và bổ sung chức năng là điều thường xuyên xảy ra nên việc sở hữu code rõ ràng, dễ hiểu là vô cùng quan trọng.

Nguyên tắc này nói rằng mỗi lớp chỉ nên đảm nhận một trách nhiệm duy nhất
Nguyên tắc này nói rằng mỗi lớp chỉ nên đảm nhận một trách nhiệm duy nhất

Ví dụ:

Hãy tưởng tượng một công ty phần mềm có 3 vị trí công việc: lập trình viên (developer), kiểm thử phần mềm (tester) và nhân viên bán hàng (salesman). Mỗi nhân viên sẽ có một chức vụ và thực hiện công việc tương ứng. Vậy bạn có nên thiết kế lớp “Employee” với thuộc tính “position” và 3 phương thức developSoftware(), testSoftware() và saleSoftware() không?

Câu trả lời là KHÔNG.

Hãy hình dung nếu có thêm vị trí quản lý nhân sự, bạn sẽ phải sửa đổi lớp “Employee” và thêm phương thức mới. Vậy nếu có thêm 10 vị trí khác thì sao? Khi đó, các đối tượng được tạo ra sẽ có rất nhiều phương thức dư thừa. Ví dụ, developer không cần sử dụng hàm testSoftware() và saleSoftware(), và việc sử dụng sai phương thức có thể dẫn đến hậu quả nghiêm trọng.

Áp dụng nguyên tắc Single Responsibility

Hãy tạo một lớp trừu tượng “Employee” với phương thức working(). Sau đó, kế thừa từ lớp này để tạo ra 3 lớp cụ thể: Developer, Tester và Salesman. Mỗi lớp sẽ triển khai phương thức working() riêng theo chức năng của từng vị trí. Nhờ vậy, việc nhầm lẫn phương thức sẽ không còn xảy ra.

Open/Closed principle

Nguyên tắc cốt lõi: Không sửa đổi lớp có sẵn, thay vào đó hãy mở rộng bằng kế thừa.

Open/Closed là nguyên tắc thứ hai trong SOLID, tương ứng với chữ “O” nhấn mạnh rằng khi cần bổ sung chức năng cho chương trình, ta nên tạo lớp mới kế thừa (hoặc sử dụng) lớp cũ thay vì chỉnh sửa trực tiếp lớp hiện tại. Điều này khiến chương trình có nhiều lớp hơn, nhưng bù lại ta không cần kiểm thử lại các lớp cũ mà chỉ tập trung vào các lớp mới.

Đây là nguyên tắc khi cần bố sung chức năng nên tạo một lớp mới để kế thừa
Đây là nguyên tắc khi cần bổ sung chức năng nên tạo một lớp mới để kế thừa

Tuy nhiên, việc mở rộng chức năng thường đi kèm với việc viết thêm code. Để thiết kế module dễ dàng mở rộng mà không cần sửa đổi code nhiều, ta cần tách biệt phần dễ thay đổi khỏi phần khó thay đổi, đảm bảo không ảnh hưởng đến những phần còn lại.

Ví dụ: 

Giả sử chúng ta có một lớp ConnectionManager dùng để quản lý kết nối đến cơ sở dữ liệu (CSDL). Ban đầu, lớp này chỉ hỗ trợ kết nối với SQL Server và MySQL.

class ConnectionManager 

{

  public function connect(Connection $connection) 

{

    if ($connection instanceof SqlServer) {

      // Kết nối với SQL Server

    } else if ($connection instanceof MySql) {

      // Kết nối với MySQL

    }

  }

}

Sau đó, yêu cầu được đặt ra là cần hỗ trợ thêm kết nối với Oracle và các CSDL khác. Để đáp ứng yêu cầu này, chúng ta có thể sửa đổi mã nguồn của lớp ConnectionManager bằng cách thêm các khối else-if cho các hệ quản trị CSDL mới. Tuy nhiên, cách này làm cho mã nguồn trở nên cồng kềnh và khó quản lý.

Áp dụng nguyên tắc Open/Closed

Áp dụng nguyên tắc OCP, chúng ta có thể thiết kế lại như sau:

  • Định nghĩa một lớp cơ sở Connection để mô tả các phương thức chung cho tất cả các lớp kết nối.
  • Áp dụng Abstract để tạo các lớp SqlServer, MySql, Oracle,… kế thừa từ lớp cơ sở Connection và triển khai phương thức doConnect cho từng lớp.
  • Lớp ConnectionManager chỉ cần sử dụng phương thức doConnect() của đối tượng Connection để kết nối đến cơ sở dữ liệu.

Thiết kế sau khi sửa đổi:

abstract class Connection 
{
    public abstract void doConnect();
}
class SqlServer extends Connection 
{
    public void doConnect() 
   {
          //connect with SqlServer
    }
}
class MySql extends Connection 
{
    public void doConnect() 
   {
        //connect with MySql
    }
}
class Oracle extends Connection 
{
  public function doConnection(Connection $connection)
 {
    //something
    //.................
    //connection
    $connection->doConnect();
  }
}
}

Với thiết kế này, lớp ConnectionManager không cần sửa đổi khi thêm các loại CSDL mới. Bạn chỉ cần tạo lớp mới kế thừa từ Connection và thực thi phương thức doConnect tương ứng cho loại CSDL đó.

Liskov substitution principle

Nguyên tắc cốt lõi: Đối tượng (instance) của lớp con có thể thay thế cho đối tượng của lớp cha mà không gây lỗi.

Liskov substitution là nguyên tắc thứ 3 trong bộ nguyên tắc SOLID, tương ứng với chữ “L”. Nguyên tắc này quy định rằng các lớp con phải kế thừa và duy trì hành vi cơ bản của lớp cha. Nếu vi phạm nguyên tắc này, chương trình có thể gặp lỗi khi sử dụng các đối tượng của lớp con thay thế cho các đối tượng của lớp cha.

Nguyên tắc này quy định lớp con phải kế thừa duy trì hành vi của lớp cha
Nguyên tắc này quy định lớp con phải kế thừa duy trì hành vi của lớp cha

Ví dụ: 

Tiếp nối ví dụ lớp Employee ở nguyên tắc 1, giả sử công ty đó tiến hành chấm công nhân viên chính thức vào mỗi buổi sáng. Lúc này, ta sẽ bổ sung thêm phương thức  checkAttendance() cho lớp Employee.

Tuy nhiên, công ty cũng thuê thêm nhân viên lao công làm vệ sinh văn phòng. Những nhân viên này không phải là nhân viên chính thức nên không được cấp ID cũng như  không được chấm công.

Lúc này bạn có thể tạo ra lớp CleanerStaff kế thừa từ Employee và thực thi hàm working() cho lớp này. Tuy nhiên, lớp CleanerStaff cũng sẽ kế thừa phương thức checkAttendance() để điểm danh, vi phạm quy định và gây lỗi chương trình. Do đó, thiết kế lớp CleanerStaff kế thừa từ Employee là không hợp lý.

Áp dụng nguyên tắc Liskov substitution

Có thể giải quyết vấn đề này bằng cách tách hàm checkAttendance() ra một giao diện riêng và chỉ dành cho các lớp Developer, Tester và Salesman. Lớp CleanerStaff sẽ không thực thi được giao diện trên nên sẽ không thể sử dụng hàm checkAttendance() để điểm danh. 

Interface segregation principle

Nguyên tắc cốt lõi: Thay vì sử dụng một giao diện (interface) lớn, hãy tách thành nhiều giao diện nhỏ hơn với các mục đích cụ thể.

Interface segregation là nguyên tắc thứ 4 trong bộ nguyên tắc SOLID, tương ứng với chữ “I”. Hãy tưởng tượng bạn đang đối mặt với một interface khổng lồ có khoảng 100 methods. Lúc này việc implements interface rất khó khăn bởi vì các lớp buộc phải thực thi tất cả các method trong interface. Điều này dẫn đến tình trạng dư thừa khi một lớp không cần sử dụng hết 100 methods đó.

Đây là nguyên tắc giúp bạn thiết kế hệ thống linh hoạt, dễ bảo trì và mở rộng hơn
Đây là nguyên tắc giúp bạn thiết kế hệ thống linh hoạt, dễ bảo trì và mở rộng hơn

Giải pháp chính là chia nhỏ interface lớn này thành các interface con, mỗi interface chỉ chứa methods liên quan chặt chẽ với nhau. Nhờ vậy, việc implements và quản lý trở nên dễ dàng, hiệu quả hơn.
Ví dụ: 

Đoạn code sau thể hiện giao diện Animal với các phương thức eat(), run(), và fly():

interface Animal {

    void eat();

    void run();

    void fly();

}

Tuy nhiên, việc sử dụng giao diện chung cho cả mèo (cat) và nhện (spider) là không hợp lý vì mèo không thể bay (fly) và nhện không thể chạy (run).

Áp dụng nguyên tắc Interface segregation

Để giải quyết vấn đề này, ta nên chia tách giao diện Animal thành 3 giao diện riêng biệt cho từng phương thức

interface Animal {

    void eat();

}

interface RunnableAnimal extends Animal {

    void run();

}

interface FlyableAnimal extends Animal {

    void fly();

}

Dependency inversion principle

Nguyên tắc cốt lõi:

  • Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstractions (sự trừu tượng).
  • Abstractions không nên phụ thuộc vào chi tiết (implementation) mà chi tiết nên phụ thuộc vào abstractions.

Dependency inversion có thể được hiểu như sau: các thành phần trong một chương trình chỉ nên phụ thuộc vào các khái niệm trừu tượng (abstractions). Các khái niệm trừu tượng này không nên phụ thuộc vào những chi tiết cụ thể mà ngược lại, chính những chi tiết cụ thể nên phụ thuộc vào chúng.

Các khái niệm trừu tượng là những yếu tố ổn định, ít biến đổi, chúng bao gồm những đặc tính chung nhất của các yếu tố cụ thể. Dù các yếu tố cụ thể có thể rất khác nhau nhưng chúng vẫn tuân theo những nguyên tắc chung mà khái niệm trừu tượng đã định nghĩa. Sự phụ thuộc vào khái niệm trừu tượng giúp cho chương trình trở nên linh hoạt và thích ứng tốt hơn với các thay đổi liên tục.

Ví dụ: 

Đối với ổ cứng máy tính, bạn có thể sử dụng ổ cứng thể rắn SSD mới nhất để tăng tốc độ truy cập dữ liệu, nhưng ổ đĩa quay HDD cũng hoàn toàn phù hợp. Nhà sản xuất mainboard không thể biết bạn sẽ chọn loại ổ nào, nhưng họ luôn đảm bảo mainboard tương thích với cả hai chuẩn giao tiếp SATA để bạn gắn vào bo mạch chủ dễ dàng. Trong trường hợp này, chuẩn giao tiếp SATA đóng vai trò là interface (giao diện), còn SSD và HDD là những implementation (trình triển khai) cụ thể.

Áp dụng nguyên tắc Dependency inversion

Tương tự, khi áp dụng nguyên lý này trong lập trình, ta thường sử dụng interface thay vì kiểu kế thừa cụ thể ở các lớp trừu tượng cấp cao. Chẳng hạn, để kết nối với cơ sở dữ liệu, ta thường thiết kế lớp trừu tượng DataAccess bao gồm các phương thức chung như save(), get(),… Sau đó, tùy theo loại DBMS nào được sử dụng (ví dụ như MySQL, MongoDB,…), ta sẽ kế thừa và triển khai phương thức cụ thể. Nguyên lý này tận dụng tối đa tính đa hình của lập trình hướng đối tượng (OOP).

Câu hỏi thường gặp

OOP là gì?

OOP là viết tắt của Object-Oriented Programming, hay Lập trình hướng đối tượng. Đây là một mô hình lập trình dựa trên khái niệm đối tượng (object). Mỗi đối tượng đại diện cho một thực thể trong thế giới thực và có hai thành phần chính:

– Thuộc tính (attribute): Miêu tả trạng thái của đối tượng, ví dụ như màu sắc, kích thước, tên,…
– Phương thức (method): Các hành động mà đối tượng có thể thực hiện, ví dụ như di chuyển, thay đổi màu sắc, phát ra âm thanh,…

OOP được thiết kế để tăng khả năng tái sử dụng code, giúp việc lập trình trở nên linh hoạt hơn và dễ quản lý hơn. Một số ngôn ngữ lập trình phổ biến hỗ trợ OOP bao gồm Java, C++, Python, và Ruby.

Design pattern là gì? 

Design pattern (Mẫu thiết kế) là một giải pháp tiêu chuẩn, đã được kiểm chứng để giải quyết các vấn đề thường gặp trong lập trình. Mẫu thiết kế không phải là một đoạn code cụ thể mà là một khuôn mẫu để giải quyết vấn đề một cách hiệu quả và có thể tái sử dụng.

Lời kết

Trên đây là những thông tin mà Vietnix muốn chia sẻ tới bạn về khái niệm SOLID là gì cũng như cách sử dụng từng nguyên tắc cụ thể trong bộ nguyên tắc này. Việc học hỏi và áp dụng các nguyên tắc SOLID có thể tốn nhiều thời gian, nhưng lợi ích mà chúng mang lại là không hề nhỏ. Nếu bạn là một nhà phát triển phần mềm, hãy dành thời gian để tìm hiểu về SOLID và bắt đầu áp dụng các nguyên tắc này vào các dự án ngay từ hôm nay.

THEO DÕI VÀ CẬP NHẬT CHỦ ĐỀ BẠN QUAN TÂM

Đăng ký ngay để nhận những thông tin mới nhất từ blog của chúng tôi. Đừng bỏ lỡ cơ hội truy cập kiến thức và tin tức hàng ngày

Chọn chủ đề :

Kết nối với mình qua

Kết nối với mình qua

Tăng tốc độ website - Nâng tầm giá trị thương hiệu

Tăng tốc tải trang

95 điểm

Nâng cao trải nghiệm người dùng

Tăng 8% tỷ lệ chuyển đổi

Thúc đẩy SEO, Google Ads hiệu quả

Tăng tốc ngay

SẢN PHẨM NỔI BẬT

7 NGÀY DÙNG THỬ HOSTING

NẮM BẮT CƠ HỘI, THÀNH CÔNG DẪN LỐI

Cùng trải nghiệm dịch vụ hosting tốc độ cao được hơn 100,000 khách hàng sử dụng

ĐĂNG KÝ NHẬN TÀI LIỆU THÀNH CÔNG
Cảm ơn bạn đã đăng ký nhận tài liệu mới nhất từ Vietnix!
ĐÓNG

ĐĂNG KÝ DÙNG THỬ HOSTING

7 NGÀY MIỄN PHÍ

ĐĂNG KÝ DÙNG THỬ HOSTING

7 NGÀY MIỄN PHÍ

XÁC NHẬN ĐĂNG KÝ DÙNG THỬ THÀNH CÔNG
Cảm ơn bạn đã đăng ký thông tin thành công. Đội ngũ CSKH sẽ liên hệ trực tiếp để kích hoạt dịch vụ cho bạn nhanh nhất!
ĐÓNG