برنامه نویسی

اصل باز/بسته در سی شارپ با فیلترها و مشخصات

Summarize this content to 400 words in Persian Lang
اصول طراحی نرم افزار برای اطمینان از اینکه کد ما قابل نگهداری، مقیاس پذیر و قوی باقی می ماند، اساسی است. یکی از اصول کلیدی در اصول طراحی جامد، اصل باز/بسته (OCP) است. این اصل بیان می‌کند که موجودیت‌های نرم‌افزار باید برای توسعه باز باشند اما برای اصلاح بسته شوند. بیایید بررسی کنیم که چگونه می توانیم به این اصل از طریق یک مثال عملی مربوط به فیلتر کردن محصول پایبند باشیم.

پیاده سازی اولیه: مشکل

تصور کنید ما یک کاتالوگ محصول ساده داریم که در آن هر محصول دارای نام، رنگ و اندازه است. ما به راهی برای فیلتر کردن این محصولات بر اساس معیارهای مختلف نیاز داریم. یک پیاده سازی ساده ممکن است به شکل زیر باشد:

public enum Color
{
Red, Green, Blue
}

public enum Size
{
Small, Medium, Large, Yuge
}

public class Product
{
public string Name;
public Color Color;
public Size Size;

public Product(string name, Color color, Size size)
{
Name = name ?? throw new ArgumentNullException(paramName: nameof(name));
Color = color;
Size = size;
}
}

public class ProductFilter
{
public IEnumerableProduct> FilterByColor(IEnumerableProduct> products, Color color)
{
foreach (var p in products)
if (p.Color == color)
yield return p;
}

public static IEnumerableProduct> FilterBySize(IEnumerableProduct> products, Size size)
{
foreach (var p in products)
if (p.Size == size)
yield return p;
}

public static IEnumerableProduct> FilterBySizeAndColor(IEnumerableProduct> products, Size size, Color color)
{
foreach (var p in products)
if (p.Size == size && p.Color == color)
yield return p;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در حالی که این پیاده سازی کار می کند، به راحتی می توان دید که چگونه می تواند به سرعت غیرقابل مدیریت شود. هر معیار فیلتر جدید یا ترکیبی از معیارها نیاز به روش جدیدی دارد. این رویکرد اصل باز/بسته را نقض می‌کند زیرا ProductFilter هر بار که یک نیاز فیلترینگ جدید معرفی می شود، کلاس باید اصلاح شود.

Refactoring با OCP: راه حل

برای پایبندی به اصل باز/بسته، ما به راهی برای گسترش عملکرد فیلترینگ خود بدون تغییر کد موجود نیاز داریم. ما می توانیم با استفاده از الگوی Specification که به ما امکان می دهد معیارها را به روشی قابل استفاده مجدد و ترکیبی تعریف کنیم، به این هدف دست یابیم.

مرحله 1: تعریف رابط

ابتدا دو رابط تعریف می کنیم: یکی برای مشخصات و دیگری برای فیلترها.

public interface ISpecificationT>
{
bool IsSatisfied(T item);
}

public interface IFilterT>
{
IEnumerableT> Filter(IEnumerableT> items, ISpecificationT> spec);
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 2: پیاده سازی مشخصات

در مرحله بعد، مشخصات بتن را برای رنگ و اندازه اجرا می کنیم.

public class ColorSpecification : ISpecificationProduct>
{
private Color color;

public ColorSpecification(Color color)
{
this.color = color;
}

public bool IsSatisfied(Product p)
{
return p.Color == color;
}
}

public class SizeSpecification : ISpecificationProduct>
{
private Size size;

public SizeSpecification(Size size)
{
this.size = size;
}

public bool IsSatisfied(Product p)
{
return p.Size == size;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 3: مشخصات را ترکیب کنید

ما همچنین می توانیم مشخصات ترکیبی برای ترکیب چندین معیار ایجاد کنیم.

public class AndSpecificationT> : ISpecificationT>
{
private ISpecificationT> first, second;

public AndSpecification(ISpecificationT> first, ISpecificationT> second)
{
this.first = first ?? throw new ArgumentNullException(paramName: nameof(first));
this.second = second ?? throw new ArgumentNullException(paramName: nameof(second));
}

public bool IsSatisfied(T item)
{
return first.IsSatisfied(item) && second.IsSatisfied(item);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

در نهایت، ما را اجرا می کنیم BetterFilter کلاسی که از مشخصات برای فیلتر کردن محصولات استفاده می کند.

public class BetterFilter : IFilterProduct>
{
public IEnumerableProduct> Filter(IEnumerableProduct> items, ISpecificationProduct> spec)
{
foreach (var i in items)
if (spec.IsSatisfied(i))
yield return i;
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نمایش: قرار دادن همه چیز

در اینجا نمایشی از نحوه استفاده از سیستم فیلتر اصلاح شده آورده شده است:

public class Demo
{
static void Main(string[] args)
{
var apple = new Product(“Apple”, Color.Green, Size.Small);
var tree = new Product(“Tree”, Color.Green, Size.Large);
var house = new Product(“House”, Color.Blue, Size.Large);

Product[] products = { apple, tree, house };

var pf = new ProductFilter();
WriteLine(“Green products (old):”);
foreach (var p in pf.FilterByColor(products, Color.Green))
WriteLine($” – {p.Name} is green”);

var bf = new BetterFilter();
WriteLine(“Green products (new):”);
foreach (var p in bf.Filter(products, new ColorSpecification(Color.Green)))
WriteLine($” – {p.Name} is green”);

WriteLine(“Large products:”);
foreach (var p in bf.Filter(products, new SizeSpecification(Size.Large)))
WriteLine($” – {p.Name} is large”);

WriteLine(“Large blue items:”);
foreach (var p in bf.Filter(products, new AndSpecificationProduct>(new ColorSpecification(Color.Blue), new SizeSpecification(Size.Large))))
WriteLine($” – {p.Name} is big and blue”);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه

با اعمال اصل Open/Closed از طریق الگوی Specification، یک سیستم فیلتر انعطاف پذیر و قابل نگهداری ایجاد کردیم. این BetterFilter کلاس برای گسترش از طریق مشخصات جدید باز است اما برای اصلاح بسته است، زیرا دیگر نیازی به تغییر اجرای آن برای افزودن معیارهای فیلتر جدید نداریم.

این رویکرد نه تنها به اصول SOLID پایبند است، بلکه مقیاس پذیری و خوانایی کد ما را نیز افزایش می دهد و نگهداری و گسترش آن در آینده را آسان تر می کند.

اصول طراحی نرم افزار برای اطمینان از اینکه کد ما قابل نگهداری، مقیاس پذیر و قوی باقی می ماند، اساسی است. یکی از اصول کلیدی در اصول طراحی جامد، اصل باز/بسته (OCP) است. این اصل بیان می‌کند که موجودیت‌های نرم‌افزار باید برای توسعه باز باشند اما برای اصلاح بسته شوند. بیایید بررسی کنیم که چگونه می توانیم به این اصل از طریق یک مثال عملی مربوط به فیلتر کردن محصول پایبند باشیم.

پیاده سازی اولیه: مشکل

تصور کنید ما یک کاتالوگ محصول ساده داریم که در آن هر محصول دارای نام، رنگ و اندازه است. ما به راهی برای فیلتر کردن این محصولات بر اساس معیارهای مختلف نیاز داریم. یک پیاده سازی ساده ممکن است به شکل زیر باشد:

public enum Color
{
    Red, Green, Blue
}

public enum Size
{
    Small, Medium, Large, Yuge
}

public class Product
{
    public string Name;
    public Color Color;
    public Size Size;

    public Product(string name, Color color, Size size)
    {
        Name = name ?? throw new ArgumentNullException(paramName: nameof(name));
        Color = color;
        Size = size;
    }
}

public class ProductFilter
{
    public IEnumerableProduct> FilterByColor(IEnumerableProduct> products, Color color)
    {
        foreach (var p in products)
            if (p.Color == color)
                yield return p;
    }

    public static IEnumerableProduct> FilterBySize(IEnumerableProduct> products, Size size)
    {
        foreach (var p in products)
            if (p.Size == size)
                yield return p;
    }

    public static IEnumerableProduct> FilterBySizeAndColor(IEnumerableProduct> products, Size size, Color color)
    {
        foreach (var p in products)
            if (p.Size == size && p.Color == color)
                yield return p;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در حالی که این پیاده سازی کار می کند، به راحتی می توان دید که چگونه می تواند به سرعت غیرقابل مدیریت شود. هر معیار فیلتر جدید یا ترکیبی از معیارها نیاز به روش جدیدی دارد. این رویکرد اصل باز/بسته را نقض می‌کند زیرا ProductFilter هر بار که یک نیاز فیلترینگ جدید معرفی می شود، کلاس باید اصلاح شود.

Refactoring با OCP: راه حل

برای پایبندی به اصل باز/بسته، ما به راهی برای گسترش عملکرد فیلترینگ خود بدون تغییر کد موجود نیاز داریم. ما می توانیم با استفاده از الگوی Specification که به ما امکان می دهد معیارها را به روشی قابل استفاده مجدد و ترکیبی تعریف کنیم، به این هدف دست یابیم.

مرحله 1: تعریف رابط

ابتدا دو رابط تعریف می کنیم: یکی برای مشخصات و دیگری برای فیلترها.

public interface ISpecificationT>
{
    bool IsSatisfied(T item);
}

public interface IFilterT>
{
    IEnumerableT> Filter(IEnumerableT> items, ISpecificationT> spec);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 2: پیاده سازی مشخصات

در مرحله بعد، مشخصات بتن را برای رنگ و اندازه اجرا می کنیم.

public class ColorSpecification : ISpecificationProduct>
{
    private Color color;

    public ColorSpecification(Color color)
    {
        this.color = color;
    }

    public bool IsSatisfied(Product p)
    {
        return p.Color == color;
    }
}

public class SizeSpecification : ISpecificationProduct>
{
    private Size size;

    public SizeSpecification(Size size)
    {
        this.size = size;
    }

    public bool IsSatisfied(Product p)
    {
        return p.Size == size;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرحله 3: مشخصات را ترکیب کنید

ما همچنین می توانیم مشخصات ترکیبی برای ترکیب چندین معیار ایجاد کنیم.

public class AndSpecificationT> : ISpecificationT>
{
    private ISpecificationT> first, second;

    public AndSpecification(ISpecificationT> first, ISpecificationT> second)
    {
        this.first = first ?? throw new ArgumentNullException(paramName: nameof(first));
        this.second = second ?? throw new ArgumentNullException(paramName: nameof(second));
    }

    public bool IsSatisfied(T item)
    {
        return first.IsSatisfied(item) && second.IsSatisfied(item);
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

در نهایت، ما را اجرا می کنیم BetterFilter کلاسی که از مشخصات برای فیلتر کردن محصولات استفاده می کند.

public class BetterFilter : IFilterProduct>
{
    public IEnumerableProduct> Filter(IEnumerableProduct> items, ISpecificationProduct> spec)
    {
        foreach (var i in items)
            if (spec.IsSatisfied(i))
                yield return i;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نمایش: قرار دادن همه چیز

در اینجا نمایشی از نحوه استفاده از سیستم فیلتر اصلاح شده آورده شده است:

public class Demo
{
    static void Main(string[] args)
    {
        var apple = new Product("Apple", Color.Green, Size.Small);
        var tree = new Product("Tree", Color.Green, Size.Large);
        var house = new Product("House", Color.Blue, Size.Large);

        Product[] products = { apple, tree, house };

        var pf = new ProductFilter();
        WriteLine("Green products (old):");
        foreach (var p in pf.FilterByColor(products, Color.Green))
            WriteLine($" - {p.Name} is green");

        var bf = new BetterFilter();
        WriteLine("Green products (new):");
        foreach (var p in bf.Filter(products, new ColorSpecification(Color.Green)))
            WriteLine($" - {p.Name} is green");

        WriteLine("Large products:");
        foreach (var p in bf.Filter(products, new SizeSpecification(Size.Large)))
            WriteLine($" - {p.Name} is large");

        WriteLine("Large blue items:");
        foreach (var p in bf.Filter(products, new AndSpecificationProduct>(new ColorSpecification(Color.Blue), new SizeSpecification(Size.Large))))
            WriteLine($" - {p.Name} is big and blue");
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه

با اعمال اصل Open/Closed از طریق الگوی Specification، یک سیستم فیلتر انعطاف پذیر و قابل نگهداری ایجاد کردیم. این BetterFilter کلاس برای گسترش از طریق مشخصات جدید باز است اما برای اصلاح بسته است، زیرا دیگر نیازی به تغییر اجرای آن برای افزودن معیارهای فیلتر جدید نداریم.

این رویکرد نه تنها به اصول SOLID پایبند است، بلکه مقیاس پذیری و خوانایی کد ما را نیز افزایش می دهد و نگهداری و گسترش آن در آینده را آسان تر می کند.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا