Open-Closed Principle

یک کلاس باید برای توسعه باز اما برای تغییر بسته باشد. منظور از باز بودن برای توسعه این است که تغییر رفتار کلاس باید به سادگی انجام بگیرد – بسته بودن برای تغییر نیز یعنی بدون تغییر سورس کد بتوانیم رفتار کلاس یا اینتیتی را تغییر دهیم. برای درک بهتر این اصل اجازه دهید یک مثال را بررسی کنیم:

1
2
3
4
5
6
7
8
9
10
11
public class Square
{
    public Square(int width, int height)
    {
        Width = width;
        Height = height;
    }
    public int Width { get; set; }
    public int Height { get; set; }
}

 

در آینده براساس نیازمندی می‌خواهیم امکان محاسبه مربع را نیز به کلاس فوق اضافه کنیم؛ برای اینکار براساس اصل SRP یک کلاس برای اینکار ایجاد خواهیم کرد:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AreaCalculator
{
    public int Calculate(Square[] squares)
    {
        var area = 0;
        foreach (var square in squares)
        {
            area += square.Width * square.Height;
        }
        return area;
    }
}

 

تا اینجا مشکلی با کدهای فوق وجود ندارد؛ اما فرض کنید براساس نیازمندی جدید بخواهیم Circle را نیز به کد فوق اضافه کنیم:

1
2
3
4
5
6
7
8
9
public class Circle
{
    public Circle(int radius)
    {
        Radius = radius;
    }
    public int Radius { get; set; }
}

 

در این حالت نیز نیاز خواهیم داشت به صورت جداگانه تابعی برای محاسبه‌ی مساحت دایره نیز پیاده‌سازی کنیم زیرا تابع محاسبه‌ی مساحت فقط برای مربع قابل استفاده است؛  بنابراین باید سورس کد را برای اصلاح رفتار کلاس تغییر دهیم؛ یعنی تا اینجا اصل OC را نقض کرده‌ایم.

برای حل این مشکل بهتر است از توصیه Uncle Bob پیروی کنیم:

Separate extensible behavior behind an interface, and flip the dependencies

اولین قدم ایجاد یک اینترفیس با نام IShape است:

1
2
3
4
public interface IShape
{
    double CalculateArea();
}

اکنون هر کلاسی که نیاز به محاسبه‌ی مساحت دارد باید این اینترفیس را پیاده‌سازی کند:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Square : IShape
{
    public Square(int width, int height)
    {
        Width = width;
        Height = height;
    }
    public int Width { get; set; }
    public int Height { get; set; }
    public double CalculateArea()
    {
        return this.Width * this.Height;
    }
}
public class Circle : IShape
{
    public Circle(int radius)
    {
        Radius = radius;
    }
    public int Radius { get; set; }
    public double CalculateArea()
    {
        return this.Radius * this.Radius * Math.PI;
    }
}

 

قدم بعدی نیز تغییر کلاس محاسبه مساحت است:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AreaCalculator
{
    public double Calculate(IShape[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.CalculateArea();
        }
        return area;
    }
}

 

خب مزیت اینکار این است که برای افزودن shape جدید کد فوق نیاز به هیچ تغییری ندارد!

مثال فوق شاید در دنیای واقعی کاربردی نداشته باشد، هدف از این مثال آشنایی با الگوی OC بود؛ به عنوان یک مثال واقعی می‌توان نرم‌افزاری را در نظر گرفت که قرار است مالیات را براساس حقوق محاسبه کند؛ این نرم‌افزار همچنین قرار است در چندین کشور استفاده شود؛ نحوه‌ی محاسبه‌ی مالیات نیز در هر کشور متفاوت می‌باشد؛ در این سناریو می‌توانید از اصل OC به راحتی استفاده کنید.

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top