اصل Open Closed چيست؟

در سري پست هاي تعريف اصول SOLID  با

آشنا شديم در اين پست دومين اصل يعني Open Closed رو برسي ميكنيم.
اين اصل ميگه يك كلاس بايد براي توسعه باز و براي تغيير بسته باشه بنابراين طراحي شما بايد به گونه ي باشه كه براي اضافه كردن يك قابليت جديد به كلاستون حداقل تغيير ممكن رو تو كلاستون داشته باشيد.در واقع بايد براي اضافه كردن قابليت جديد از كلاس جديد استفاده كنيم چون فرض شده تغيير تو كلاسي كه قبلا ساخته شده و داره ازش استفاده ميشه منجر به نتايج ناخواسته (باگ و…) در استفاده كننده هاي اين كلاس (مثلا يك كلاس ديگر) ميشه.
ابهامات احتمالي

  • كلاسي كه نبايد تغيير كنه پس نياز به تست واحد هم نداره؟
  • اگه كلاس باگ داشت چي(نياز به تغيير)؟

جواب بعضي از اين ابهامات + و + (شايد نادرست)

  • حتي اگه شما اصل Open Closed رو رعايت كرده باشيد امكانش هست سهوا يك كلاس رو تغيير بديد پس تست واحد رو داشته باشد!
  • كلاسي كه براي اولين بار طراحي شده براي صحت عملكرد بايد تست بشه پس شما تست واحد رو از قبل داريد(توجيه خوبي براي اولين ابهام)
  • مورد استفاده اين اصل بيشتر در مواقعي است كه استفاده كننده ي كلاس شما نيستيد مثلا طراحي API.
  • اي اصل براي كلاس ي هست كه در حال استفاده شدنه نه كلاسي كه در مرحله طراحي و تست اون هستم و در نتيجه اضافه كردن قابليت و تغييرات داخلش بديهي هست.

و اما برای مثال عملی فرض کنید میخواهیم کلاسی (كنترلر MVC) تعریف کنیم که کاربر بتونه براي بلاگ كامنت ارسال كنه البته قبلش اطمينان حاصل بشه كه كاربر Bot نيست و از ارسال اسپم جلوگيري كنيم.

public class EntryController
{
    public void AddComment()
    {
        if(ValidateNotSpam())
        {
             //ذخيره در بانك
        }
    }
    
    private bool ValidateNotSpam(string comment)
    {
        //چك كردن اينكه آي پي كاربر پست كننده در ليست سياده سرويس دهنده ي مربوطه نباشه
    }
}

اولين اصلي كه اين كد نقض كرده Single responsibility هست كار اين كلاس هم ارسال كامنت و هم چك كردن اسپمر نبودن كاربره و… 
در اين كلاس براي راحتي كار از CAPTCHA استفاده نشده و از سرويسي كه سايت Project Honey Pot ارئه داده (چك كردن اسمپر نبودن كاربران) استفاده شده فرض كنيد ميخواهيم علاوه بر سرويس حاضر از سرويس ديگري مثلا سايت Akismet هم استفاده كنيم چه بايد كرد …
با توجه به كلاس بالا يك راه داريم تغيير 
و به اين ترتيب اصل Open Closed رو هم نقض كرديم.
و اما راه حل درست (رعايت كردن اصل Open Closed)
با تعريف اينترفيس كا رو شروع ميكينم

public interface ICommentValidator
{
    bool Validate(string authorIP, string content);
}

در سازنده كلاسمون آرايه اي از ICommentValidator رو ميگيريم

public class EntryController : Controller 
{
    private ICommentValidator[] _commentValidators;

    public EntryController(ICommentValidator[] commentValidators)
    {
        _commentValidators = commentValidators;
    }
}

متد ValidateNotSpam رو هم تغيير ميديم كه متد Validate تمام اعضاي آرايه ي ورودي رو فراخواني ميكنه

public class EntryController
{
    private bool ValidateNotSpam(string comment)
    {
        foreach (ICommentValidator validator in _commentValidators)
        {
            if (!validator.Validate(Request.ServerVariables["REMOTE_ADDR"], comment))
                return false;
        }

        return true;
    }
}

آخرين كاري هم كه بايد انجام بديم اينه كه به ازاي هر سرويس دهندي مود نظرمون يك كلاسي كه اينترفيس ICommentValidator رو پياده سازي كرده داشته باشيم(در اينجا Project Honey Pot وAkismet)

public class ProjectHoneyPotCommentValidator : ICommentValidator
{
    public bool Validate(string authorIP, string content)
    {
        //چك كردن اينكه آي پي كاربر پست كننده در ليست سياده سرويس دهنده ي مربوطه نباشه
    }
}

و اين يعني باز بودن براي توسعه

ادامه دارد …

نظرات

پست‌های معروف از این وبلاگ

lnav ابزاری بسیار کاربردی برای پیمایش لاگ ها در لینوکس و البته مک

ساختن ایمیج های داکری به کمک BuildKit - بخش دوم

ساختن ایمیج های داکری به کمک BuildKit - بخش اول