اگه تا حالا نفهمیدید دیزاین پترن ها چی هستند الان وقتشه که این پست رو خوب بخونید تا متوجه بشید . در این پست سعی میکنم که بگم چرا دیزاین پترن ها مهم هستند و چنتا مثال تو PHP براتون میگم و اینکه چرا و کی باید از اونها استفاده کنیم.
دیزاین پترن ها چی هستند ؟
دیزاین پترنها یه راه حل بهینه و قایل استفاده مجدد برای مسایل برنامه نویسی هستند که ما همه روزه باهاش سرکار داریم . دیزاین پترن یه کلاس یا کتابخانه نیست که ما به پروژمون اضافه کنیم , و خیلی بیشتر از اینهاست . یه قالب هست که باید تو شرایط درست به کار بگیریم. به زیان برنامه نویسی هم به کار میگرید اصلا ربط نداره . یه دیزاین پترن خوب باید تو خیلی از زبانهای برنامه نویسی و الیته نه همه قابل استفاده باشه البته به قابلیتهای زبانتون هم وابسته هست. دیزاین پترن مثل یه شمشیر دو لبه هست , آگه تو جای اشتباهی به کار گرفته بشه ممکنه خیلی مشکلات واستون پیش بیاره ولی در عوض تو جای درست و زمان درست میتونه نجات دهندتون هم باشه.
سه تا دیزاین پترن پایه وجود داره :
۱- structural ( ساختاری )
۲- creational ( اینو نتونستم فارسی چیزی پیدا کنم گفتم بنویسم خلقتی دیدم نمیشه، ساختنی بازم نمیشه , اگه چیزی یافتید بگید جایگزین کنم من فعلا از آفرینشی استفاده میکنم تو ادامه پست)
۳- behavioral ( رفتاری )
structural patterns اصولا بین موجودیت ها (entities) ارتباط برقرار میکنند و کارکردنشان با هم را راحت میکنند.
Creational patterns یه مکانیزم نمونه سازی ( instantiation mechanisms ) فراهم میکنند , ساخت شی هارو (objects) رو به صورتی که برای شرایط مناسب باشه رو راحت میکنند.
Behavioral patterns تو ارتباط برقرار کردن موجودیتها استفاده میشه و انعطاف پذیری و راحتی زیادی هنگام ارتباط بین موجودیتها بهتون میده.
چرا ما باید از اینها استفاده کنیم ؟
دیزاین پترنها بر اساس اصول هستند و یه راه حل خوب و تفکرشده برای مشکلات برنامه نویسی ارایه میدند . خیلی از برنامه نویسها با این مشکلات موجه شدهاند و یا استفاده از این ‘راه حلها’ مشکلشون رو حل کردند . اگه شما هم با این نوع مشکلات روبرو شدید چرا باید به دنبال کشف راه حل باشید در حالی که یه راه حل ثابت شده براش وجود داره ؟ (چرخ رو اختراع نکنیم)
مثال
فرض کنید میخواید دو تا کلاس رو که هر کدوم کار مختلفی رو بسته به شرایط انجام میدند رو با هم قاطی کنید . این دو تا کلاس تو جاهای مختلف تو سیستم فعلی استفاده شدند، و خیلی سخته که بخواید این دوتا کلاس رو حذف کنید و یه کلاس جدید بنویسید. اگه هم بخواید اینکا رو بکنید باید بعدش شروع کنید همه جاهایی که این کلاسها اضافه شدند رو تست کنید و اصولا این جور تغییرات که به خیلی کامپوننتها وابستس باعث میشه یه سری باگهای جدید متولد بشوند . به جای این کارها شما میتونید از استراتژی پترن و آداپتر پترن (strategy pattern و adapter pattern) که به راحتی این جور سناریوها رو واستون هندل میکنه استفاده کنید.
<?php class StrategyAndAdapterExampleClass { private $_class_one; private $_class_two; private $_context; public function __construct( $context ) { $this->_context = $context; } public function operation1() { if( $this->_context == "context_for_class_one" ) { $this->_class_one->operation1_in_class_one_context(); } else ( $this->_context == "context_for_class_two" ) { $this->_class_two->operation1_in_class_two_context(); } } }
خیلی آسونه , نه ؟ حالآ بیاید یه نیگا به استراتژی پترن بندازیم .
استراتژی پترن (Strategy Pattern)
استراتزی پترن یه دیزاین پترن رفتاری هست که به شما اجازه تصمیم گیری اینکه کدام مرحله از عملیات رو برنامه باید انجام بده رو میده البته بر اساس شرایطی که در حال اجرا پیش میاد. شما دو تا الگوریتم رو به صورت کپسوله شده تو دو تا کلاس میزارید و هنگام اجرا تصمیم میگیرید که حالا باید از کدام استراتژی استفاده کنید.
تو مثال بالا , استراتژی ما بر اساس مقداری هست که $context میگره و بر اساس اون کلاس نمونش ایجاد میشه (یعنی یک نمونه از کلاس ساخته میشه) . اگه شما class_one رو به context بدین اون از class_one یه نمونه میسازه و برعکس.
خوبه , ولی من کجا میتونیم از این استفاده کنم ؟
فرض کنید دارید یه کلاس مینویسید که هم آپدیت و هم ساخت کاربر رو بر عهده داره , این در هر حالت نیاز به یه سری ورودی مانند (name, address, mobile number, …) داره، ولی نسبت به شرایط داره از متدهای مختلفی برای آپدیت و ساخت استفاده میکنه. حالا، شما احتمالا از if-else برای هندل کردن این استفاده میکنید، خب اگه الان بخواید از این کلاس تو یه جا دیگه استفاده کنید؟ تو این حالت، شما باید اون if-else رو دوباره کلشو از اول بنویسید، راحتتر نمیشد اگه شما فقط شرایطتتون رو مشخص میکردید؟
<?php class User { public function CreateOrUpdate($name, $address, $mobile, $userid = null) { if( is_null($userid) ) { // it means the user doesn't exist yet, create a new record } else { // it means the user already exists, just update based on the given userid } } }
آداپتر پترن ( Adapter Pattern )
آداپتر پترن یه دیزاین پترن ساختاری هست که به شما اجازه میده که یه کلاس رو با یه اینترفیس تغییر هدف بدید , این کار به سیستم اجازه میده که با فرخوانی یه سری متدهای متفاوت از اون استفاده کنه.
این همچنین به شما اجازه تغییر یه سری ورودی هایی که از کلاس کلاینت دریافت میشه رو میده و سازگار با متدهای کلاس آداپته میکنه.
چه جوری میتونم از این استفاده کنم ؟
یه اصطلاح دیگه واسه کلاس آداپتر wrapper هست، که اساسا به شما اجازه میده یه عمل رو با یه کلاس ‘پنهان’ بکنید و این عملیات رو تو شرایط مناسب مجددا استفاده بکنید . یه مثال کلاسیک که میشه واسه این زد زمانی هست که شما یه کلاس دامنه (Domian class) برای جدول کلاسهاتون (table classes) میسازید . به جای اینکه برای هر جدول کلاسهاتون نمونه بسازید و یکی یکی متدهاشون رو فراخوانی کنید . شما می تونید همه این متدها رو تو یه متد آداپتر کلاس کپسوله کنید. این هم به شما اجازه میده که هر عملیاتی که میخواید انجام بدید و هم جلوگیری میکنه از اینکه بخواید کد تکراری بنویسید واسه همون عملیاتی که یه جا دیگه میخواید استفاده کنید.
این دو تا پیاده سازی رو مقایسه کنید :
روش بدون آداپتر
<?php $user = new User(); $user->CreateOrUpdate( //inputs ); $profile = new Profile(); $profile->CreateOrUpdate( //inputs );
اگه ما نیاز داشته باشیم همین کدها رو یه جا دیگه استفاده کنیم باید از اول همشو بنویسیم تا کارهایی که میخوایم انجام بشه.
روش بهتر
<?php $account_domain = new Account(); $account_domain->NewAccount( //inputs );
تو این وضعیت ما یه کلاس پوشاننده (wrapper) داریم که یه کلاس دامنه Account خواهد بود:
<?php class Account() { public function NewAccount( //inputs ) { $user = new User(); $user->CreateOrUpdate( //subset of inputs ); $profile = new Profile(); $profile->CreateOrUpdate( //subset of inputs ); } }
تو این روش ما یه کلاس Acoount داریم که همیشه میتونید ازش برای ساختن اکانت استفاده کنید همچنین میتونید کلاسهای دیگر و هم با این کلاس پوشش بدید.
فکتوری متد پترن (Factory Method Pattern)
فکتوری متد پترن یه دیزاین پترن آفرینشی هست که دقیقا کاری میکنه که اسمش میگه، یه کلاس هست که مانند کارخونه ای از نمونههای شی کار میکنه.
هدف اصلی این پترن اینه که کپسوله کردن روش آفرینشی هست که باعث میشه چنتا کلاس محدود به یک متد بشوند . با فراهم کردن شرایط و ورودیهای مناسب برای فکتوری متد , اون میتونه یه شی مناسب رو برای شما برگردونه.
کی میتونم از این استفاده کنم ؟
بهترین زمان برای استفاده از فکتوری متد پترن زمانیه که شما انواع مختلفی از یک موجودیت (entity) رو دارید. بیاین اینجوری در نظر بگیریم که شما یک کلاس دکمه (button) دارید، این کلاس انواع مختلفی داره، مانند ImageButton, InputButton و FlashButton . بر اساس مکان، شما باید از دکمههای مختلف استفاده بکنید، اینجا دقیقا همونجاست که شما باید از فکتوری متد پترن استفاده کنید تا دکمهها رو براتون ایجاد کنه.
خوب بیاین اون سه تا کلاس دکمه هامون رو بسازیم :
<?php abstract class Button { protected $_html; public function getHtml() { return $this->_html; } } class ImageButton extends Button { protected $_html = "..."; //This should be whatever HTML you want for your image-based button } class InputButton extends Button { protected $_html = "..."; //This should be whatever HTML you want for your normal button (<input type="button"... />); } class FlashButton extends Button { protected $_html = "..."; //This should be whatever HTML you want for your flash-based button }
حالا، بیاید فکتوری کلاسمون رو بسازیم :
<?php class ButtonFactory { public static function createButton($type) { $baseClass = 'Button'; $targetClass = ucfirst($type).$baseClass; if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) { return new $targetClass; } else { throw new Exception("The button type '$type' is not recognized."); } } }
حالا تو کدمون اینجوری میتونیم ازش استفاده کنیم :
$buttons = array('image','input','flash'); foreach($buttons as $b) { echo ButtonFactory::createButton($b)->getHtml() }
خروجی باید یه HTML از انواع دکمه های که داریم باشه . با این روش می تونید انواع دکمه ها رو تو هر شرایطی که میخواید استفاده کنید و فقط اون دکمه رو ایجاد کنید نه چیز بیشتر .
دکوریتور پترن (Decorator Pattern)
دکوریتور پترن یه دیزان پترن ساختاری هست که به ما این امکان رو میده که موقع اجرا برای هر کلاس یه سری رفتار جدید یا اضافی رو بسته به شرایط اضافه کنیم .
هدف اصلی اینه که ما بتوینم یه سری ویژگیها رو به یه نمونه خاص از کلاس اضافه کنیم در حالی که میشه تو اون زمان یه نمونه اصلی دیگه بسازیم که فقط قابلیتهای کلاس اصلی رو داره و اون قابلیتهای اضافی رو نداره. همچنین این کارم میتونید بکنیم که چنتا دکوریتور رو با هم قاطی کنیم و به یه نمونه اضافه کنیم، پس واسه هر نمونه دیگه لازم نیست فقط به یه دکوریتور گیر بدید. این پترن یه جایگزین برای زیر کلاس درست کردن هست که یه زیر کلاس میتونه مشخصات و عملیات هاش رو از کلاس والد به ارث ببره. ولی بر خلاف ساب کلاس که رفتارشو زمان کامپایل اضافه میکنه “دکوریتور کردن” به شما اجازه اینکه رفتارهایی رو در زمان اجرا بر اساس شرایطی خاص به نمونتون اضافه کنید رو میده.
برای پیاده سازی دکوریتور پترن، ما میتونیم از مرحلههای زیر پیروی کنیم:
- یه زیرکلاس از نوع “دکوریتور” کلاس از “کلاساصلی” درست کنید.
- تو دکریتور کلاستون یه اشارهگر (pointer) از کلاس اصلی به عنوان یه فیلد درست کنید.
- کلاس اصلی رو به سازنده (constructor) دکوریتور کلاستون پاس بدید تا اشارهگر کلاس اصلی تو دکوریتور کلاس تعریف بشه یا میشه گفت مقدار دهی اولیه (initialize) بشه.
- تو دکوریتور کلاس همه متدهای کلاس اصلی رو به اشاره گر کلاس اصلی هدایت (redirect) کنید.
- و تو دکوریتور کلاس همه اون متدهایی که میخواید تغییر داده بشه رو override کنید. override کردن یعنی نام متدتون تو زیر کلاس همون نام کلاس اصلی هست ولی کارایی که میکنه متفاوتتر از کلاس اصلی هست و حتی کلا میتونه یه کار دیگه بکنه.
این مراحل از مراحل تعریف شده در http://wikipedia.org/wiki/Decorator_pattern می باشد.
کی میتونم از این استفاده کنم ؟
بهترین مکان برای استفاده از دکوریتور پترن زمانیه که شما میخواید یه موجودیت تو شرایط خاص رفتار جدیدی از خودش داشته باشه. بیاین در نظر بگیرم شما یه لینک HTML دارید، یه لینک خروج از سیستم، که میخواید تو صفحه جاری یکمی متفاوت عمل کنه. برای این ما از دکوریتور پترن استفاده میکنیم.
اولش بیاین “دکوراسیون” های که نیاز خواهیم داشت رو تعریف کنیم.
- اگه تو صفحه خانه (home page) باشیم و وارد سیستم شده باشیم (logged in) ، میخوایم این لینک توی یه تگ h2 قرار بگیره.
- اگه تو صفحه های دیگه باشیم و وارد سیستم شده باشیم، میخوایم این لینک توی یه تگ underline قرار بگیره.
- اگه به سیستم وارد شدیم، میخوایم این لینک توی یه تگ strong قرار بگیره.
حالا که انواع دکوراسیون هامونو تعریف کردیم بریم یکم کد بزنیم.
<?php class HtmlLinks { //some methods which is available to all html links } class LogoutLink extends HtmlLinks { protected $_html; public function __construct() { $this->_html = "<a href=\"logout.php\">Logout</a>"; } public function setHtml($html) { $this->_html = $html; } public function render() { echo $this->_html; } } class LogoutLinkH2Decorator extends HtmlLinks { protected $_logout_link; public function __construct( $logout_link ) { $this->_logout_link = $logout_link; $this->setHtml("<h2>" . $this->_html . "</h2>"); } public function __call( $name, $args ) { $this->_logout_link->$name($args[0]); } } class LogoutLinkUnderlineDecorator extends HtmlLinks { protected $_logout_link; public function __construct( $logout_link ) { $this->_logout_link = $logout_link; $this->setHtml("<u>" . $this->_html . "</u>"); } public function __call( $name, $args ) { $this->_logout_link->$name($args[0]); } } class LogoutLinkStrongDecorator extends HtmlLinks { protected $_logout_link; public function __construct( $logout_link ) { $this->_logout_link = $logout_link; $this->setHtml("<strong>" . $this->_html . "</strong>"); } public function __call( $name, $args ) { $this->_logout_link->$name($args[0]); } }
ما بعدش میتونیم به شکل زیر ازش استفاده کنیم.
$logout_link = new LogoutLink(); if( $is_logged_in ) { $logout_link = new LogoutLinkStrongDecorator($logout_link); } if( $in_home_page ) { $logout_link = new LogoutLinkH2Decorator($logout_link); } else { $logout_link = new LogoutLinkUnderlineDecorator($logout_link); } $logout_link->render();
اینجا میتونید ببینید که ما چطوری اگه چنتا دکوریتور پترن نیاز داشته باشیم میتونیم ترکیبشون کنیم و استفاده کنیم . تا زمانی که همه دکوریتور پترنها از __call متد جادویی (magic function) استفاده میکنند ما می تونیم متدهای کلاس اصلی رو فراخوانی کنیم. اگه فرض کنیم ما در حال حاضر تو صفحه خانه هستیم و به سیستم وارد شدیم پس خروجی ما یه چیزی شبیه زیر میشه:
<strong><h2><a href="logout.php">Logout</a></h2></strong>
سینگلتون پترن (Singleton Pattern)
سینگلتون دیزاین پترن یه دیزاین پترن آفرینشی هست که باعث میشه همیشه یک نمونه از یک کلاس خاص در زمان اجرا داشته باشید و نه بیشتر و یه نقطه دسترسی سراسری (global) به همون یه نمونه برای ما فراهم میکنه.
این همین طور برای متغییرهایی که از نمونه سینگلتون استفاده میکنند یه نقطه “هماهنگی” نیز ایجاد میکنه و باعث میشه هر نمونه سینگلتون همیشه برای هر چیزی که اون رو فراخوانی میکنه مثل هم باشه.
کی میتونم از این استفاده بکنم ؟
اگه ما بخوایم یه نمونه از یه کلاس رو به یه کلاس دیگه پاس کنیم از سینگتون میتونید استفاده کنید تا از اینکه نمونه رو با سازنده یا یکی از آرگومانها به کلاس بفرستیم جلوگیری کنیم. فرض کنید یه کلاس نشست (Session) ساختید که کارش شبیه سازی آرایه سراسری _SESSION هست .تا زمانی که نیاز باشه این کلاس فقط یه بار نمونه سازی بشه پس ما میتونیم سینگلتون پترن رو پیاده سازی کنیم:
<?php class Session { private static $instance; public static function getInstance() { if( is_null(self::$instance) ) { self::$instance = new self(); } return self::$instance; } private function __construct() { } private function __clone() { } // any other session methods we might use ... ... ... } // get a session instance $session = Session::getInstance();
با انجام این کار ما به نمونه کلاس Session مون همه جا حتی تو کلاسهای دیگه هم میتونیم دسترسی داشته باشیم . داده ما همیشه تو همه فراخوانیها ثابت هست.
نتیجه گیری
خیلی دیزاین پترن دیگه وجود داره که ما تو این پست فقط یه اونهایی که زیاد استفاده میکنیم اشاره کردیم. اگه علاقه مندید که در مورد دیزاین پترن های دیگه مطالعه داشته باشید. صفحه ویکی پدیای دیزاین پترن ها خیلی اطلاعات داره و جامع و کامل هست.
آخرین جمله: همیشه زمانی که میخواید از دیزاین پترنها استفاده کنید مطمئن شوید که دارید یه مشکل رو درست حل میکنید. همین طور که قبلا گفتم دیزاین پترنها به مثال شمشیر دو لبه هستند، اگه تو شرایط غلط استفاده بشوند به طور بالقوه مشکلات رو بدتر میکنند ولی اگه درست استفاده شدند دیگه نمیتونید که ازشون صرفنظر کنید.
این پست ترجمه شده این پست هست، اگه جاهایی هست که به خاطر ترجمه فکر میکنید نامفهوم هست، میتونید از پست اصلی مطالعه کنید.
مهندس خیلی ممنون
خوب و مفید و جامع 🙂
سلام ممنون از پست خوبتون
توی این زمینه کناب فوق العاده Head First Design Patterns رو برای مطالعه ببشتر پیشنهاد میکنم.
فوق العاده بود 🙂 . یه دنیا ممنون
خیلی خوب بود امیر. دستت درد نکنه واقعن مفید بود برام
از این که این همه وقت گذاشتی تا مطلب جامعی را بنویسی ممنونم 🙂
[…] مطلب قبلی گفتیم که دیزاین پترن ها به سه دسته تقسیم می شوند و […]
خیلی خوشحالم که بالاخره یکی شروع کرد این موضوعو 🙂
امیر جان عالی!
من Swift کار می کنم اگه میشه مطالب بیشتری در مورد OOA, OOD, Design Pattern بزارید , دمتون گــــــــرم ☺️
ترجمتون خیلی مشکل داره دوست عزیز.
creational -> ساختی
structural -> ساختاری
ما که نفهمیدیم که شما از آدرس زیر کپی کرده اید و یا آنها از شما کپی …..
در هر صورت ….
http://alihossein.ir/%D9%85%D8%B9%D8%B1%D9%81%DB%8C-%D8%AF%DB%8C%D8%B2%D8%A7%DB%8C%D9%86-%D9%BE%D8%AA%D8%B1%D9%86-%D9%87%D8%A7%DB%8C-php/
در مورد این مقاله خاص، به زمان انتشارشون دقت میکردید متوجه میشدید که این مقاله سال ۹۳ منتشر شده و تاریخ اون مقاله ۲۰۱۶ هست.
نمیگیم که اونا کپی کردن شاید از یک مقاله انگلیسی ترجمه شده باشن.
به هر حال …
مرسی عزیزم عالی بود
اقا واقعا ممنون و دستتوت درد نکنه بسیار آموزنده و مفید و به زبان ساده بود
خسته نباشید