SOLID Principles nguyên tắc thiết kế trong OOP

2nd Nov 2022
SOLID Principles nguyên tắc thiết kế trong OOP
Table of contents

Nếu là một developer, chắc các bạn đều đã nghe tới một số khái niệm trong OOP cơ bản như sau:

  • Abstraction (Tính trừu tượng)
  • Encapsulation (Tính bao đóng)
  • Inheritance (Tính kế thừa)
  • Polymophirsm (Tính đa hình)

Nhưng hôm nay mình sẽ không đề cập đến những tính chất trên mà mình sẽ giới thiệu đến mọi người tới chủ đề: Những nguyên tắc thiết kế trong OOP. Đây được coi là những nguyên lý được đúc kết bởi kinh nghiệm sương máu của vô số developer.

Chắc tới đây các bạn cũng đã nghĩ tới nguyên tắc SOLID rồi phải không?

SOLID là từ viết tắt của 5 nguyên tắc thiết kế hướng đối tượng(OOP). Các nguyên tắc này định hình ra một quy chuẩn cho việc phép triển phần mềm với các cân nhắc để duy trì và mở rộng khi phát triển dự án.

SOLID là viết tắt của 5 nguyên tắc sau:

  • S - Single-responsiblity Principle
  • O - Open-closed Principle
  • L - Liskov Substitution Principle
  • I - Interface Segregation Principle
  • D - Dependency Inversion Principle

Trong bài biết này mình sẽ giới thiệu tới các bạn từng nguyên tắc một để các bạn có một cái nhìn thật tổng quan và chi tiết nhất.

Single-Responsibility Principle

Nguyên tắc này được phát biểu như sau: Một class nên có một và chỉ một lý do để thay đổi, nghĩa là một class chỉ làm một công việc duy nhất.

Ví dụ: chúng ta có một ứng dụng lấy một tập hợp các hình - hình tròn và hình vuông - và tính tổng diện tích của tất cả các hình đó.

Đối với hình vuông bạn sẽ cần biết được chiều dài của một cạnh:

Solid

Đối với hình tròn, bạn sẽ cần biết được bán kính của nó:

SOLID Principles nguyên tắc thiết kế trong OOP

Tiếp theo, tạo class AreaCalculator và sau đó viết logic để tính diện tích của các hình được cung cấp:

SOLID Principles nguyên tắc thiết kế trong OOP

Tiếp theo là sử dụng, bạn sẽ cần khởi tạo class AreaCalculator và truyền vào một mảng các hình dạng sau đó gọi là output() để hiển thị kết quả:

SOLID Principles nguyên tắc thiết kế trong OOP

Tất cả logic đang được xử lý bởi class AreaCalculator . Điều này đã vi phạm nguyên tắc Single-Responsibility, class AreaCalculator chỉ nên chứa logic tính tổng diện tích của các hình và nó không nên biết đến định dạng kết quả đầu ra là gì.

Để giải quyết vấn đề này, bạn có thể tạo một class SumCalculatorOutputter để chuyên xử lý logic bạn cần để xuất dữ liệu cho người dùng:

SOLID Principles nguyên tắc thiết kế trong OOP

Và sau đó việc sử dụng class SumCalculatorOutputter  sẽ như sau:

SOLID Principles nguyên tắc thiết kế trong OOP

Bây giờ, logic bạn cần để xuất dữ liệu cho người dùng được xử lý bởi class SumCalculatorOutputter . Điều này đã thỏa mãn nguyên tắc Single-Responsibility

Open-Closed Principle

Nguyên tắc này được phát biểu như sau: Các đối tượng hoặc thực thể phải được mở để mở rộng nhưng bị đóng để sửa đổi.

Điều này có nghĩa là một class có thể được mở rộng mà không cần sửa đổi chính class đó. Mở rộng ở đây được hiểu là một class extends một class khác, hoặc là tái sử dụng chức năng tồn tại bên trong một class.

Tiếp tục với ví dụ trên, nhưng bạn hãy tập trung vào phương thức sum(). Hãy xem xét một trường hợp nếu người dùng muốn thêm các hình như tam giác, v.v. Bạn sẽ phải đi sửa đổi lại function này, cụ thể là bổ sung thêm khối if/else. Và điều này đã vi phạm nguyên tắc Open-Closed.

Cách giải quyết vấn đề này là bạn loại bỏ logic tính toán diện tích của mỗi hình học ra khỏi phương thức sum() và giao nó cho class của từng hình học quản lý.

Đây là phương thức area() được định nghĩa bên trong Square:

SOLID Principles nguyên tắc thiết kế trong OOP

Và đây là phương thức area() được định nghĩa trong Circle:

SOLID Principles nguyên tắc thiết kế trong OOP

Phương thức sum() trong class AreaCalculator được viết lại như sau:

SOLID Principles nguyên tắc thiết kế trong OOP

Bây giờ, bạn có thể tạo một class hình dạng khác và chuyển nó vào khi tính tổng mà không vi phạm nguyên tắc Open-Closed.

Tuy nhiên có một vấn để khác lại nảy sinh là làm thế nào để bạn biết rằng đối tượng được truyền vào class AreaCalculator thực sự là một hình và bên trong class của hình đó đã có phương thức area() ?

Câu trả lời ở đây chính là Interface, Interface là một phần không thể thiếu trong SOLID.

Tại đây bạn tạo một function area() bên trong một interface là ShapeInterface.

SOLID Principles nguyên tắc thiết kế trong OOP

Tiếp theo bạn sửa đổi lại các class để implement interface ShapeInterface:

Đây là bản cập nhật cho Square:

SOLID Principles nguyên tắc thiết kế trong OOP

Và đây là bản cập nhật cho Circle:

SOLID Principles nguyên tắc thiết kế trong OOP

Và trong phương thức sum(), bạn có thể kiểm tra xem các hình dạng được cung cấp có thực sự là instances của ShapeInterface.

SOLID Principles nguyên tắc thiết kế trong OOP

Liskov Substitution Principle

Nguyên tắc này được phát biểu như sau: Gọi q(x) là một thuộc tính có thể cho phép đối với các đối tượng của x thuộc loại T. Khi đó q(y) sẽ có thể cho phép đối với các đối tượng y thuộc loại S trong đó S là một kiểu con của T.

Điều này có nghĩa là mọi lớp con hoặc lớp dẫn xuất phải có thể thay thế cho lớp cơ sở hoặc lớp cha của chúng.

Hãy xem một ví dụ về class VolumeCalculator mở rộng class AreaCalculator.

SOLID Principles nguyên tắc thiết kế trong OOP

Class SumCalculatorOutputter đã được định nghĩa ở trên với nội dung như sau:

SOLID Principles nguyên tắc thiết kế trong OOP

Nếu bạn cố gắng chạy một ví dụ như thế này:

SOLID Principles nguyên tắc thiết kế trong OOP

Khi bạn gọi phương thức HTML() trên đối tượng $output2, bạn sẽ gặp lỗi về việc chuyển đổi mảng thành chuỗi.Để khắc phục điều này, thay vì trả vì một mảng ở phương thức sum() trong class VolumeCalculator chúng ta hãy trả về $summedData:

SOLID Principles nguyên tắc thiết kế trong OOP

Interface Segregation Principle

Nguyên tắc này được phát biểu như sau: Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.

Vẫn là ShapeInterface từ ví dụ trước,  ở đây gỉa sử ứng dụng của bạn cần phải bổ sung thêm các hình khối và hình cầu. Và các hình dạng này sẽ cần phải tính toán thể tích.

SOLID Principles nguyên tắc thiết kế trong OOP

Bây giờ, bất kỳ hình dạng nào bạn tạo đều phải triển khai phương thức volume(), nhưng bạn biết rằng hình vuông là hình phẳng và chúng không có khối lượng, vì vậy với interface này nó sẽ buộc class Square thực hiện một phương thức mà nó không sử dụng.

Điều này sẽ vi phạm nguyên tắc Interface Segregation. Thay vào đó, bạn có thể tạo ra một interface khác được gọi là ThreeDimensionalShapeInterfacecó function volume() và các hình dạng ba chiều có thể implement interface này.

SOLID Principles nguyên tắc thiết kế trong OOP

Đây là một cách tiếp cận tốt hơn nhiều, nhưng có một vấn đề cần chú ý khi sử dụng các interface này. Thay vì sử dụng một ShapeInterfacehoặc một ThreeDimensionalShapeInterface, bạn có thể tạo ra một interface khác như ManageShapeInterface và triển khai nó trên cả ba hình phẳng và hình dạng ba chiều.

Bằng cách này bạn sẽ có một interface duy nhất để quản lý các hình dạng:

SOLID Principles nguyên tắc thiết kế trong OOP

Bây giờ trong class AreaCalculator thay vì gọi phương thức area() bạn sẽ gọi tới phương thức calculate() và bạn có thể kiểm tra xem đối tượng hình học đó có phải là một instance của ManageShapeInterface hoặc ShapeInterface hay không.

Điều này sẽ thỏa mãn được nguyên tắc Interface Segregation

Dependency Inversion Principle

Nguyên tắc này được phát biểu như sau:  Các module cấp cao không nên phụ thuộc vào module cấp thấp mà cả hai nên phụ thuộc vào abstract.

Đây là một ví dụ về việc kết nối CSDL MySQL:

SOLID Principles nguyên tắc thiết kế trong OOP

Đầu tiên, MySQLConnection là module cấp thấp và PasswordReminder là module cấp cao. Nhưng dựa trên phát biểu trên thì đoạn code này đang vi phạm nguyên tắc này vì class PasswordReminder đang phụ thuộc vào class MySQLConnection.

Sau này, nếu bạn có sự thay đổi về cơ sở dữ liệu, bạn cũng sẽ phải chỉnh sửa class PasswordReminder, điều này sẽ vi phạm nguyên tắc Open-Closed.

Các class  PasswordReminder không nên quan tâm tới cơ sở dữ liệu được sử dụng trong ứng dụng của bạn. Để giải quyết vấn đề này các bạn tạo thêm 1 interface.

SOLID Principles nguyên tắc thiết kế trong OOP

Trong interface này có một phương thức connect() và class MySQLConnection sẽ implement interface này. Ngoài ra thay vì sử dụng trực tiếp MySQLConnection bên trong constructor của class PasswordReminder, thay vào đó bạn sẽ sử dụng DBConnectionInterface . Và khi có bất kỳ sự thay đổi nào về cơ sở dữ liệu thì điều bạn cần làm là thay đổi implement cho DBConnectionInterface.

SOLID Principles nguyên tắc thiết kế trong OOP

Phần kết luận

Trong bài viết này mình đã trình bày 5 nguyên tắc trong SOLID. Hy vọng các bạn có thể hiểu và áp dụng trong các dự án thực tế.

Bạn thấy bài viết này như thế nào?
1 reaction

Add new comment

Image CAPTCHA
Enter the characters shown in the image.

Related Articles

Mỗi kết nối cơ sở dữ liệu được định nghĩa trong một mảng, với tên kết nối là khóa của mảng

Eager Loading là một kỹ thuật tối ưu hóa truy vấn cơ sở dữ liệu trong Laravel, giúp tăng tốc độ truy vấn và giảm số lượng truy vấn cần thiết để lấy dữ liệu liên quan đến một bản ghi.

Để sử dụng Eager Loading với điều kiện trong Laravel, bạn có thể sử dụng phương thức whereHas hoặc orWhereHas trong Eloquent Builder.

E hiểu đơn giản vầy nha. auth() hay Auth trong laravel là những function global hay class, nó cũng chỉ là 1 thôi

Xin chào các bạn, tuần này mình sẽ viết một bài về cách xử lý Real Time(thời gian thực) với Laravel và Pusher