Trang chủ Program Nguyên lý S.O.L.I.D

Nguyên lý S.O.L.I.D

bởi Tran Tung

Giới thiệu

Trong lập trình có khá nhiều nguyên lý như: SOLID, DRY, KISS, YAGNI,…. mỗi nguyên lý có 1 tác dụng riêng, tuy nhiên ý nghĩa cuối cùng mà các nguyên lý đêu hướng đến là giúp chúng ta thiết kế chương trình, viết code 1 cách chuyên nghiệp, dễ đọc, dễ bảo trì, mở rộng. Trong bài viết này sẽ đi vào 1 nguyên lý kinh cmn điển đã được rút ra từ xương máu của không biết bao nhiêu lập trình viên đi trước.

Chi tiết

SOLID là tập hợp của 5 nguyên tắc ứng với các ký tự:

  1. S: Single responsibility principle.
  2. O: Open/Closed principle.
  3. L: Liskov Substitution Principle.
  4. I: Interface segregation principle.
  5. D: Dependency Inversion Principle.

S: Single responsibility principle.

Một class chỉ nên giữ 1 trách nhiệm duy nhất (Chỉ có thể sửa đổi class với 1 lý do duy nhất)

Nếu 1 class có quá nhiều chức năng, quá cồng kềnh, việc thay đổi code sẽ rất khó khăn, mất nhiều thời gian, còn dễ gây ảnh hưởng tới các module đang hoạt động khác.

VD:

1
2
3
4
5
6
7
public class MasterRobot()
{
   public void cooking();
   public void garden();
   public void paint();
   public void driver();
}

Theo class bên trên thì con robot thực hiện 4 chức năng không liên quan gì đến nhau, và khi có 1 chức năng nào đó bị lỗi chúng ta phải sửa lại cả con robot. Nếu tuân theo nguyên tắc S chúng ta sẽ chia nhỏ thành 4 con robot mỗi con thực hiện 1 chức năng riêng biệt, theo đó việc sửa chữa cũng đơn giản hơn.

Nguyên tắc này nhằm mục đích phân tách nhỏ các hành vi để nếu có lỗi phát sinh do thay đổi của bạn, nó sẽ không gây ảnh hưởng đến các hành vi không liên quan khác.

O: Open/Closed principle.

Có thể thoải mái mở rộng 1 class, nhưng không được sửa đổi bên trong class đó (open for extension but closed for modification)

Việc thay đổi hành vi hiện tại của 1 Class sẽ ảnh hưởng đến hệ thống đang sử dụng Class đó. Nếu bạn muốn Class thực hiện nhiều chức năng hơn, cách tiếp cận lý tưởng là viết Class mới mở rộng từ Class cũ (bằng cách kế thừa hoặc sở hữu Class cũ).

Nguyên tắc này nhằm mục đích mở rộng hành vi của Class mà không làm thay đổi hành vi hiện có của Class đó. Điều này là để tránh gây ra lỗi ở bất cứ nơi nào Class đang được sử dụng.

L: Liskov Substitution Principle

Trong một chương trình, các object của class con có thể thay thế class cha mà không làm thay đổi tính đúng đắn hay không ra lỗi của chương trình.

Các Class con nên có thể xử lý các yêu cầu tương tự và cung cấp những kết quả tương tự như Class cha.

Liên tưởng đến hình ảnh trên, Robot tên là Sam (Class cha) cung cấp cafe (có thể là bất kỳ loại cafe nào). Có thể chấp nhận cho Robot tên Eden (Class con) để cung cấp Cappuchino vì nó là 1 loại cụ thể của cafe, nhưng không thể chấp nhận khi Robot tên Eden cung cấp 1 chai nước được.

Nguyên tắc này nhằm mục đích thực thi tính nhất quán để Class cha hoặc Class con của nó có thể được sử dụng theo cùng 1 cách mà không có bất kỳ lỗi nào.

I: Interface segregation principle

Một class không nên thực hiện một interface mà nó không dùng đến hoặc không nên phụ thuộc vào một phương thức method() mà nó không sử dụng. Để làm điều này, thay vì một interface lớn bao trùm chúng ta tách thành nhiều interface khác nhau.

1 Class implement từ 1 interface sẽ phải thực hiện override lại tất cả các phươngthức method() của interface này, và có thể có những phương thức method() trong interface mà class này không dùng đến

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Workable
{
    public function canSpinAround();
    public function canRotateArm();
}
 
class RobotA implement Workable
{
    public function canSpinAround(){
        return 'Robots that can spin around';
    }
    
    public function canRotateArm() {
        return false;
    }
}
 
class RobotB implement Workable
{
    public function canSpinAround(){
        return false;
    }
    
    public function canRotateArm() {
        return 'Robots that can rotate arm';
    }
}

Sự dư thừa ở ví dụ trên đã hiện lên rõ ràng, và chúng ta sẽ tối ưu lại bằng cách tách interface tổng thành các interface nhỏ hơn:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface SpinAround()
{
    public function canSpinAround();
}
 
interface RotateArm()
{
    public function canRotateArm();
}
 
class RobotA implement SpinAround
{
    public function canSpinAround(){
        return 'Robots that can spin around';
    }
}
 
class RobotB implement RotateArm
{
    public function canRotateArm() {
        return 'Robots that can rotate arm';
    }
}

Nguyên tắc này nhằm mục đích là chia 1 tập hợp các hành động thành các tập nhỏ hơn để Class CHỈ thực hiện tập hợp các hành động mà nó yêu cầu.

D: Dependency Inversion Principle

Các module cấp cao không nên phụ thuộc vào các modules cấp thấp.

Trừu tượng (Interface/Abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại.

Đầu tiên, nên hiểu vài thuật ngữ sau:

  • module cấp cao (Class) là 1 module phụ thuộc vào module khác.
  • Trừu tượng (Abstract) là 1 cái gì đó không hoàn toàn cụ thể. Nó chỉ là 1 ý tưởng hoặc ý chính của 1 cái gì đó mà không có bản triển khai cụ thể. Vì vậy, sự trừu tượng trong lý thuyết có nghĩa là tạo ra 1 interface hoặc 1 abstract class không cụ thể.

Nguyên tắc này có thể hiểu:

  • Những thành phần trong 1 chương trình chỉ nên phụ thuộc vào những cái trừu tượng (abstraction).
  • Những thành phần trừu tượng không nên phụ thuộc vào các thành phần mang tính cụ thể mà nên ngược lại.
  • Những cái trừu tượng (abstraction) là những cái ít thay đổi và biến động, nó tập hợp những đặc tính chung nhất của những cái cụ thể.
  • Những cái cụ thể dù khác nhau thế nào đi nữa đều tuân theo các quy tắc chung mà cái trừu tượng đã định ra.
  • Việc phụ thuộc cái trừu tượng sẽ giúp chương trình linh động và thích ứng tốt với các sự thay đổi diễn ra liên tục.

Với hình ảnh ví dụ trên thì … interface chính là đuôi xoáyimplementation là cái dao cắt và cái cưa cắt. Ta có thể swap dễ dàng giữa 2 dụng cụ cắt khác nhau vì Robot chỉ quan tâm chỉ quan tâm tới interface (đuôi xoáy) để nó có thể cắt được pizza mà không quan tâm đến implementation là gì.

Trong code cũng như vậy, anh em có thể xem qua ví dụ này nhé:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Robot
{
    public function cut();
}
 
class RobotUseKnife implement Robot
{
    public function cut() {
        //code
    }
}
 
class RobotUseSaw implement Robot
{
    public function cut() {
        //code
    }
}
 
clas RobotCutPizza
{
    private $robot;
    
    public function __construct(Robot $robot)
    {
        $this->robot = $robot;
    }
}

Nguyên tắc này nhằm mục đích giảm sự phụ thuộc của Module cấp cao vào Module cấp thấp bằng việc sử dụng interface.

Xong, qua bài viết hy vọng anh em có thể hiểu được và vận dụng SOLID vào công việc code của mình.

Nguồn tham khảo:

https://viblo.asia/p/phuong-phap-thiet-ke-huong-doi-tuong-solid-qua-hinh-anh-WAyK82jelxX

https://toidicodedao.com/2015/03/24/solid-la-gi-ap-dung-cac-nguyen-ly-solid-de-tro-thanh-lap-trinh-vien-code-cung/

Nhấn để đánh giá bài viết!
[Số đánh giá: 0 Trung bình: 0]

Có thể bạn quan tâm

Để lại bình luận