Hiểu Open - Closed Principle qua ví dụ !

Open - Closed Principle là gì

  • The Open Closed Principle (OCP) is the SOLID principle which states that the software entities (classes or methods) should be open for extension but closed for modification.
  • Nguyên tắc đóng mở (OCP) là một nguyên tắc trong SOLID mà ở đó các thực thể (class hoặc method) cần dễ mở rộng và khó sửa đổi.

Ví dụ thực tế

  • Hiện tại scope của team tất cả member đều là developer.
  • Bây giờ sếp cần chúng ta tính toán tổng lương của nhân viên trong team.
  • Ở đây salary của một dev = HourlyRate * WorkingHours
Bắt đầu nào !
  • Tạo class DeveloperReport
public class DeveloperReport
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Level { get; set; } // Level của member
    public int WorkingHours { get; set; } // Số giờ làm việc
    public double HourlyRate { get; set; } // Hệ số lương
}
  • Tiếp theo là viết hàm tính toán tổng lương của team
public class SalaryCalculator
{
    private readonly IEnumerable<DeveloperReport> _developerReports;
    public SalaryCalculator(List <DeveloperReport> developerReports)
    {
        _developerReports = developerReports;
    }
    public double CalculateTotalSalaries()
    {
        double totalSalaries = 0D ;
        foreach (var devReport in _developerReports)
        {
            totalSalaries += devReport.HourlyRate * devReport.WorkingHours;
        }
        return totalSalaries;
    }
}
  • Hàm Main
static void Main(string[] args)
{
    var devReports = new List<DeveloperReport>
    {
        new DeveloperReport {Id = 1, Name = "Dev1", Level = "Senior developer", HourlyRate = 10.5, WorkingHours = 55 }),
        new DeveloperReport {Id = 2, Name = "Dev2", Level = "Junior developer", HourlyRate = 20, WorkingHours = 30 }),
        new DeveloperReport {Id = 3, Name = "Dev3", Level = "Senior developer", HourlyRate = 12, WorkingHours = 50 })
    };
    var calculator = new SalaryCalculator(devReports);
    Console.WriteLine($"Tong luong cua anh em {calculator.CalculateTotalSalaries()} dollars");
}
  • Mọi thứ hoạt động đều tốt 😃. Nhưng một ngày đẹp trời chính sách công ty thay đổi. Sếp bảo "Không được tháng này là tháng 12 rồi các huynh đài senior member lúc này được tăng thêm 20%" vì một lý do nào đó mà ai cũng biết nhưng không ai nói 😃
  • Lúc này thì chúng ta cần phải sửa lại code một chút
  • Lúc này hàm tính toán lương của thành viên trong team sẽ trở thành
public double CalculateTotalSalaries()
{
    double totalSalaries = 0D;
    foreach (var devReport in _developerReports)
    {
        if(devReport.Level == "Senior developer")
        {
            totalSalaries += devReport.HourRate * devReport.WorkingHours * 1.2;
        }
        else
        {
            totalSalaries += devReport.HourRate * devReport.WorkingHours;
        }
    }
    return totalSalaries;
}
  • Mọi thứ lại hoạt động tốt trở lại nhưng có vẻ không ổn.
  • Mỗi lần có sự thay đổi yêu cầu của sếp về bonus salary của dev thì chúng ta phải sửa lại code ở hàm CalculateTotalSalaries cho đúng ý sếp 😦
  • Rõ ràng là cần có giải pháp nào đó để xử lý vấn đề này hmmm
  • Lúc này thì Open - Closed Principlesẽ phát huy tác dụng 😃

Áp dụng Open - Closed Principle

  • Tạo abstract class BaseSalaryCalculator
public abstract class BaseSalaryCalculator
{
    protected DeveloperReport DeveloperReport { get; private set; }
    public BaseSalaryCalculator(DeveloperReport developerReport)
    {
        DeveloperReport = developerReport;
    }
    public abstract double CalculateSalary();
}
  • Tiếp theo là tạo 2 class SeniorDevSalaryCalculator, JuniorDevSalaryCalculator tương ứng với 2 level dev trong team Senior & Junior (mỗi level dev thì có một cách tính hệ số lương khác nhau ở đây thì các huynh đài senior sẽ được x 1.2)
public class SeniorDevSalaryCalculator : BaseSalaryCalculator
{
    public SeniorDevSalaryCalculator(DeveloperReport report)
        :base(report)
    {
    }

    public override double CalculateSalary() => DeveloperReport.HourlyRate * DeveloperReport.WorkingHours * 1.2;
}
public class JuniorDevSalaryCalculator : BaseSalaryCalculator
{
    public JuniorDevSalaryCalculator(DeveloperReport developerReport)
        :base(developerReport)
    {
    }

    public override double CalculateSalary() => DeveloperReport.HourlyRate * DeveloperReport.WorkingHours;
} 
  • Bây giờ là lúc viết hàm tính toán lương SalaryCalculator

public class SalaryCalculator
{
    private readonly IEnumerable<BaseSalaryCalculator> _developerCalculation;

    public SalaryCalculator(IEnumerable<BaseSalaryCalculator> developerCalculation)
    {
        _developerCalculation = developerCalculation;
    }

    public double CalculateTotalSalaries()
    {
        double totalSalaries = 0D;

        foreach (var devCalc in _developerCalculation)
        {
            totalSalaries += devCalc.CalculateSalary();
        }

        return totalSalaries;
    }
}
  • Hàm main
 class Program
{
    static void Main(string[] args)
    {
        var devCalculations = new List<BaseSalaryCalculator>
        {
            new SeniorDevSalaryCalculator(new DeveloperReport {Id = 1, Name = "Dev1", Level = "Senior developer", HourlyRate = 10.5, WorkingHours = 55 }),
            new JuniorDevSalaryCalculator(new DeveloperReport {Id = 2, Name = "Dev2", Level = "Junior developer", HourlyRate = 20, WorkingHours = 30 }),
            new SeniorDevSalaryCalculator(new DeveloperReport {Id = 3, Name = "Dev3", Level = "Senior developer", HourlyRate = 12, WorkingHours = 50 })
        };
        var calculator = new SalaryCalculator(devCalculations);
        Console.WriteLine($"Tong luong cua anh em {calculator.CalculateTotalSalaries()} dollars");
    }
} 
  • Mọi thứ bây giờ có vẻ khá ổn. Nếu một ngày đẹp trời sếp lại yêu cầu thay đổi cách tính lương của một level dev mới trong team thì đơn giản. Chúng ta chỉ cần tạo thêm một class giống như class SeniorDevSalaryCalculator là xong !!
Tóm cái váy lại.
  • Nhìn vào ví dụ bên trên thì chúng ta thấy class SalaryCalculator đã bị đóng sổ (Closed) nhưng có thể được mở rộng (Open). Đúng với những gì mà Open - Closed Principle yêu cầu.
  • Cách code và tổ chức code nhìn rất khoa học dễ hiểu và quan trọng hơn là chiều được lòng sếp 😉

All Rights Reserved