SOLID

S - Single Responsibility Principles (SRP)

Definition

A class should have only one reason to change, meaning it should have only one job or responsibility.

Mỗi class chỉ nên đảm nhận 1 công việc (a single part of the functionality provided by the software). Nếu nó đảm nhận nhiều hơn 1 responsibility, các responsibilities sẽ bị “coupled”. Chỉ cần 1 thay đổi tới responsibility sẽ dẫn tới thay đổi ở nhiều class khác nhau.

Mục tiêu sau cùng của nguyên tắc này là giảm bớt đi sự phức tạp trong mỗi class.

Responsibility - “Trách nhiệm” lại là một từ khó hiểu. Làm thế nào để định nghĩa Trách nhiệm 1 cách hợp lý? Ví dụ: Khi đăng ký User xong thì sẽ phải Gửi mail.

class RegistrationService
	def call
		save_user(user)
		send_mail(user)
	end
end
 
// 'Refactor'
class RegistrationService
	def call
		UserService.new(user).save
		EmailService.send_welcome_mail(user)
	end
end

Dễ thấy, dev ở case 1 cho rằng: Trách nhiệm của class RegistrationService là hoàn thành flow đăng ký user, trong đó có nhiệm vụ lưu thông tin vào DB, và gửi mail.

Dev ở case 2 cho rằng: Trách nhiệm của class RegistrationService là nơi tổng hợp các step khi đăng ký user, chứ không phải là nơi implement từng actions cụ thể. Việc save user và gửi mail phải để ở class riêng của nó.

Vậy ai đúng, ai sai? và làm thế nào để định nghĩa ‘Responsibility’ của 1 class?

Define Responsibility

  1. Identify Different Concerns
    • Data persistence: Thao tác với DB. eg: UserRepository, OrderRepository, …
    • User interface: Quản lý presentation layer và user interactions: LoginForm, DashboardView, ProductPage, …
    • Business logic: Core function và rules của application. Eg: OrderProcessor, PaymentService, DiscountCalculator, …
    • Communication with external services: Tương tác với các hệ thống bên ngoài. Eg: EmailService, PaymentGateway, WeatherApiClient, …
    • Validation: Validate data. Eg: UserValidator, OrderValidator, ProductValidator, …
    • Logging
    • Error handling
    • Reporting
  2. Focus on a Single Concern
    • Đảm bảo mỗi class đều tập trung vào một trong những concerns ở trên. Nếu 1 class đang handle nhiều concerns, thì nó có thể refactor được.
  3. Describe in One Sentence
    • Thử mô tả responsibility của 1 class trong 1 câu. Nếu bạn phải sử dụng những từ ngữ như: “A và B” thì có thể class này đang có nhiều hơn 1 responsibility.

Tips được khuyên dùng: Thiết kế theo hướng “Use case”. Mỗi use case sẽ tạo 1 class. Class này sẽ chỉ có duy nhất 1 hàm: call/ perform/execute/handle - và hàm này chỉ làm 1 việc duy nhất.

Nếu có 1 nghiệp vụ mói phát sinh, mọi người sẽ tạo 1 class mới, với hàm call mới và làm tương tự như trên.

Trong Rails, design pattern Services cũng thường áp dụng cách này, chỉ chứa 1 public method: perform.

Example

Questions

  • TODO: https://www.youtube.com/watch?v=Gt0M_OHKhQE

  • Nếu mỗi class chỉ đảm nhận 1 nhiệm vụ, vậy sẽ sinh ra rất nhiều class, điều này có bất lợi gì không?

  • Example thực tế trong code Rails/ real-life project?

O - Open/ Closed Principle (OCP)

Definition

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Các lớp nên mở cho việc mở rộng, nhưng đóng cho việc sửa đổi.

Mục tiêu chính của nguyên tắc này là giữ cho mã hiện tại không bị hỏng khi triển khai tính năng mới. Hạn chế tối đa việc sửa code cũ, và ưu tiên mở rộng, “thay thế” nếu có thể.

Example

Questions

  • Có những loại software entities nào nữa =)) ?
  • Closed for modification là như thế nào? Nếu thực sự cần sửa logic thì sao?
  • “Open for extension” là như thế nào? Extend là được phép thực hiện những hành động ntn?

L - Liskov Substitution Principle (LSP)

Definition

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

Example

Questions

I - Interface Segregation Principle (ISP)

Definition

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

Example

Questions

D - Dependency Inversion Principle (DIP)

Definition

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.

Example

Questions

  • Thế nào là high-level modules (complex logic?), low-level modules (utility features?)
  • Abstractions gồm những cái gì? Interfaces là gì? Interfaces trong Rails/Ruby là như thế nào?

Notes

Things that change for the same reasons should be grouped together, things that change for different reasons should be separated. — Robert C Martin

Resources