1. Công nghệ thông tin

SOLID là gì? Tại sao các lập trình viên lại thần thánh hóa SOLID?

SOLID

Nói một cách ngắn gọn, việc viết những đoạn code không chỉ chạy tốt ở thời điểm hiện tại mà còn có thể dễ dàng đáp ứng những yêu cầu hoặc thay đổi trong tương lai luôn là mục tiêu của các lập trình viên. Và SOLID được ra đời để các lập trình viên phần nào đạt được mục tiêu ấy.

Tác giả của SOLID là Robert C. Martin
- người đồng thời cũng là đồng sáng lập của
Agile Manifesto
.

SOLID là cách viết rút gọn của 5 nguyên tắc:

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

NOTE: Trong bài viết này, các định nghĩa của những nguyên tắc sẽ là tiếng Anh vì đấy là định nghĩa từ tài liệu gốc và mình không muốn thay đổi để giữ nguyên nghĩa gốc của chúng. Mình sẽ giải thích rõ các định nghĩa ấy bằng tiếng Việt nên đừng lo :hearteyes:

NOTE 2: Trước khi đọc bài viết này, mình hi vọng các bạn đã nắm rõ các khái niệm về

Abstraction và Concretion
;
Thế nào là Abstract Class và Concrete Class
;
Interface là gì
. (nếu chưa từng nghe về các khái niệm trên thì các bạn hãy đọc các bài viết ở các links mình chèn nhé, hi vọng nó dễ hiểu :thinking:)

------------------------------------

Nguyên tắc thứ nhất: Single Responsibility Principle

A class should have only a single responsibility.

Nguyên tắc này có thể hiểu đơn giản rằng một class chỉ nên đảm nhận một và chỉ một trách nhiệm. Bạn chỉ nên sửa đổi class ấy chỉ vì một lí do duy nhất.

Việc nguyên tắc này giúp đảm bảo code dễ bảo trì và quản lí hơn là điều khá rõ ràng.

Giả sử bạn có một class gọi là DBHelpers có các chức năng sau

  • Mở/đóng kết nối database của Noron!
  • Truy xuất danh sách những bài viết trên Noron!
  • In danh sách những người dùng Noron! ra Excel


class DBHelper { public MySQLConnection openConnection() {}; public bool closeConnection() {}; public bool writeExternal() {}; public UserList userList() {}; }



Và sau một thời gian, bạn được yêu cầu thực hiện những thay đổi sau:

  • Đổi sang một database mới
  • Chỉ truy xuất những bài viết nổi bật trên Noron!
  • In danh sách những người dùng Noron! ra Google Sheet

Với chỉ một class DBHelpers "gánh vác" rất nhiều nhiệm vụ như thế, việc thay đổi nó theo những yêu cầu như trên là rất khó khăn và dễ dẫn đến những lỗi khó đoán trước. Thay vào đó, bạn có thể tách DBHelpers ra nhiều class khác nhau, mỗi class sẽ có nhiệm vụ riêng của nó:

class DBConnection { public MySQLConnection openConnection() {}; public bool closeConnection() {}; } class DBExternal { public bool writeExternal() {}; } class DBUsers { public UserList userList() {}; }



------------------------------------

Nguyên tắc thứ hai: Open/Closed Principle

Software entities … should be open for extension, but closed for modification.

Nguyên tắc này có thể hiểu là nếu bạn có thêm những yêu cầu mới cho một class mà những yêu cầu này lại nằm ngoài trách nhiệm (responsibility) được xác định ở trên thì bạn nên mở rộng/kế thừa class có sẵn thay vì sửa đổi chúng.

Ví dụ: lập trình viên A đã viết ra class Animal, lập trình viên B được yêu cầu viết một class biểu diễn Con Mèo thì lập trình viên B không nên thay đổi class Animal để thêm vào các thuộc tính của một Con Mèo mà nên viết một class Cat kế thừa từ class Animal. Lí do là bởi vì không phải Animal nào cũng có chức năng "Cào rách ghế nệm" cả :D


------------------------------------

Nguyên tắc thứ ba: Liskov Substitution Principle

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Nguyên tắc này có thể hiểu là việc đối tượng class con thay thế đối tượng của class cha thì sẽ không làm thay đổi tính đúng đắn của chương trình, hay có thể nói cách khác là chương trình vẫn sẽ hoạt động như bạn dự đoán.

Để dễ hiểu hơn, mình hãy cùng xem một ví dụ rất điển hình sau đây:

class Animal { public void makeNoise() { System.out.println("Animal is making noise...."); } } public class Dog extends Animal { @Override public void makeNoise() { System.out.println("Woof woof"); } } public class Cat extends Animal { @Override public void makeNoise() { System.out.println("Meow Meow"); } }



Bạn có thể thấy rằng việc các class con như Dog và Cat thay thế (override) hàm makeNoise của class cha là Animal sẽ không ảnh hưởng tới chương trình hiện tại, các con vật vẫn sẽ "kêu" đúng như bạn dự đoán. Tuy nhiên, nếu bạn có một class con là UnknowAnimal như sau thì chương trình sẽ không còn đảm bảo tính đúng đắn nữa:

public class UnknowAnimal extends Animal { @Override public void makeNoise() { System.exit(0); } }



------------------------------------

Nguyên tắc thứ tư: Interface Segregation Principle

Many client-specific interfaces are better than one general-purpose interface.


Nguyên tắc này có thể hiểu là thay vì chỉ định nghĩa chung một interface cho nhiều mục đích khác nhau, ta nên tách interface ấy ra nhiều interface nhỏ hơn.


Hãy cùng xem ví dụ sau đây :behold:

interface Animal { public void fly(); public void run(); }



Giả sử bạn có 2 class con là Bird và Dog implement class Animal thì có vẻ hơi dư thừa khi mà Bird phải implement hàm run() và Dog phải implement hàm fly(). Có con chó nào mà bay chứ? :megalul:

Thay vào đó, bạn sẽ cảm thấy việc tách interface ra như thế này sẽ dễ hiểu hơn rất nhiều :hopeful:

interface RunnableAnimal extends Animal { public void run(); } interface FlyableAnimal extends Animal { public void fly(); }



------------------------------------

Nguyên tắc thứ năm: Dependency Inversion Principle

Depend upon abstractions, not concretions.

Đây là nguyên tắc khó hiểu nhất trong 5 nguyên tắc vì nó đề cập tới một số khái niệm như interface, concrete class, abstraction. (*nhắc nhở một cách thân thiện*: nếu bạn chưa hiểu rõ các khái niệm này hãy quay lên đầu bài ở phần NOTE 2 và đọc các links mình chèn vào nhé)

Quay trả lại với nguyên tắc thứ 5, nguyên tắc này có thể hiểu là các class chỉ nên quan tâm tới cái trừu tượng (abstraction) hơn là cái cụ thể (concretion). Nói một cách dễ hiểu hơn, các class phải quản lí concrete class thông qua interface chứ không nên quản lí trực tiếp trên concrete class đó.

Hmmm, nghe cũng có vẻ chưa dễ hiểu :sad:. Đừng lo, mình lần đầu tiên đọc đến định nghĩa này cũng không hiểu nổi nó đang nói gì nữa =)) Nhưng mình hi vọng với ví dụ sau đây, bạn có thể phân biệt được như thế nào là quản lí theo abstraction và như thế nào là quản lí concretion.

Hãy nhìn đoạn code sau đây:

class ComputerScienceStudent { private void writeScientificPaper() {}; } class MathematicsStudent { private void study() {}; } class HCMUS { private ComputerScienceStudent csStudent = new ComputerScienceStudent(); private MathematicsStudent mathStudent = new MathematicsStudent(); public void operate() { csStudent.writeScientificPaper(); mathStudent.study(); } }



Giả sử ở một thời điểm nào đó, Trường Đại học Khoa Tự Nhiên HCMUS không còn muốn sinh viên Khoa Học Máy Tính viết bài nghiên cứu khoa học nữa và đồng thời không muốn sinh viên khoa Toán học toán nữa. Họ muốn đổi vai trò của các sinh viên cho nhau!

Hãy thử tưởng tượng, để thực hiện sự thay đổi đó, bạn phải sửa code ở cả class HCMUS và ở cả 2 concrete class ComputerScienceStudentMathematicsStudent! Quá rườm rà phải không? :holyshit:

Bạn gặp phải rắc rối ấy là bởi vì bạn đang quản lí concrete class (ở đây là ComputerScienceStudentMathematicsStudent) trong một class khác là HCMUS.Hãy thay đổi một chút để có thể đạt được nguyên tắc thứ 5 trong SOLID:

interface Student { public void work(); } class ComputerScienceStudent implements Student { @Overridepublic void work() { writeScientificPaper(); } private void writeScientificPaper() {}; } class MathematicsStudent implements Student { @Overridepublic void work() { study(); } private void study() {}; } class HCMUS { private List<Student> students = new List<Student>(); public void operate() { students.forEach(student -> student.work()) } }



Với cách code mới này, bạn có thể thấy rằng nếu bạn muốn thay đổi hành động của một học sinh thì bạn chỉ cần đi thẳng vào concrete class tương ứng để thay đổi và class HCMUS vẫn được giữ nguyên! Đấy chính là lợi ích của việc quản lí concrete class thông qua interface -hay cũng có thể hiểu là chỉ quan tâm tới abstraction thay vì concretion.


---------------

Kết luận

SOLID là một tập hợp những nguyên tắc rất hay không chỉ giúp ích cho bản thân bạn mà còn cả cho cả một team kĩ thuật.

Mình chắc chắn là lần đầu đọc về SOLID, bạn sẽ cảm thấy rất khó hiểu. Do đó, mình khuyên bạn hãy dành thời gian ra đọc đi đọc lại về SOLID nhiều lần. :hearteyes:

Nếu đọc bài của mình mà không hiểu thì có thể tham khảo các nguồn sau nhé =)))


Tài liệu tham khảo

  • https://en.wikipedia.org/wiki/SOLID
  • h
    ttps://dzone.com/articles/solid-principles-dependency-inversion-principle
  • https://medium.com/mindorks/solid-principles-explained-with-examples-79d1ce114ace
Từ khóa: solid, java, vtcc_intern_6, technical practices, engineering, công nghệ thông tin