الگوهای طراحی مانند دستورالعملهایی برای توسعهدهندگان در برنامهنویسی شیگرا، و راهحلهایی برای حل مشکلات متداول در طراحی نرمافزار هستند. الگوهای طراحی برای اولین بار توسط یک گروه چهار نفره به نام GOF معرفی شدند. این گروه در کتابی به معرفی 23 الگوی طراحی پرداختند و این 23 الگو را در 3 دسته الگوهای ایجادی، الگوهای ساختاری و الگوهای رفتاری قرار دادند.
دیزاین پترنها، سلوشنهایی (راهکارهایی) بهینه و با قابلیت استفاده مجدد، برای مشکلات برنامهنویسی هستند که هر روز با آنها سروکار داریم. هر دیزاین پترن در واقع یک الگو است که باید در شرایط درستی پیادهسازی شود. دیزاین پترنها مختص زبان خاصی نیستند. یک دیزاین پترن خوب بسته به قابلیتهای زبانهای برنامهنویسی باید در اکثر زبانهای برنامهنویسی -نه همه- قابل پیادهسازی باشد. مهمتر از همه اینکه، هر دیزاین پترن میتواند مانند یک شمشیر دو لبه عمل کند و اگر در محل اشتباهی پیادهسازی شود، منجر به فاجعه و ایجاد مشکلات زیادی برای شما خواهد شد. با این حال، اگر در زمان و مکان درست پیادهسازی شود، کمک زیادی به توسعه اپلیکیشن شما خواهد کرد.
همانظور که در ابتدای پست گفتیم، دیزاین پترنها بر اساس هدف در 3 دسته طبقهبندی میشوند:
الگوهای ساختاری (Structural) به طور کلی با روابط بین موجودیتها سروکار دارند، این الگوها کار موجودیتها با یکدیگر را سادهتر میکنند.
الگوهای ایجادی (Creational) مکانیزمهای نمونهسازی (instantiation mechanisms) ارائه میکنند، ساخت آبجکتها را به روشی مناسب شرایط سادهتر میکنند. الگوهای ایجادی به جای اینکه مستقیما از آبجکتها نمونهسازی (instantiate) کنید، برای شما آبجکت میسازند. اینکار به برنامه شما انعطافپذیری بیشتری در تصمیمگیری درباره اینکه کدامیک از آبجکتها باید برای یک مورد خاص ایجاد شود را میدهد.
الگوهای رفتاری (Behavioral) در ارتباطات بین موجودیتها استفاده میشوند. ارتباط بین موجودیتها را سادهتر و انعطافپذیرتر میسازد. به طور کلی برای مدیریت الگوریتمها، روابط و مسئولیتها بین آبجکتها استفاده میشود.
در این مطلب الگوی طراحی زیر بررسی میشوند:
هدف از الگوی Factory، انتقال یک آبجکت و از بین بردن پیچیدگیهای آن است. ایده کلی این است که یک کلاس داریم و با یک متد نوع انتخاب را مشخص میکنیم و با یک متد دستور را اجرا میکنیم، مثلا کلاس مدیریت دیتابیس دارای یک متد برای انتخاب و تعیین نام درایور اتصال است، همچنین متد دیگری برای عملیات اتصال دارد و یک متد دیگر نیز اجرای کوئری و ... را انجام میدهد. نوع متدها استاتیک و درایور و کانکشن دارای فیلد خصوصی هستند.
به مثال بالا دقت کنید، بزرگترین مشکل کد بالا این است که اگر بخواهیم پروژه را بزرگتر و نوع دیتابیسها را افزایش دهیم، پیچیدهتر میشود، پس دستورات شرطی کد بالا را حذف کنید و کلاس Factory را اضافه کنید:
در نهایت با ساخت یک آبجکت به راحتی از آن استفاده کنید:
الگوی Abstract Factory مانند الگوی Factory است ولی از یک کلاس Abstract ارثبری میکنند.
الگوی Facade یک رابط مشترک برای آبجکتهای مختلف ایجاد میکند. در این الگو، کلاسهای مورد نظرتان را مینویسید تا از طریق یک کلاس که نقش واسطه را دارد، با این کلاسها کار کند. اسم این کلاس را Facade (هر اسمی دوست دارید) میگذاریم، فرض کنید کلاسی داریم که از سایر کلاسهای مورد نیازش آبجکت میسازد، با یک متد دادهها را گرفته و با یک حلقه foreach
پردازش میکند. در نهایت با کمک کلاسهای دیگر دستورات و اعمال مورد نظرش را روی آنها انجام میدهد. هنگام استفاده نیز ابتدا از کلاس Facade یک آبجکت میسازیم، سپس کارها را از طریق آبجکت مورد نظرمان انجام میدهیم.
برای اینکه نتوان از یک کلاس آبجکت ایجاد کرد، خصوصا برای استاتیک، راه اول private
کردن متد سازنده است و راه دوم ایجاد خطا در متد سازنده (throw new Exception(‘Error message’))
است، در هنگام استفاده نیز از بلاک try catch
استفاده میکنیم و حرفهایتر از حالت اول است.
الگو decorator یک مشکل دیگر برنامهنویسان را حل کرده است. وقتی نتوانیم به هر دلیلی یک کد را ویرایش کنیم، مثلا کلاسی داریم که بنا به هر دلیلی (مانند کپیرایت، فاینال بودن یا غیره) نمیتوانیم آن را ریفکتور (بازنویسی) کنیم، اگر بخواهیم متدی به آن کلاس اضافه کنیم، از این الگو استفاده میکنیم. نحوه پیادهسازی بدین صورت است که ابتدا یک کلاس ساخته و در این کلاس یک فیلد خصوصی تعریف میکنیم، آبجکت (این آبجکت از کلاس مورد نظر ما مشتق شده است) را به عنوان پارامتر متد سازنده کلاس در نظر میگیریم و در فیلد فوق قرار میدهیم. حالا متدی همنام با نام متد کلاس مورد نظرمان میسازیم و عملیات مورد نظر را انجام میدهیم (اغلب تغییرات روی دادهها است) و در صورت نیاز مقادیری را برمیگردانیم که البته در این متد میتوانیم متدهای دیگری را نیز برای اعمال تغییرات یا اجرای دستورات فراخوانی کنیم. به این تکنیک مضروبسازی (Containment) میگویند که شما یک آبجکت از یک کلاس دیگر میسازید و از خصوصیات public
آن استفاده میکنید و یه سری خصوصیات دیگر نیز اضافه میکنید و این در جاهایی که خصوصا وراثت امکانپذیر نیست، خیلی مفید است، به خصوص در غلبه کردن بر این محدودیت که یک آبجکت نمیتواند همزمان بیش از یک کلاس والد داشته باشد.
الگوی Observer نیز مشکل دیگری را حل میکند، فرض کنید میخواهید برخی از آبجکتها تغییرات آبجکتهای دیگر را دنبال (Track) کنند، یعنی اگر تغییراتی در یک آبجکت دیگر داده شود، متوجه شوند و کار دیگری را انجام دهند. این الگو از دو نوع آبجکت تشکیل شده است Observable و Observer که یکی Event تولید میکند و یکی به Eventها پاسخ میدهد. هرگاه وضعیت آبجکت observable تغییر کند، تمام آبجکتهای observer ثبت شده در آبجکت خود را فراخوانی میکند و میگوید وضعیت من تغییر کرده است. یکی از کاربردها گزارشگیری است که خطاها را اعلام میکند. باید برای استفاده از آن رابطی با نام observer را پیادهسازی کنیم که حاوی متد notify است. مثال استفاده از این الگو به صورت زیر است:
تمامی کلاسهای سینگلتون باید حداقل سه عنصر زیر را داشته باشند:
public
برای دسترسی به آبجکتبرخلاف کلاسهای معمولی، از کلاس سینگلتون نمیتوان ارثبری کرد.
یک مثال برای استفاده از این الگو، اتصالها به دیتابیس و تنظیمات و اطلاعات فایلهای config است. الگوی Singleton یکی از پرکاربردترین الگوها است و مشکلات زیادی را حل کرده است، هدف اصلی آن این است که در هر لحظه بیش از یک آبجکت از یک کلاس وجود نداشته باشد. برای پیادهسازی این روش، یک فیلد استاتیک خصوصی مثلا $instance
میسازیم و داخل متد سازنده آبجکت، یک شرط قرار میدهیم که اگر if(self::$instance)
آنگاه self::$instance = $this
و در بیرون شرط نیز همین مقدار return self::$instance
را برمی گردانیم. حالا حتی اگر یک میلیون آبجکت هم بسازیم، فقط یک نسخه وجود دارد و فقط همان آبجکت در حافظه قرار میگیرد، پس تا جایی که میشود باید از این الگو استفاده کنیم تا مصرف حافظه به شدت کاهش یابد.
الگوی Iterator کمک میکند تا مجموعهای از دادهها را راحتتر مدیریت کنید. رابط Iterator دارای 5 متد میباشد، rewind
ایندکس آرایه را به اولین خانه برمیگرداند، current
عنصر جاری را برمیگرداند، key
کلید (اندیس) عنصر جاری را برمیگرداند، next
اشارهگر (اندیس) را یک واحد به جلو میبرد، valid
میگوید که آیا عنصری (اندیسی) که اشارهگر به آن اشاره دارد، وجود داره یا نه. مثلا میتوانیم پستهای یک مطلب را به کلاسی بدهیم که از این رابط ارثبری کرده است و این کلاس پستها را پردازش میکند، به صورت زیر:
حالا فرض کنید کلاس مشابهی برای کامنتها داریم، و این کلاسها متدهای دیگری نیز دارند، نحوه استفاده از این کدها به صورت زیر است:
اگر بخواهیم از foreach
بتوانیم استفاده کنیم، باید رابط Iterator را پیادهسازی کرده باشیم. البته در آرایههای php این رابط به صورت پیشفرض پیادهسازی شده است اما میتوانیم ساختارش را تغییر بدهیم.
در الگوی Strategy یک رابط مینویسیم و برای هر حالت مثلا ارسال sms و mail و fax یک کلاس به ازای هر کدام قرار میدهیم که همه یک متد هم نام برای اجرای دستور دارند و انتخاب استفاده از کدام کلاس تعیین کننده ادامه داستان است.
الگوی ActiveRecord یک الگوی فوقالعاده کاربردی است. رکوردهای داخل دیتابیس به شکل آبجکت در میآیند، یعنی هر آبجکت ActiveRecord با یک رکورد در دیتابیس متناظر است. هدف اصلی این الگو این است که بتوانیم با روشی مانند $post->field
به پراپرتی برای ساخت یا تغییر دسترسی داشته باشیم که با مقداردهی یک پراپرتی، یک رکورد متناظر در جدول ایجاد میشود و با خواندن یک پراپرتی نیز رکورد متناظر برمیگردد، البته ساخت با متدی مانند save
انجام میشود ولی مقداردهی از روش گفته شده انجام میشود و روش پیادهسازی بدین صورت است که به نام هر تیبل (مفرد آن مثلا برای تیبل posts نام کلاس را post قرار میدهیم)، یک کلاس میسازیم، در این کلاس یک فیلد خصوصی قرار میدهیم و در متد سازنده نام ستونها را بعنوان اندیسهای این فیلد مقداردهی اولیه مینماییم، سپس با __GET
و __SET
مقداردهی و مقدارگیری را انجام میدهیم، حالا متدهای مورد نظرمان را به صورت استاتیک مینویسیم، مثلا متد findByPk($id)
یک رکورد خاص از جدول را با استفاده از id آن برمیگرداند که این کار را به این صورت انجام میدهد که هر ستون رکورد را در یک اندیس پراپرتی کلاس قرار میدهیم و سپس میتوانیم آن را فراخوانی کنیم.
الگوی Adapter یک آبجکت را به متدهای یک آبجکت دیگر تبدیل میکند. وقتی از api یک سرویس استفاده میکنیم و سپس به هر دلیلی مجبوریم از یک سرویسدهنده دیگر استفاده کنیم. فرض کنیم از سرویس Writely استفاده میکردیم و بعد از خرید آن توسط گوگل، مجبور خواهیم بود تا از گوگلداک استفاده کنیم، حالا اگر رابطی داریم که کلاس Writely از آن استفاده میکند، حالا باید از کلاس گوگل داک استفاده کنیم، برای استفاده از این کلاس نیز نیاز به یک کلاس از نوع Adapter داریم که این کلاس از رابط فوق استفاده میکند و در بدنهاش، دارای متدهایی است که هر کدام با اجرا بخشی از کد کلاس گوگلداک را اجرا میکنند، مثلا متد سازنده مسئول ساخت یک شی از کلاس فوق است که آن را در پراپرتی مربوط قرار میدهد و متدها نیز مقادیر مورد نظر را برمیگردانند.
در رابطه با دیزاینپترن، کتابهای بسیاری وجود دارد که از جمله بهترین آنها میتوان کتاب Design Patterns: Elements of Reusable Object-Oriented Software، کتاب Head First Design Patterns و کتاب Patterns of Enterprise Application Architecture را نام برد.