- vừa được xem lúc

Nguyên tắc thiết kế SOLID là gì?

0 0 5

Người đăng: Trần Quân

Theo Viblo Asia

Giới thiệu về "Uncle Bob"

Robert C. Martin, thường được biết đến với biệt danh "Uncle Bob", là một trong những nhân vật có tầm ảnh hưởng lớn trong lĩnh vực kỹ thuật phần mềm. Với hơn 50 năm kinh nghiệm, ông đã trở thành biểu tượng cho việc thúc đẩy Clean code và là người tiên phong trong việc thúc đẩy các nguyên tắc giúp xây dựng phần mềm chất lượng, dễ bảo trì.

Ông nổi tiếng nhất với khái niệm "Clean Code", một triết lý về cách viết mã nguồn dễ đọc, dễ hiểu và dễ bảo trì. Theo Uncle Bob, mã nguồn không chỉ cần hoạt động tốt mà còn phải rõ ràng, có cấu trúc hợp lý để các lập trình viên khác có thể tiếp tục phát triển mà không gặp khó khăn. Uncle Bob là tác giả của nhiều cuốn sách bán chạy về kỹ thuật phần mềm, trong đó nổi bật nhất là:

  1. Clean Code: A Handbook of Agile Software Craftsmanship
  2. Clean Architecture: A Craftsman's Guide to Software Structure and Design
  3. The Clean Coder: A Code of Conduct for Professional Programmers

Các tác phẩm này đã trở thành cẩm nang cho nhiều thế hệ lập trình viên, cung cấp những nguyên tắc nền tảng giúp tạo nên phần mềm chất lượng cao, dễ mở rộng và bảo trì. Những cuốn sách này không chỉ tập trung vào khía cạnh kỹ thuật mà còn nhấn mạnh tầm quan trọng của đạo đức nghề nghiệp, kỷ luật và trách nhiệm trong lập trình. Với tầm nhìn xa và những đóng góp của mình, Robert C. Martin đã truyền cảm hứng cho hàng triệu lập trình viên trên toàn thế giới và đóng góp không nhỏ vào việc nâng cao tiêu chuẩn trong phát triển phần mềm hiện đại.

Nguyên tắc thiết kế SOLID là gì?

SOLID là một tập hợp năm nguyên tắc thiết kế phần mềm quan trọng trong lập trình hướng đối tượng (OOP), giúp xây dựng hệ thống dễ bảo trì, mở rộng và tránh lỗi. Dưới đây là định nghĩa ngắn gọn cho từng nguyên tắc:

  • S - Single Responsibility Principle (SRP): Mỗi lớp chỉ nên có một trách nhiệm duy nhất, và lý do để thay đổi lớp đó phải liên quan đến trách nhiệm này.
  • O - Open-Closed Principle (OCP): Các lớp nên được mở rộng nhưng không được phép sửa đổi. Điều này có nghĩa là có thể mở rộng chức năng của lớp mà không cần thay đổi mã hiện tại.
  • L - Liskov Substitution Principle (LSP): Các đối tượng của lớp con có thể thay thế cho đố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.
  • I - Interface Segregation Principle (ISP): Không nên ép buộc các lớp phụ thuộc vào những giao diện mà chúng không sử dụng; các giao diện nên nhỏ và cụ thể thay vì lớn và đa năng.
  • D - Dependency Inversion Principle (DIP): Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp, mà cả hai nên phụ thuộc vào các trừu tượng (interface hoặc abstract class).

Single Responsibility Principle (SRP)

"A class should have only one reason to change." -- Robert C. Martin

class User { name: string; email: string; constructor(name: string, email: string) { this.name = name; this.email = email; }
} class UserAuthentication { user: User; constructor(user: User) { this.user = user; } authenticate(password: string): boolean { // Implement authentication logic here. }
}

Trong ví dụ trên, chúng ta đã tách lớp User thành hai phần riêng biệt: User class sẽ chỉ quản lý thông tin người dùng, còn Authentication class sẽ chịu trách nhiệm xử lý việc xác thực thông tin người dùng. Bằng cách này, mỗi class chỉ có một lý do duy nhất để thay đổi: User class thay đổi khi có thay đổi về thông tin người dùng, và Authentication class thay đổi khi có thay đổi liên quan đến xác thực. Đây chính là việc áp dụng nguyên tắc SRP (Single Responsibility Principle).

Ưu điểm khi sử dụng SRP:

  1. Dễ dàng bảo trì
  2. Dễ đọc, dễ hiểu
  3. Dễ dàng trong việc kiểm thử
  4. Giảm sự phụ thuộc
  5. Tăng việc tái sử dụng code

Open-Closed Principle (OCP)

"The Open-Closed Principle states that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." -- Robert C. Martin

interface Customer { giveDiscount(): number;
} class RegularCustomer implements Customer { giveDiscount(): number { return 5; }
} class PremiumCustomer implements Customer { giveDiscount(): number { return 10; }
} class Discount { giveDiscount(customer: Customer): number { return customer.giveDiscount(); }
}

Trong ví dụ trên, chúng ta đã định nghĩa Customer interface với 1 method giveDiscount. 2 class RegularCustomerPremiumCustomer implement Customer interface. class Discount đã được đóng lại và nó không cần phải sửa đổi mỗi khi chúng ta có thêm loại khách hàng mới. Đây là việc áp dụng nguyên tác OCP

Ưu điểm khi sử dụng OCP:

  1. Giảm nguy cơ gặp lỗi
  2. Tăng khả năng tái sử dụng code

Liskov Substitution Principle (LSP)

"If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program."

abstract class Shape { abstract calculateArea(): number;
} class Rectangle extends Shape { constructor(public width: number, public height: number) { super(); } public calculateArea(): number { return this.width * this.height; }
} class Square extends Shape { constructor(public side: number) { super(); } public calculateArea(): number { return this.side * this.side; }
}
// ====== Client Code
function area(shape: Shape) { return shape.calculateArea();
} let rectangle = new Rectangle(4, 8);
let square = new Square(5); area(rectangle); // 24
area(square); // 25

Trong ví dụ này, SquareRectangle đều là các lớp con của Shape. Mỗi lớp triển khai phương thức calculateArea theo các đặc tính hình học riêng của chúng. Hàm area được thiết kế để hoạt động với bất kỳ đối tượng nào thuộc loại Shape. Nó sử dụng phương thức calculateArea và các setter cụ thể để thay đổi kích thước của shape.

Ưu điểm:

  1. Giảm sự trùng lặp
  2. Tăng cường tính linh hoạt
  3. Giảm chi phí bảo trì
  4. Tăng tính module

Interface Segregation Principle (ISP)

"No client should be forced to depend on interfaces they do not use." -- Robert C. Martin

interface PostCreator { createPost(post: Post): void;
} interface CommentCreator { commentOnPost(comment: Comment): void;
} interface PostSharer { sharePost(post: Post): void;
}
// ------------------ Implement ------------------
class Admin implements PostCreator, CommentCreator, PostSharer { createPost(post: Post): void { // Actual implementation } commentOnPost(comment: Comment): void { // Actual implementation } sharePost(post: Post): void { // Actual implementation }
} class RegularUser implements CommentCreator, PostSharer { commentOnPost(comment: Comment): void { // Actual implementation } sharePost(post: Post): void { // Actual implementation }
}

Mỗi client sẽ chỉ phụ thuộc vào interface mà chúng sử dụng. Ví dụ admin có thể truy cập được hết interface trong khi RegularUser chỉ có thể comment và share bài post.

Ưu điểm:

  1. Cải thiện khả năng bảo trì
  2. Giảm thiểu tác động khi thay đổi
  3. Tăng tính đóng gói
  4. Kiểm thử dễ dàng

Dependency Inversion Principle (DIP)

"High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." -- Robert C. Martin

Với nguyên tắc này chúng ta cần tách nhỏ chúng ra thành 2 phần để hiểu rõ hơn:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.
// không sử dụng DIP
class MySQLDatabase { save(data: string): void { // logic to save data to a MySQL database }
} class HighLevelModule { private database: MySQLDatabase; constructor() { this.database = new MySQLDatabase(); } execute(data: string): void { // high-level logic this.database.save(data); }
}

Với ví dụ này, HighLevelModule là hight-level module và nó phụ thuộc vào low-level module MySQLDatabase. Điều đó có nghĩa nếu chúng ta quyết định thay đổi db từ MySQL sang MongoDB, chúng ta sẽ phải sửa HighLevelModule. Điều này thực sự không tốt.

Ví dụ sau, chúng ta sẽ sửa lại một chút bằng cách cho HighLevelModule phụ thuộc vào IDatabse, nó chỉ sử dụng lại IDatabase với method save mà không cần quan tâm bên dưới đó là MySQL hay MongoDB. THiết kế này sẽ cho chúng ta thay đổi database mà không phải sửa đổi HighLevelModule.

// Sử dụng DIP
interface IDatabase { save(data: string): void;
} class MySQLDatabase implements IDatabase { save(data: string): void { // logic to save data to a MySQL database }
} class MongoDBDatabase implements IDatabase { save(data: string): void { // logic to save data to a MongoDB database }
} class HighLevelModule { private database: IDatabase; constructor(database: IDatabase) { this.database = database; } execute(data: string): void { // high-level logic this.database.save(data); }
}

Ưu điểm:

  1. Tách biệt được code
  2. Dễ dàng sửa đổi và mở rộng
  3. Dễ dàng kiểm thử
  4. Dễ dàng tái sử dụng code, đọc hiểu code

Bình luận

Bài viết tương tự

- vừa được xem lúc

Chia nhỏ Interface sử dụng Kotlin extension và inline functions

Interface segregation: Nguyên lý thứ 4 trong SOLID. Robert C Martin nổi tiếng với biệt danh “Uncle Bob” đã định nghĩa các nguyên lý SOLID - là những nguyên lý cần tuân theo khi xây dựng phần mềm để dễ

0 0 32

- vừa được xem lúc

9 điều cần học khi bạn trở thành một Kỹ sư Phần mềm

Trở thành kỹ sư phần mềm không chỉ đơn thuần là học ngôn ngữ lập trình hay viết mã. Đó còn là một quá trình liên tục phát triển kỹ năng, hiểu biết và thái độ.

0 0 20

- vừa được xem lúc

SOLID - nguyên lý bạn nên biết để trở thành dev có tâm, có tầm

Nguyên tắc SOLID trong lập trình là một tập hợp các nguyên tắc thiết kế phần mềm. Các nguyên tắc này giúp tạo ra mã nguồn linh hoạt, dễ bảo trì và mở rộng về sau.

0 0 13

- vừa được xem lúc

SOLID: Nguyên tắc, Ứng dụng Thực tiễn và Tầm Quan Trọng

Giới thiệu về SOLID. SOLID là tập hợp năm nguyên tắc thiết kế phần mềm được giới thiệu bởi Robert C.

0 0 14

- vừa được xem lúc

Nguyên tắc SOLID trong lập trình hướng đối tượng (OOP) - thực hành cùng ngôn ngữ Python

SOLID là gì. SOLID giúp thiết kế chương trình hướng đối tượng linh động, dễ hiểu và dễ duy trì. . .

0 0 23

- vừa được xem lúc

Principle trong Programing, Các nguyên tắc lập trình cơ bản

Mã của bạn phải rõ ràng và dễ bảo trì.Thật dễ dàng để viết mã nhưng Thật khó để viết mã tốt.

0 0 30