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

Single Responsibility Principle (SRP) trong C#

0 0 2

Người đăng: OssiLV

Theo Viblo Asia

Nguồn: https://dotnettutorials.net/lesson/single-responsibility-principle/


SRP là một trong năm nguyên lý SOLID trong thiết kế và phát triển phần mềm hướng đối tượng. Mục tiêu của nguyên lý này là giúp phần mềm trở nên dễ hiểu hơn, linh hoạt hơn và dễ bảo trì hơn.

🔸 SRP phát biểu rằng:

Một class chỉ nên có một lý do duy nhất để thay đổi, nghĩa là nó chỉ nên đảm nhận một trách nhiệm hay một vai trò duy nhất.

Vậy điều này có nghĩa là gì?

  • Ta nên thiết kế phần mềm sao cho mọi thứ trong một class đều liên quan đến cùng một nhiệm vụ chính.
  • Không có nghĩa là class chỉ được phép có một method, bạn có thể có nhiều method miễn là chúng phục vụ cùng một mục đích chính.
  • Khi áp dụng SRP, các class sẽ trở nên:
    • Nhỏ gọn hơn
    • Sạch hơn
    • Và đặc biệt là dễ bảo trì hơn

Trước khi đi sâu vào SRP, bạn cần hiểu rõ “Trách nhiệm (Responsibility)” là gì:

  • Trách nhiệm ở đây chính là vai trò hoặc mục đích chính mà class đó phục vụ trong hệ thống.
  • Mỗi class chỉ nên chịu trách nhiệm cho một phần cụ thể của yêu cầu nghiệp vụ, không nên ôm đồm nhiều thứ không liên quan.

Làm thế nào để áp dụng Nguyên lý SRP trong C#?

  1. Xác định rõ các trách nhiệm (Identify Responsibilities):

    Bước đầu tiên là xác định rõ những trách nhiệm mà class của bạn đang hoặc sẽ đảm nhiệm.

    Các trách nhiệm này thường thuộc các nhóm như:

    • Truy cập dữ liệu (data access)
    • Kiểm tra hợp lệ (validation)
    • Ghi log (logging)
    • Cache dữ liệu (caching)
    • Chuyển đổi dữ liệu (serialization)
    • Đăng ký người dùng (user registration)
    • Xác thực (authentication)
    • ...

    👉 Nếu bạn thấy một class xử lý nhiều nhiệm vụ như trên, thì đó là dấu hiệu vi phạm SRP.

  2. Tạo các class riêng biệt (Create Separate Classes):

    Sau khi xác định được các trách nhiệm, ta cần tách mỗi trách nhiệm thành một class riêng biệt.

    Không nên gộp nhiều chức năng không liên quan vào cùng một class.

    Ví dụ: Nếu một class vừa xử lý đăng ký người dùng, vừa gửi email, vừa ghi log → nên chia thành:

    • UserRegistrationService
    • EmailService
    • LoggingService
  3. Tách biệt các mối quan tâm (Separate Concerns):

    Cuối cùng, đảm bảo rằng mỗi class chỉ thực hiện một nhiệm vụ cụ thể duy nhất, tập trung vào "mối quan tâm" chính của nó.

    Điều này giúp bạn:

    • Dễ mở rộng
    • Dễ bảo trì
    • Dễ test

Ví dụ minh họa Nguyên lý SRP bằng C#:

Giả sử bạn cần thiết kế một lớp Invoice (Hóa đơn)

  • Lớp Invoice sẽ thực hiện việc tính toán các khoản tiền (tổng, thuế, giảm giá...) dựa trên dữ liệu của hóa đơn.
  • Tuy nhiên, lớp này không nên kiêm luôn các công việc như:
    • Truy xuất dữ liệu từ cơ sở dữ liệu
    • Hiển thị dữ liệu
    • Gửi email
    • Ghi log
    • Xuất hóa đơn ra file hoặc in ấn

Nếu ta gộp tất cả các logic trên vào cùng một lớp Invoice, thì lớp đó sẽ có nhiều trách nhiệm:

  • Truy cập cơ sở dữ liệu (Data Access)
  • Xử lý nghiệp vụ (Business Logic)
  • Gửi thông báo/email
  • Ghi log...

➡ Điều này vi phạm nguyên lý SRP.

Khi một class có quá nhiều trách nhiệm sẽ dẫn đến

  1. Khó hiểu (Difficult to Understand):

    Rất khó để đọc và nắm bắt mục đích của từng phần code trong lớp.

  2. Khó test (Difficult to Test):

    Nếu muốn test logic tính toán tiền, bạn phải khởi tạo cả phần gửi email, kết nối database…

  3. Dễ bị trùng lặp logic với phần khác (Chance of Duplicating Logic):

    Vì nhiều phần trong app cũng cần xử lý tương tự, bạn có thể lặp lại logic ở nơi khác thay vì tái sử dụng code một cách hiệu quả.

Giải pháp theo SRP:

Tách các trách nhiệm riêng biệt thành các lớp:

Responsibility (Trách nhiệm) Class name (Tên class)
Xử lý tính toán hóa đơn InvoiceCalculator
Ghi log Logger
Gửi email EmailSender
Truy xuất dữ liệu InvoiceRepository
In hóa đơn InvoicePrinter

Ví dụ KHÔNG tuân theo SRP trong C#

Sơ đồ lớp Invoice không tuân thủ SRP

Theo như hình trên, lớp Invoice có 4 chức năng

  • Add Invoice
  • Delete Invoice
  • Sending Email
  • Error Logging

Thì trong đó 2 chức năng Add InvoiceDelete Invoice là 2 chức năng chính của lớp Invoice.

Hai chức năng còn lại là Sending EmailError Logging là 2 chức năng không nên có trong lớp Invoice điều này làm lớp.

using System;
using System.Net.Mail;
namespace SOLID_PRINCIPLES.SRP
{ public class Invoice { public long InvoiceAmount { get; set; } public DateTime InvoiceDate { get; set; } public void AddInvoice() { try { // Here we need to write the Code for adding invoice // Once the Invoice has been added, then send the mail MailMessage mailMessage = new MailMessage("EMailFrom", "EMailTo", "EMailSubject", "EMailBody"); this.SendInvoiceEmail(mailMessage); } catch (Exception ex) { //Error Logging System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString()); } } public void DeleteInvoice() { try { //Here we need to write the Code for Deleting the already generated invoice } catch (Exception ex) { //Error Logging System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString()); } } public void SendInvoiceEmail(MailMessage mailMessage) { try { // Here we need to write the Code for Email setting and sending the invoice mail } catch (Exception ex) { //Error Logging System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString()); } } } }

Với thiết kế này, nếu chúng ta muốn thay đổi chức năng ghi log hoặc chức năng gửi email, thì chúng ta buộc phải chỉnh sửa lớp Invoice. Điều này vi phạm Single Responsibility Principle, bởi vì chúng ta đang thay đổi lớp Invoice để phục vụ cho các chức năng không thuộc trách nhiệm chính của nó.

Khi thực hiện những thay đổi như vậy, chúng ta cần phải kiểm thử lại toàn bộ các chức năng, bao gồm: chức năng ghi log, gửi email và xử lý hóa đơn. Điều này làm cho quá trình phát triển và bảo trì trở nên khó khăn, mất thời gian và dễ phát sinh lỗi.


Ví dụ CÓ tuân theo SRP trong C#

Sơ đồ 3 lớp Invoice, Logger, Email đều tuân thủ SRP

Theo như hình trên

  • Lớp Invoice sẽ thực thi các chức năng liên quan đến nó.
  • Lớp Logger sẽ chỉ sử dụng với mục đích logging.
  • Lớp Email sẽ xử lí các hoạt động liên quan tới mail

→ Vậy giờ mỗi lớp có các “trách nhiệm” đúng với chính nó.

Với thiết kế đã cải tiến như trên, nếu bạn muốn thay đổi chức năng gửi email, bạn chỉ cần sửa đổi lớp Email, không cần đụng đến các lớp Invoice hay Logging. Tương tự, nếu bạn muốn chỉnh sửa chức năng xử lý hóa đơn (Invoice), bạn chỉ cần thay đổi lớp Invoice, không ảnh hưởng đến lớp Email hay Logging.

Điều này đúng với nguyên lý SRP (Single Responsibility Principle) – mỗi lớp chỉ chịu trách nhiệm về một vai trò duy nhất, nên việc bảo trì, kiểm thử và mở rộng hệ thống trở nên dễ dàng và an toàn hơn.

Logger.cs

Ở đây, chúng ta tạo một Interface có tên là ILogger với ba phương thức trừu tượng (abstract) (mặc định, các phương thức trong interface đều là trừu tượng).

Sau đó, chúng ta cài đặt các phương thức của interface ILogger trong lớp Logger.

Các phương thức Info, DebugError sẽ thực hiện các hoạt động ghi log khác nhau, và tất cả các phương thức này đều được đặt trong lớp Logger.

using System;
namespace SOLID_PRINCIPLES.SRP
{ public interface ILogger { void Info(string info); void Debug(string info); void Error(string message, Exception ex); } public class Logger : ILogger { public Logger() { // here we need to write the Code for initialization  // that is Creating the Log file with necesssary details } public void Info(string info) { // here we need to write the Code for info information into the ErrorLog text file } public void Debug(string info) { // here we need to write the Code for Debug information into the ErrorLog text file } public void Error(string message, Exception ex) { // here we need to write the Code for Error information into the ErrorLog text file } }
}

MailSender.cs

namespace SOLID_PRINCIPLES.SRP
{ public class MailSender { public string EMailFrom { get; set; } public string EMailTo { get; set; } public string EMailSubject { get; set; } public string EMailBody { get; set; } public void SendEmail() { // Here we need to write the Code for sending the mail } }
}

Invoice.cs

using System.Net.Mail;
using System;
namespace SOLID_PRINCIPLES.SRP
{ public class Invoice { public long InvAmount { get; set; } public DateTime InvDate { get; set; } private ILogger fileLogger; private MailSender emailSender; public Invoice() { fileLogger = new Logger(); emailSender = new MailSender(); } public void AddInvoice() { try { fileLogger.Info("Add method Start"); // Here we need to write the Code for adding invoice // Once the Invoice has been added, then send the mail emailSender.EMailFrom = "emailfrom@xyz.com"; emailSender.EMailTo = "emailto@xyz.com"; emailSender.EMailSubject = "Single Responsibility Princile"; emailSender.EMailBody = "A class should have only one reason to change"; emailSender.SendEmail(); } catch (Exception ex) { fileLogger.Error("Error Occurred while Generating Invoice", ex); } } public void DeleteInvoice() { try { //Here we need to write the Code for Deleting the already generated invoice fileLogger.Info("Delete Invoice Start at @" + DateTime.Now); } catch (Exception ex) { fileLogger.Error("Error Occurred while Deleting Invoice", ex); } } }
}

Chúng ta cần thiết kế ứng dụng theo đúng Single Responsibility Principle - SRP trong C#.

Điều này có nghĩa là mỗi lớp trong ứng dụng phải có một trách nhiệm duy nhất của riêng nó, và chỉ nên có một lý do duy nhất khiến lớp đó cần được thay đổi.

Nói cách khác, mỗi module phần mềm hoặc mỗi lớp nên chỉ tập trung vào một chức năng cụ thể, không đảm nhiệm quá nhiều vai trò khác nhau.


Lợi ích của việc áp dụng Nguyên lý SRP trong C#:

  • Dễ hiểu hơn:

    Các lớp chỉ đảm nhiệm một trách nhiệm duy nhất thường nhỏ gọn và tập trung hơn, giúp chúng dễ đọc và dễ hiểu. Mỗi lớp có mục đích rõ ràng, nên lập trình viên có thể nhanh chóng nắm bắt được chức năng của nó.

  • Dễ sửa đổi hơn:

    Khi các lớp được thiết kế chỉ với một trách nhiệm, thì những thay đổi trong yêu cầu của hệ thống sẽ chỉ ảnh hưởng đến ít thành phần hơn. Điều này giúp việc cập nhật và bảo trì mã nguồn trở nên dễ dàng, vì thay đổi ở một phần của hệ thống sẽ ít có khả năng ảnh hưởng đến các phần khác.

  • Dễ kiểm thử hơn:

    SRP tạo ra các lớp nhỏ gọn hơn, do đó việc kiểm thử cũng đơn giản hơn. Mỗi bài test có thể tập trung kiểm tra một chức năng duy nhất, giúp giảm độ phức tạp của test case. Ngoài ra, việc viết và hiểu các unit test cũng dễ dàng hơn.

  • Tăng khả năng tái sử dụng:

    Những lớp chỉ phụ trách một chức năng cụ thể thì có khả năng tái sử dụng cao hơn trong các phần khác của ứng dụng — hoặc thậm chí trong các dự án khác.

  • Tổ chức mã nguồn tốt hơn:

    SRP giúp tổ chức codebase một cách khoa học hơn. Mỗi lớp và mỗi module sẽ tập trung vào một phần riêng biệt của ứng dụng, điều này rất quan trọng trong phát triển hiện đại, ví dụ như mô hình Microservices, nơi mỗi dịch vụ đều tập trung vào một chức năng nghiệp vụ cụ thể.

Bình luận

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

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

4 đặc tính của lập trình hướng đối tượng (Object oriented program)

Lập trình hướng đối tượng quá quen thuộc rồi bạn nào học lập trình đều phải học, đi phỏng vấn cũng vậy hỏi suốt(chắc cái này tùy vào vị trí tuyển dụng chủ yếu junior chắc chắn sẽ hỏi).nó là nền tảng cho hầu hết các design pattern hiện nay.

0 0 52

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

Khác nhau giữa abstract class và interface khi nào dùng chúng

Nhắc đến Interface và abstract class hãy nhớ 2 từ này khá clear rồi, Khi sử dụng Interface là bạn Implement còn sử dụng abstract class là bạn extend. . Interface:. .

0 0 47

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

So sánh Interface và Abstract trong lập trình hướng đối tượng.

Tổng quan. Interface và Abstract class là 2 khái niệm cơ bản trong lập trình OOP.

0 0 67

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

Áp Dụng Nguyên Tắc SOLID Trong Lập Trình

Giới Thiệu. 1. SOLID là gì. SOLID là viết tắt của 5 chữ cái đầu trong 5 nguyên tắc:.

0 0 42

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

Kỹ thuật giải quyết bài toàn về policy và công thức tính toán động cho sản phẩm phần mềm

Dạo này tôi có một mối duyên rất tình cờ với việc làm các phần mềm thuộc lĩnh vực tài chính và ngân hàng. Một số bài toán trong lĩnh vực này làm tôi nhớ đến những ngày đầu làm việc với phần mềm Trinet

0 0 39

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

Object Relational Mapping

Trong cách phát triển ứng dụng web hiện nay chắc hẳn các bạn đã quen với với từ khóa ORM(Object Relational Mapping). Khi mà thời đại của các framework ứng với các ngôn ngữ đang lên ngôi một cách mạnh

0 0 44