در برنامه نویسی بسیار مهم است که در زمان طراحی کد، قوانین استانداردسازی آن رعایت شود. استانداردسازی کدها باعث تولید کدهای بهینه، تمیز و خوانا میشود. شاید عده ای از برنامه نویسان (تعدادشان اصلا کم نیست!) تصور کنند صرف تحویل دادن یک برنامه که کار میکند (Working Software) کافی است. اما واقعیت این است که کدهای کثیف و غیر استاندارد معمولا با بهینه نبودن خود باعث میشوند که حتی کاربر برنامه از آن رضایت نداشته باشد. بنابراین در اغلب موارد کدهای کثیف و بی کیفیت حتی از دید کاربران عادی برنامهها پنهان نمیماند!
ضمنا تغییر کدهای کثیف و بی قانون بسیار مشکل، پر هزینه و زمان بر است. علاوه بر اینکه تغییر کدهای یک برنامه نویس کثیف برای برنامه نویسان همکار او دشوار است، برای خود او میتواند به یک کابوس واقعی تبدیل شود!
یکی از قوانین مهمی که در برنامه نویسی باید رعایت شود قوانین معروف به SOLID (بخوانید سالید) است. SOLID شامل پنج اصل است که همگی برای داشتن یک کد تمیز و استاندارد و حتی عقلانی (!) ضروری هستند. این پنج اصل عبارت اند از:
در این مطلب به آشنایی با اصل دوم یعنی اصل باز و بسته بودن میپردازیم.
اصل باز و بسته بودن یا اصل Open/Closed به نظر بسیاری، اساس برنامه نویسی شی گرا را تشکیل میدهد. رابرت مارتین (Robert C. Martin) که در بین برنامه نویسان به عمو باب (Uncle Bob) مشهور است با عبارت: “مهمترین اصل طراحی شی گرا” از این اصل یاد کرده است.
موجودیتهای برنامه که شامل کلاس ها، ماژول ها، توابع و… می را در نظر بگیرید. بر اساس اصل باز و بسته بودن هر کدام از این موجودیتها باید برای گسترده شدن (Extension) باز، اما برای تغییر کردن بسته باشند.
تصور کنید شخصی دست هایی شبیه قیچی داشته باشد! این دستها باید برای کارهای روزمره هر بار به طور کامل عوض شوند (که البته عوض کردن دست در دنیای واقعی کمی دور از ذهن به نظر میرسد!). درست مانند کلاسی که تمام کارایی را داخل خود پیاده کرده است و توسعه دهنده برای تغییر کارایی و حتی گسترش آن، محتویات کلاس را دستکاری میکند. اما روش درستتر این است که دستها به جای اینکه تبدیل به ابزار شوند، به ابزارهای مورد نیاز “مجهز شوند“. در این حالت دستها با گسترش کارایی یا Extend شدن تجهیز میشوند.
رعایت این اصل به شما کمک میکند مجبور نباشید برای تغییر یک کلاس، تمام کدهایی که از آن کلاس استفاده میکنند را تغییر دهید. به عنوان مثال به کد زیر توجه کنید:
public function drawAllShapes($shapes) { for ($i = 0; $i < count($shapes); $i++) { $shape = $shapes[$i]; switch ($shape->type) { case "square": $this->drawSquare($shape); break; case "circle": $this->drawCircle($shape); break; } } }
در این نمونه کد، بر اساس خاصیت type از اشیای نوع shape، در یک ساختار switch، شی مناسب بر اساس آن با تابع drawSquare() و تابع drawCircle() کشیده میشود. در این ساختار switch دو نوع مربع (Square) و دایره (Circle) بررسی شده است. حال اگر بخواهیم شکل دیگری غیر از مربع و دایره را بکشیم چه باید کرد؟
همانطور که حدس میزنید مجبوریم یک case دیگر برای شکل جدید در switch وارد کنیم. برای هر شکل جدیدی مجبوریم switch را بارها و بارها تغییر دهیم و به کد اصلی دست ببریم که این دقیقا بر خلاف اصل باز و بسته بودن است! بنابراین مشکل را به شکل زیر حل میکنیم:
interface Shape { public function draw(); } class Square implements Shape { public function draw() { // Draw Square Here echo "square"; } } class Circle implements Shape { public function draw() { // Draw Circle Here echo "circle"; } } class Draw { public function drawAllShapes($shapes) { for ($i = 0 ; $i < count($shapes) ; $i++) $shapes[$i]->draw(); } } $draw = new Draw(); $square = new Square(); $circle = new Circle(); $shapes = array($square , $circle); $draw->drawAllShapes($shapes);
اینبار یک Interface با نام Shape تعریف کرده ایم. یک متد draw() درون این Interface تعریف شده است. هر کلاسی که بخواهد نوع جدیدی از شکل را معرفی کند مجبور است این Interface را Implement کرده و متد draw() را به سبک خود پیاده سازی کند.
سپس برای ساختن حلقه نمایش اشکال در متد drawAllShapes() مثل سابق لیست اشکال را از طریق پارامترهای متد دریافت میکنیم. در داخل حلقه کافی است متد draw() از هر شی shape موجود در لیست shapes را فراخوانی کنیم تا شکل به سبک خودش نمایش داده شود!
در تعریف اول اصل باز و بسته بودن، برتراند میر (Bertrand Meyer) در کتاب با نام ساختار نرم افزار شی گرا (Object Oriented Software Construction) اینطور نوشته است:
اما مشکل این تعریف در آن است که به طور تنگاتنگی با Inheritance یا وراثت در رابطه است. بنابراین کلاسهای فرزند با ارث بردن از کلاس والد تا حد زیادی به جزئیات ساختار والد وابسته میشدند. این روند باعث ایجاد اتصال تنگاتنگ (Tight Coupling) میشد.
برای آشنایی بیشتر با این مشکل بهتر است در مورد اصول طراحی کد بیشتر صحبت کنیم. به طور کلی اصول طراحی کد بر اساس این دو اصل استوار است:
به طور خلاصه انسجام بالا یعنی این که متدهای یک کلاس همه در خدمت یک هدف باشند. اتصال کم یعنی ماژولهای یک برنامه نباید با یکدیگر در هم تنیده و بیش از حد متصل باشند. در صورت وجود چنین مشکلی در برنامه، اصطلاحا آن برنامه را (Tight Coupled) مینامند. در برنامههای در هم تنیده پس از ایجاد تغییر در یک کلاس مجبوریم کلاسهای وابسته و مربوط دیگر را هم تغییر دهیم. یعنی تغییر کوچکی در برنامه باعث بر زمین ریختن آوار برنامه و ارورهای مکرر میشود.
به اصل باز و بسته بودن بر میگردیم. گفتیم در صورتی که در این اصل از Inheritance استفاده کنیم، باعث ایجاد اتصال تنگاتنگ در برنامه میشویم. بنابراین عمو باب تعریف تازه ای از اصل باز و بسته بودن ارائه داد که به جای وراثت بر چندریختی (پلی مورفیسم) استوار است. حالا به جای سوپرکلاسها یا کلاسهای والد از Interface استفاده میکنیم. با این کار برای جایگزینی کد به جای درگیر شدن در جزئیات سوپرکلاس ها، تنها Interfaceها را پیاده سازی میکنیم و به گسترش برنامه میپردازیم. اما به هر حال انتخاب بین وراثت و چند ریختی برای رعایت اصل باز و بسته بودن انتخاب شماست!
با خواندن این مطلب مهمترین اصل برنامه نویسی شی گرا را یاد گرفتیم! اصل باز و بسته بودن یکی از اصول پنجگانه از SOLID است. با رعایت کردن این اصل، با تغییر یک کلاس یا ماژول، احتیاجی به تغییر دادن همه کدهایی که از آن استفاده کرده اند نداریم. آیا شما تجربه ای از رعایت کردن یا نکردن این اصل داشته اید؟ استفاده از این اصل چه کمکی به شما برای داشتن کدهای بهتر کرده است؟ از خواندن نظرات شما خوشحال میشویم!
فرض کنید ما یه متغییر ورودی میگیرم و بر اساس اون تشخصی میدیم کدوم shape رو بکشیم مثلا اگر ۱ بود square اگر ۲ بود circle اونوقت چطور میشه ؟
باز ما همون switch case یا if else ها رو خواهیم داشت
خب اینجا کلا مساله ای که مطرح کردید شرطی هست و ناچارید از switch یا if استفاده کنید. اما برای رعایت کردن این اصول بهتره که یه کلاس جدا به عنوان input detector برای تشخیص رشته ورودی و مپ کردنش به شکل بگذارید تا کدهای کمتری نیاز به تغییر دوباره داشته باشه.