در این مقاله در رابطه با یکی دیگر از دیزاینپترنهای مهم صحبت میکیم و با هم نحوه پیاده سازی و کاربرد آن را یاد میگیریم. در مقاله قبل در رابطه با Strategy Design Pattern صحبت کردیم و با کاربرد و نحوه پیاده سازی آن آشنا شدیم.
Bridge Design Pattern یکی دیگر از دیزاین پترنهای کاربردی است که از ترکیب استراتژی پترن ایجاد شده است Bridge در دسته دیزاینپترنهای ساختاری(Structural Patterns) قرار میگیرد و همانطور که میدانیم این دسته چگونگی جمع آوری اشیا و کلاسها را به ساختارهای بزرگتر توضیح می دهد در حالی که این ساختارها را انعطاف پذیر و کارآمد نگه می دارد.
الگوی پل، یک الگوی طراحی در مهندسی نرمافزار است که به معنای "جداسازی یک انتزاع از اجرای آن به طوری که این دو بتوانند به صورت مستقل تغییر پذیر باشند."میباشد. هنگامی که یک کلاس اغلب تغییر میکند، ویژگیهای برنامهنویسی شی گرا بسیار مفید خواهد بود، چرا که تغییرات در کد برنامه، میتواند با حداقل اطلاعات از برنامه صورت گیرد. زمانی که کلاسها و کاری که آنها انجام میدهند نسبت به یگدیگر تفاوتهای زیادی داشته باشد، از الگوی پل استفاده میشود. خود کلاس به عنوان انتزاع در نظر گرفته میشود و کاری که انجام میدهد در مرحله پیادهسازی آن است. (ویکی پدیا)
وقتی صحبت از الگوی طراحی Bridge می شود، الگوی استراتژی را به یاد می آوریم گرچه الگوی استراتژی یک الگوی رفتاری(Behavioral Patterns) است و الگوی Bridge یک الگوی ساختاری(Structural Patterns) است اما شباهت هایی با هم دارند. در الگوی استراتژی شما بین چند استراتژی انتخاب یکی را انتخاب میکنید ولی در الگوی Bridge شما دو مجموعه از استراتژی ها را دارید و هر استراتژی در مجموعه اول می تواند از هر استراتژی در مجموعه دوم استفاده کند. بنابراین به عبارت دیگر الگوی Bridge یک الگوی استراتژی است اما در مقیاس(scale) بزرگتر.
اولین مجموعه در این الگو abstraction (انتزاع) است در حالی که مجموعه دوم implementation (پیاده سازی و اجرا) است. abstraction نیازی به دانستن نحوه اجرا ندارند. فقط آنچه لازم است بداند این است که چگونه اجرای آن را فراخوانی کند. abstraction (که به آن interface نیز گفته میشود) یک لایه کنترل سطح بالا برای برخی موجودیتها است. این لایه قرار نیست به تنهایی کاری انجام دهد. باید کار را به لایه اجرا (implementation) تفویض کند.
نکته مهم: توجه داشته باشید که ما در مورد interfaces یا کلاسهای abstract از زبان برنامه نویسی صحبت نمی کنیم. اینها یکسان نیستند، هنگام صحبت در مورد برنامه های واقعی، abstraction را می توان با یک رابط کاربری گرافیکی (GUI) نشان داد، و implementation می تواند کد اصلی سیستم (API) باشد که لایه GUI در پاسخ به تعاملات کاربر فراخوانی می کند.
یکی از تصاویر جالب و مفهومی برای نشان دادن این الگوی طراحی در یک تصویر، از وبسایت refactoring به صورت زیر است.
Bridge یک الگوی طراحی ساختاری است که به شما امکان می دهد یک کلاس بزرگ یا مجموعه ای از کلاسهای نزدیک به هم را به دو سلسله مراتب جداگانه تقسیم کنید - انتزاع و اجرا (Abstraction و Implementation) - که می توانند مستقل از یکدیگر توسعه پیدا کنند.
تصویر زیر را درنظر بگیرید، یک کلاس شکل هندسی با یک جفت sub-class داریم: دایره و مربع.
حالا می خواهیم این سلسله مراتب کلاس را با ترکیب رنگها گسترش دهیم، بنابراین باید sub-classهایی با شکل قرمز و آبی ایجاد کنیم. از آنجا که قبلاً دو sub-class داریم، باید چهار ترکیب کلاس مانند BlueCircle و RedSquare ایجاد کنیم. تا به اینجای کار هیچ مشکلی وجود ندارد ولی اگر بخواهیم رنگ و یا شکل دیگری را اضافه کنیم؟ و این روند تغییر و افزایش رنگ و شکل افزایش داشته باشد تعداد sub-classهایی که باید ایجاد کنیم چقدر زیاد میشود؟!
مشکل فوق به این خاطر رخ میدهد که ما میخواهیم کلاس shape را در دو بعد نوع شکل و رنگ گسترش دهیم و همانطور که میدانیم این یک موضوع بسیار رایج در ارث بری در کلاسها است. Bridge Pattern یه جای ارث بری این مشکل را با ادغام اشیا حل میکند، به این صورت که ما دو بخش را به صورت یک سلسه مراتب Extract میکنیم به طوریکه به جای اینکه یک کلاس همه حالتها و رفتارها را داشته باشد به یک موضوع از سلسه مراتب جدید اشاره میکند. برای مثال ما رنگ را به دو sub-class به نامهای Blue و Red گسترش(Extract) میدهیم سپس کلاس Shape به یکی از کلاسهای موردنظر اشاره میکند. در این حالت یک پل بین Color و Shape شکل میگیرد و هر shape که داریم میتواند به رنگ موردنظر لینک شود بدون آنکه مجبور به تغییر کلاسهای موردنظر شویم. همچنین با اضافه شدن شکلها و رنگهای جدید هیچ مشکلی به وجود نمیآید و ما تغییری در بقیه به وجود نمیآوریم.
برای حل این مشکل از الگوی Bridge به صورت تصویر زیر استفاده میکنیم:
تصور کنید یک وبسایت با صفحات متنوع دارید که میخواهید امکان تغییر قالب را به کاربران بدهید، آیا برای اجرای این کار به تعداد صفحات از قالبها کپی میگیریم و روی هر مجموعه از صفحات یک قالب را اعمال میکنیم؟! دقیقا مانند مثال فوق اگر به یاد داشته باشید لازم نیست که یک کلاس تمام حالات و رفتارها را داشته باشد ما میتوانیم آن را Extract کنیم و از ترکیب اشیا این حالت را به بهترین شکل ممکن پیاده سازی کنیم. در این حالت ما باید شی را از پیاده سازی آن جدا کنیم و به جای وراثت از ترکیب اشیا استفاده کنیم.
در این مثال Abstraction(انتزاع)های ما همان قالبهای ما هستند که کاربر انتخاب میکند و Implementation (پیاده سازی و اجرا)های ما همان نحوه پیاده سازی و نمایش قالبها هستند.
ابتدا یک interface به اسم WebPage ایجاد میکنیم که همه صفحات از آن باید پیروی کنند و پس از آن برای انواع صفحات کلاسهای موردنظرمان را ایجاد میکنیم.
interface WebPage { public function __construct(Theme $theme); public function getContent(); } class About implements WebPage { protected $theme; public function __construct(Theme $theme) { $this->theme = $theme; } public function getContent() { return "About page in " . $this->theme->getColor(); } } class Careers implements WebPage { protected $theme; public function __construct(Theme $theme) { $this->theme = $theme; } public function getContent() { return "Careers page in " . $this->theme->getColor(); } }
حالا به پیاده سازی قالبها میپردازیم:
interface Theme { public function getColor(); } class DarkTheme implements Theme { public function getColor() { return 'Dark Black'; } } class LightTheme implements Theme { public function getColor() { return 'Off white'; } } class AquaTheme implements Theme { public function getColor() { return 'Light blue'; } }
بعد از پیاده سازی بخشهای Abstraction و Implementation به صورت زیر میتوانیم از آنها استفاده کنیم.
$darkTheme = new DarkTheme(); $about = new About($darkTheme); $careers = new Careers($darkTheme); echo $about->getContent(); // "About page in Dark Black" echo $careers->getContent(); // "Careers page in Dark Black"
همانطور که میبینید چقدر عالی این بخشها را از هم جدا کردیم و با اینکار توانستیم امکانی را فراهم کنیم که بدون تغییر کلاسها این ویژگیها را توسعه دهیم در واقع Bridge این امکان را میدهد که کلاسهای بزرگ را در ساختار سلسله مراتبی پیاده سازی کنیم که در این ساختار امکان تغییر و توسعه کلاسها به صورت مستقل وجود خواهد داشت و نگهداری از کدها را ساده تر میکند و در نتیجه هزینه ها کاهش پیدا میکند.