Hiểu và Áp Dụng SOLID: Đưa Kỹ Năng Lập Trình Lên Tầm Cao Mới
S - Single Responsibility Principle
O - Open/Closed Principle
L - Liskov’s Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
1. Single Responsibility Principle ( Single Responsibility Principle -SRP )
Nguyên lý đầu tiên, tương ứng với chữ S trong SOLID. Nội dung nguyên lý:
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)
- Định nghĩa: Mỗi lớp chỉ nên có một lý do duy nhất để thay đổi, tức là nó chỉ nên đảm nhiệm một trách nhiệm cụ thể.
- Ý nghĩa: Bằng cách tuân thủ nguyên tắc này, bạn sẽ giảm thiểu sự phụ thuộc và tăng cường tính dễ bảo trì của mã nguồn. Khi một lớp chỉ có một trách nhiệm duy nhất, bất kỳ thay đổi nào trong yêu cầu phần mềm sẽ chỉ ảnh hưởng đến lớp đó mà thôi.
Nếu một 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.
public class Student {
public string Name { get; set;}
public int Age { get; set;}
// Format class này dưới dạng text, html, json để in ra
public string GetStudentInfoText() {
return "Name: " + Name + ". Age: " + Age;
}
public string GetStudentInfoHTML() {
return "<span>" + Name + " " + Age + "</span>";
}
public string GetStudentInfoJson() {
return Json.Serialize(this);
}
}
Class Student có quá nhiều chức năng: chứa thông tin học sinh, format hiển thị thông tin. Nếu có thêm nhiều chức năng nữa , class sẽ ngày càng to và việc nâng cấp sửa chữa sẽ rất khó khăn. Để giải quyết vấn đề này ta nên tách chúng thành các class nhỏ với các chứ năng riêng biệt, để khi sửa chữa sẽ không làm ảnh hưởng tới các class khác.
// Student bây giờ chỉ chứa thông tin
public class Student {
public string Name { get; set;}
public int Age { get; set;}
}
// Class này chỉ format thông tin hiển thị student
public class Formatter {
public string FormatStudentText(Student std) {
return "Name: " + std.Name + ". Age: " + std.Age;
}
public string FormatStudentHtml(Student std) {
return "<span>" + std.Name + " " + std.Age + "</span>";
}
public string FormatStudentJson(Student std) {
return Json.Serialize(std);
}
}
2. Open/Closed Principle ( Nguyên tắc Mở/Đóng - OCP )
Nguyên lý thứ hai, tương ứng với chữ O trong SOLID. Nội dung nguyên lý:
Có thể mở rộng 1 class, nhưng không được sửa đổi bên trong class đó
- Định nghĩa: Các thực thể phần mềm (lớp, module, hàm,...) nên được mở để mở rộng, nhưng đóng để sửa đổi.
- Ý nghĩa: Nguyên tắc này giúp bạn mở rộng chức năng của phần mềm mà không cần phải sửa đổi mã nguồn hiện có. Điều này giúp giảm thiểu rủi ro lỗi và đảm bảo sự ổn định của hệ thống.
Theo nguyên lý này, mỗi khi ta muốn thêm chức năng,.. cho chương trình, chúng ta nên viết class mới mở rộng class cũ ( bằng cách kế thừa hoặc sở hữu class cũ) không nên sửa đổi class cũ.
// class hình vuông
public class Square {
public Point topLeft;
public double side;
}
// class hình tam giác
public class Rectangle {
public Point topLeft;
public double height;
public double width;
}
// class Tính chu vi
public class Geometry {
public final double PI = 3.141592653589793;
public double area(Object shape) throws NoSuchShapeException
{
// Dùng if để kiểm tra hình và cho ra diện tích tương ứng
if (shape instanceof Square) {
Square s = (Square)shape;
return s.side * s.side;
}
else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle)shape;
return r.height * r.width;
}
else if (shape instanceof Circle) {
Circle c = (Circle)shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}
Dễ thấy nếu trong tương lại ta thêm nhiều class nữa, muốn tính diện tích của nó ta lại phải sửa class Geometry, viết thêm chừng đó hàm if nữa. Sau khi chỉnh sửa lại như sau:
interface Shape {
public double area();
}
public class Square implements Shape {
private Point topLeft;
private double side;
public double area() {
return side*side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
public double area() {
return height * width;
}
}
Ta dùng một interface và chuyển hàm tính diện tích vào trong các hình, như vậy khi thêm một lớp mới ta chỉ cần thực thi trong lớp đó mà không ảnh hưởng đến các lớp khác.
3. Liskov’s Substitution Principle ( Nguyên tắc Thay thế của Liskov - LSP )
Nguyên lý thứ ba, tương ứng với chữ L trong SOLID. Nội dung nguyên lý:
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 của chương trình
- Định nghĩa: Các đối tượng của một lớp con nên có thể thay thế cho các đối tượng của lớp cha mà không làm thay đổi tính đúng đắn của chương trình.
- Ý nghĩa: Nguyên tắc này đảm bảo rằng lớp con có thể được sử dụng một cách tương tự như lớp cha mà không gây ra lỗi hoặc hành vi bất ngờ. Điều này giúp duy trì tính nhất quán và khả năng dự đoán của hệ thống.
Một ví dụ đơn giản là class RecyclerView.Adapter trong android. Lập trình viên có thể dễ dàng extend class này và viết lớp adapter riêng của mình mà không cần phải thay đổi lớp RecyclerView.Adapter đã có sẵn.
public class Animal {
public void makeNoise() {
System.out.println("making some noise");
}
}
// class Cat và Dog extend từ class animal
public class Dog extends Animal {
@Override
public void makeNoise() {
System.out.println("gâu gâu");
}
}
public class Cat extends Animal {
@Override
public void makeNoise() {
System.out.println("meow meow");
}
}
Nguyên lý chỉ ra ở đây chúng ta có thể thay thể những chỗ đã sử dụng class Animal bằng class Dog hoặc Cat mà không làm chết chương trình. Chúng ta không nên thực thi đoạn code ở lớp con mà khi thay thế lớp cha sẽ làm chết chương trình. Ví dụ
class MuteCat extends Animal {
@Override
public void makeNoise() {
throw new RuntimeException("I can't make noise");
}
}
4. Interface Segregation Principle ( Nguyên tắc Tách biệt Giao diện - ISP )
Nguyên lý thứ tư, tương ứng với chữ I trong SOLID. Nội dung nguyên lý:
Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với các mục đích khác nhau
- Định nghĩa: Khách hàng không nên bị buộc phải phụ thuộc vào các giao diện mà họ không sử dụng.
- Ý nghĩa: Thay vì tạo ra một giao diện lớn với nhiều phương thức, bạn nên tạo ra nhiều giao diện nhỏ hơn, mỗi giao diện chỉ chứa các phương thức liên quan đến một nhóm chức năng cụ thể. Điều này giúp giảm sự phụ thuộc không cần thiết và làm cho mã dễ hiểu
Nguyên lý này khá dễ hiểu. Hãy tưởng tượng chúng ta có 1 interface lớn, khoảng 100 methods. Việc implements sẽ khá cực khổ, ngoài ra còn có thể dư thừa vì 1 class không cần dùng hết 100 method. Khi tách interface ra thành nhiều interface nhỏ, gồm các method liên quan tới nhau, việc implement và quản lý sẽ dễ hơn.
/**
* interface được tạo ra bắt sự kiện khi người dùng click
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
/**
* interface được tạo ra khi người dùng click và giữ
*/
public interface OnLongClickListener {
/**
* Called when a view has been clicked and held.
*
* @param v The view that was clicked and held.
*
* @return true if the callback consumed the long click, false otherwise.
*/
boolean onLongClick(View v);
}
Nếu chúng ta định nghĩa một interface gộp cả 2
public interface MyOnClickListener {
void onClick(View v);
boolean onLongClick(View v);
}
Khi implement interface này ta sẽ phải thêm những method không dùng đến vào, chưa kể khi thêm nhiều chức năng khác interface này sẽ phình to ra, nội dung của nguyên lý này chính là khuyên ta nên tách các interface cho các mục đích cụ thể, tránh việc gộp lại chúng thành 1 interface.
5. Dependency Inversion Principle ( Nguyên tắc Đảo ngược Sự phụ thuộc - DIP )
Nguyên lý cuối cùng, tương ứng với chữ D trong SOLID. Nội dung nguyên lý:
1. Các module cấp cao không nên phụ thuộc vào các modules cấp thấp.
Cả 2 nên phụ thuộc vào abstraction.
2. Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại.
( Các class giao tiếp với nhau thông qua interface,
không phải thông qua implementation.)
- Định nghĩa: 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 các trừu tượng. Ngoài ra, các trừu tượng không nên phụ thuộc vào chi tiết, mà ngược lại, chi tiết nên phụ thuộc vào trừu tượng.
- Ý nghĩa: Nguyên tắc này giúp giảm sự phụ thuộc giữa các thành phần trong hệ thống, làm cho chúng dễ dàng thay đổi và tái sử dụng hơn. Bằng cách sử dụng các trừu tượng (interfaces hoặc abstract classes), bạn có thể thay đổi các thành phần cụ thể mà không ảnh hưởng đến các thành phần khác.
Ví dụ
// khi chưa áp dụng nguyên lý
// Cart là module cấp cao
public class Cart {
public void Checkout(int orderId, int userId) {
// Database, Logger, EmailSender là module cấp thấp
Database db = new Database();
db.Save(orderId);
Logger log = new Logger();
log.LogInfo("Order has been checkout");
EmailSender es = new EmailSender();
es.SendEmail(userId);
}
}
// Code sau khi áp dụng nguyên lý
public interface IDatabase {
void Save(int orderId);
}
public interface ILogger {
void LogInfo(string info);
}
public interface IEmailSender {
void SendEmail(int userId);
}
// Các Module implement các Interface
public class Logger implements ILogger {
public void LogInfo(string info) {}
}
public class Database implements IDatabase {
public void Save(int orderId) {}
}
public class EmailSender implements IEmailSender {
public void SendEmail(int userId) {}
}
// Hàm checkout mới sẽ như sau
public void Checkout(int orderId, int userId) {
// Nếu muốn thay đổi database, logger ta chỉ cần thay đổi code ở dưới các module này mà không ảnh hưởng đến hàm checkout
//IDatabase db = new XMLDatabase();
//IDatebase db = new SQLDatabase();
IDatabase db = new Database();
db.Save(orderId);
ILogger log = new Logger();
log.LogInfo("Order has been checkout");
IEmailSender es = new EmailSender();
es.SendEmail(userId);
}
6. Kết luận
Các nguyên tắc SOLID giúp bạn thiết kế phần mềm theo cách dễ bảo trì, mở rộng và linh hoạt. Tuân thủ các nguyên tắc này sẽ giúp bạn xây dựng hệ thống phần mềm chất lượng cao, giảm thiểu lỗi và dễ dàng thích nghi với các yêu cầu thay đổi.
Hatonet kết nối doanh nghiệp ITO toàn cầu.
Giúp các doanh nghiệp IT Việt Nam tiết kiệm chi phí,tìm kiếm
đối tác,mở rộng mạng lưới.
- Mở rộng kênh tìm kiếm khách hàng gia tăng doanh thu.
- Tiết kiệm chi phí quan hệ tìm đối tác.
- Ứng tuyển trực tuyến bất cứ lúc nào khi có yêu cầu.
- Trực tiếp liên kết với công ty quốc tế
Liên hệ :
Email: hello@hatonet.vn
Zalo: https://zalo.me/hatonet