انجمن برنامه نویسان البرز

تبلیغات

برنامه نویسی و مفهوم SOLID : بخش دوم Open-Closed

برنامه نویسی و مفهوم SOLID : بخش دوم Open-Closed


در این مقاله شما را با مفهوم Open-Closed از خانواده مفاهیم SOLID آشنا خواهیم کرد و یک مثال C# هم خواهیم زد.

Open-Closed چیست؟

در سال ۱۹۸۸ مقهوم Open-Closed توسط ‘برتراند میر’ مطرح شد: موجودیت های یک نرم افزار (کلاس ها، ماژول ها، توابع و …) باید قابل گسترش پیدا کردن باشند اما تغییر نکنند! به بیان ساده تر، وقتی میخواهید برنامه ای را گسترش بدهید باید بتوانید به آن قابلیت هایی را اضافه کنید، بدون اینکه مجبور باشید کدهایتان را تغییر بدهید. در واقع باید بتوانید بدون تغییر کد فعلی، قابلیت های جدید را اضافه کنید.

چرا باید این کار را انجام بدهیم؟

صادقانه بگویید چندبار پیش آمده است که یک نرم افزار را از اول آن بنویسید و توسعه بدهید؟ در مقابل، چندبار مجبور بوده اید یک نرم افزار را ویرایش کنید و قابلیت های جدید به آن اضافه کنید؟

به احتمال زیاد، شما خیلی بیشتر از تولید کدها، به ویرایش آنها مشغول بوده اید. Open-Closed در واقع برای این طراحی شده است که شما مهمترین قابلیت برنامه نویسی شیئ گرا را تحت کنترل بگیرید: قابلیت تغییر ساده و استفاده مجدد از کدها.

چطور باید از Open-Closed استفاده کنیم؟

بهترین روش برای استفاده از این مفهوم، این است که اول از همه، قاعده SR یا Single Responsibility را در کدهایتان رعایت کنید. موضوع SR این است که یک کلاس، باید فقط و فقط و فقط یک دلیل برای تغییر داشته باشد. به این معنی که هر کلاس فقط یک مسئولیتدارد و نه بیشتر. برای مثال اگر کلاسی داشته باشید که حقوق یک کارمند را حساب کند و مقدار محاسبه شده را چاپ کند، کلاستان را غلط نوشته اید!!! باید آنرا به دو کلاس بشکنید. یکی از کلاس ها وظیفه دارد حقوق کارمند را محاسبه کند و یکی دیگر، وظیفه چاپ را به عهده دارد. به این ترتیب، اگر مشکلی با چاپ داشتید، مستقیما به سراغ کلاس مسئول چاپ خواهید رفت.

نکته بعدی این است که باید از کلاس های انتزاعی (قراردادها یا همان interface ها استفاده کنید) به این معنی که کلاس کارمند شما، برای چاپ کردن فیش حقوق، از کلاس ‘چاپ’ استفاده نخواهد کرد. بلکه با یک کلاس انتزاعی ‘چاپ’ کار میکند. اگر در فهمیدن این موضوع دچار مشکل شده اید، میتوانید مقاله کاربرد interface در برنامه نویسی سی شارپ را مطالعه کنید.

پس تا اینجای کار فهمیدیم که برای پیاده سازی Open-Closed نیاز داریم قواعد SR را رعایت کنیم و همینطور کلاس های انتزاعی بسازیم.

در یک جمله اگر بخواهیم Open-Closed را خلاصه کنیم:

  • شماباید ماژول ها و کلاس هایی طراحی کنید که هیچ وقت تغییر نکنند.
  • وقتی نیاز های برنامه تغییر میکند، شما رفتار کلاس یا ماژولتان را با افزودن کد گسترش میدهید. اما کدهای قدیمی را که به درستی کار میکنند، دستکاری نمیکنید.

Abstraction راه حل پیاده سازی این مفهوم است. کلاس هایی که از یک کلاس abstract یا یک interface مشتق شده اند، غیر قابل ویرایش و دستکاری هستند چون کلاس یا اینترفیس اصلی، تغییر نکرده است. اما از طریق ارث بری در کلاس های جدید، قابل گسترش هستند. در ادامه این موضوع رابا مثال روشن تر میکنیم.

یک مثال کاربردی

فرض کنید یک برنامه محاسبه حقوق و دستمزد کارمندان داریم. در این برنامه با انواع کارمندها سر و کار داریم. مثلا: برنامه نویس و مدیر پروژه و حسابدار. هر کدام از این کارمندها حقوق معینی دارند که از طریق عملیاتی، حساب میشود. معمولا معماری ای که در این حالت استفاده میشود(و غلط است)، به این صورت است که برای هر کارمند یک کلاس تعریف میکنیم که از کلاس Employee یا همان کارمند ارث بری میکند و از طریق یک کلاس که مسئول محاسبه حقوق است، حقوق ها را حساب میکنیم(گاهی حتی کلاس محاسبه حقوق را نمینویسیم و کدها را پشت فرم مینویسیم!!!)

چیزی شبیه به کد زیر:


    public class Salary
    {
        public long CalculateSalary(Employee employee)
        {
            if (employee is Developer)
            {
                return 10000 + 500 * 2;
            }
            else if (employee is ProjectManager)
            {
                return 10000 + 600 * 3;
            }
            else if (employee is Accountant)
            {
                return 10000 + 550 * 2;
            }
            else
            {
                return 0;
            }
        }
    }

حالا اگر بعدها بخواهیم یک کارمند جدید مثلا ‘منشی’ را به برنامه اضافه کنیم، باید این کلاس را تغییر بدهیم و یک if جدید به کلاس اضافه کنیم و این یعنی خطر خطا دادن برنامه بالا میرود.

راه درست این است که از  مفهوم Abstract استفاده کنیم. به این صورت که هر کارمندی خودش حقوقش را حساب کند و آنرا به کلاس Salary اعلام کند:


    public interface Employee
    {
        long CalculateSalary();
    }

    public class Developer : Employee
    {
        public long CalculateSalary()
        {
            return 1000 + 500 * 2;
        }
    }

        public long CalculateSalary(Employee employee)
        {
            return employee.CalculateSalary();
        }

به این ترتیب هر وقت کارمند جدیدی به سیستم اضافه شد، یک کلاس جدید را با اینترفیس Employee میسازیم و متد CalculateSalary را برای آن تعریف میکنیم.




کلمات کلیدی :

نظر بدهید