Nguyên lý SOLID strong C#
SOLID là nguyên tắc thiết kế, phát triển phần mềm giúp code dễ đọc, code tốt, code dễ bảo trì, ược ghép lại từ 5 chữ viết tắt đầu tiên của 5 nguyên tắc này:
- S is single responsibility principle (SRP): Một class chỉ nên giữ 1 trách nhiệm duy nhất
- O stands for open closed principle (OCP): Có thể thoải mái mở rộng 1 class, nhưng không được sửa đổi bên trong class đó
- L Liskov substitution principle (LSP): trong 1 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
- I interface segregation principle (ISP): thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể
- D Dependency injection principle (DIP): các module cấp cao không nên phụ thuộc vào các module cấp thấp, cả 2 nên phụ thuộc vào abstract interface(abtraction) 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 qua interface, không phải qua implement)
Example:
Single responsibility principle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace Hondanho { | |
// entity: đai diện cho các field database | |
public class Student { | |
public int StudentID { get; set; } | |
public string StudentName { get; set; } | |
public int Age { get; set; } | |
public List<Student> GetAllStudent() { | |
return new List<Student> { | |
new Student() { StudentID = 1, StudentName = "Ali", Age = 18 }, | |
}; | |
} | |
} | |
// repository: giao tiếp với data layer, lấy data từ database | |
public interface IStudentRepository { | |
List<Student> GetAllStudent(); | |
Student GetStudent(int id); | |
} | |
public class StudentRepository : IStudentRepository { | |
public Student _student { get; set; } | |
public StudentRepository(Student student) { | |
_student = student; | |
} | |
public List<Student> GetAllStudent() => _student.GetAllStudent(); | |
public Student GetStudent(int id) => _student.GetAllStudent().FirstOrDefault(x => x.StudentID == id); | |
} | |
// service: sử lý nghiệp vụ logic phức tạp tại đây | |
public interface IServiceStudent { | |
List<Student> GetAllStudent(); | |
Student GetStudent(int id); | |
} | |
public class ServiceStudent : IServiceStudent { | |
private IStudentRepository _studentRepository; | |
public ServiceStudent(IStudentRepository studentRepository) { // đưa các class StudentRepository vào hàm khởi tạo | |
_studentRepository = studentRepository; | |
} | |
public List<Student> GetAllStudent() => _studentRepository.GetAllStudent(); | |
public Student GetStudent(int id) => _studentRepository.GetStudent(id); | |
} | |
public class Program { | |
public static void Main(string[] args) { | |
var student = new Student(); | |
IStudentRepository repositoryStudent = new StudentRepository(student); // inject student vào StudentRepository | |
IServiceStudent studentService = new ServiceStudent(repositoryStudent); // inject StudentRepository vào ServiceStudent | |
var listStudent = studentService.GetAllStudent(); | |
listStudent.ForEach(std => Console.WriteLine("Student Name: " + std.StudentName)); | |
} | |
} | |
} | |
/* - trong ví dụ này, class StudentRepository dùng để lấy dữ liệu từ database | |
* - class ServiceStudent dùng để xử lý logic lấy data của student | |
* - trong trường hợp cần báo cáo về tình hình học tập, thì lại làm 1 class Report chuyên dùng để report, khi có bất cứ thay đổi nào từ class report cũng ko ảnh hướng tới student | |
* - Tóm lại: nên tách các công việc sử lý ra theo từng chứ năng riêng biệt, mục đích không để ảnh hưởng tới code trước đó: ko phải test các code trước đó, gọn gàng dễ hiểu, .. | |
*/ |
Stands for open closed principle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Composition; | |
using System.Threading.Tasks; | |
namespace Hondanho { | |
public class ReportStudent { | |
public string typeExport { get; set; } | |
public void ExportStudent(string type) { | |
if(type == "csv") { | |
// doing csv | |
} else if(type == "pdf") { | |
// doing pdf | |
} | |
} | |
} | |
} | |
/* | |
trong trường hợp thêm 1 cái type là html thì phải sửa hàm ExportStudent, vấn đề là ở đó, không nên sửa code cũ khi chức năng mở rộng, | |
thay vào đó tạo thành 1 class khác xem ví dụ dưới đây | |
*/ | |
namespace Hondanho { | |
public interface IGenerateRerport { | |
public Task Export(); | |
} | |
public class ExportCsv : IGenerateRerport { | |
public async Task Export() { | |
// doing export csv | |
} | |
} | |
public class ExportPdf : IGenerateRerport { | |
public async Task Export() { | |
// doing export pdf | |
} | |
} | |
public class ReportStudent { | |
private IGenerateRerport _generateRerport { get; set; } | |
public ReportStudent(IGenerateRerport generateRerport) { | |
} | |
public void ExportStudent() { | |
_generateRerport.Export(); | |
} | |
} | |
public class Program { | |
public static void Main(string[] args) { | |
var reportStudentPdf = new ReportStudent(new ExportPdf()); | |
reportStudentPdf.ExportStudent(); | |
var reportStudentCsv = new ReportStudent(new ExportPdf()); | |
reportStudentCsv.ExportStudent(); | |
} | |
} | |
} |
Liskov substitution principle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Composition; | |
using System.Threading.Tasks; | |
namespace Hondanho { | |
public class ReportStudent { | |
public string typeExport { get; set; } | |
public virtual void ExportStudent(string type) { | |
Console.WriteLine("class cha"); | |
} | |
} | |
public class ReportStudentSummaryA : ReportStudent { | |
public override void ExportStudent(string type) { | |
Console.WriteLine("class Con A"); | |
} | |
} | |
public class ReportStudentSummaryB : ReportStudent { | |
public override void ExportStudent(string type) { | |
Console.WriteLine("class Con B"); | |
} | |
} | |
} | |
/* vấn đề ở đây là 2 class đều ghi đè 1 method của thg cha, nên ko kiểm soát được code, dễ phát sinh bug | |
nên tách ReportStudentSummaryB thành 1 class mới | |
*/ |
Interface segregation principle
Nên chia interface lớn thành các interface nhỏ khác nhau, việc gom quá nhiều method thì sẽ khó implement(các class kế thừa phải implement tất cả các method của interface đó)