نقش الگوی طراحی bridge
اگر بخواهیم وظیفه اصلی الگوی طراحی Bridge را در یک جمله بیان کنیم، باید
بگوییم: الگوی طراحی Bridge، پیاده سازی (Implementation) را از انتزاع
(Abstraction) جدا می کند و این امکان را فراهم می کند تا این دو بتوانند
به طور مستقل از هم عمل کنند. یکی از مهمترین کاربرد های الگوی طراحی
Bridge زمانی است که نسخه جدیدی از یک نرم افزار متولد می شود که قرار است
جایگزین نسخه قدیمی آن شود و قرار هست که نسخه قدیمی این نرم افزار نیز به
همان قدرت قبلی به کار خود ادامه دهد. تا مشتریان قبلی بتوانند همچنان از
نرم افزار خریداری شده خود استفاده کنند. کد برنامه نیازی ندارد تا تغییر
کند، زیرا ساختار کد به دلیل تطابق با انتزاع (Abstraction) موجود مشخص
است، ولی استفاده کننده خود باید مشخص کند که از کدام نسخه می خواهد در
برنامه استفاده کند.
تصویر کلی الگوی طراحی bridge
گسترش نسخه جدید Net FrameWork. را در نظر بگیرید که برای تهیه و اجرای C#
3.0 استفاده شده است.شما می توانید در هر برهه ای از زمان چندین نسخه از
فریمورک را در کامپیوتر خود داشته باشید و می توانید نسخه ای را که قصد
استفاده از آن را دارید انتخاب کنید. که این کار با استفاده از تخصیص یک
مسیر مشخص به نسخه های مختلف فریم ورک در سیستم عامل ویندوز صورت می گیرد.
تنظیم این مسیر در واقع پلی است بین آنچه برنامه ها از فریم ورک می خواهند
و نسخه واقعی که در جواب درخواست دریافت می کنند. در شکل زیر می بینید که
پنج نسخه از فریم ورک در کامپیوتر بارگذاری شده است و در شکل بعد از آن نیز
می بینید که نسخه ای که سیستم به سمت آن هدایت می شود نسخه 3.5 است که این
نسخه شامل کامپایلر C3 3.0 است.
طراحی الگوی طراحی bridge
ارث بری روشی معمول برای مشخص کردن پیاده سازی های مختلف انتزاع (یک کلاس
abstract) است. با این حال پیاده سازی ها به شدت به انتزاع (این کلاس های
abstract) متصل می شوند (اتصال سخت). و اعمال تغییرات در آنها به طور
مستقل دشوار است. وقتی بیش از یک نوع از انتزاع داشته باشیم، الگوی Bridge
جایگزینی برای ارث بری ارایه می دهد. نمودار UML زیر را در نظر بگیرید. دو
نوع پیاده سازی به نام های A و B ، اینترفیس Bridge را پیاده سازی می
کنند. در انتزاع (کلاس abstract) یک مشخصه از نوع Bridge وجود دارد، اما
به عبارت دیگر هیچ ارتباطی با پیاده سازی های مربوطه ندارد.
از این نمودار می توانیم دریابیم که نقش آفرینان الگوی Bridge عبارتند از:
- انتزاع (Abstraction)
رابط کاربری که استفاده کننده می بیند.
- عملیات (Operation)
متدی که توسط استفاده کننده فرخوانی می شود.
- پل (Bridge)
اینترفیسی که آن بخش هایی از انتزاع که ممکن است متفاوت باشد را تعریف
می کند.
- پیاده سازی A و پیاده سازی B
یک نوع پیاده سازی از اینترفیس Bridge
- پیاده سازی متد
متدی است در Bridge که از متد قرار داده شده در انتزاع (کلاس Abstract)
فراخوانی می شود.
همانگونه که در الگوی طراحی Decorator دیدیم، برای یک انتزاع (کلاس
Abstract) می توانیم پیاده سازی های متعددی داشته باشیم. پیاده سازی
های مرتبط با ارث بری، اگر تصمیم دارند با همان روش قدیمی ایجاد و استفاده
شوند، مجبور نیستند تا اینترفیس Bridge را پیاده سازی کنند. آنها فقط در
صورت نیاز به استفاده متقابل از پیاده سازی های جدید ، این کار را باید
انجام دهند (اینترفیس Bridge را پیاده سازی کنند).
پیاده سازی الگوی طراحی bridge
بار دیگر با یک مثال کوچک نظری (theoretical) شروع می کنیم. همانطور که
مشاهده می کنید، هر نمونه سازی از انتزاع خود یک پیاده سازی متفاوتی دارد.
و سپس، عملیات مربوطه فراخوانی می شود. هر فراخوانی به سمت یک پیاده سازی
از عملیات هدایت می شود، و نتیجه مربوط به آن فراخوانی هم نمایش داده می
شود. توجه داشته باشید که مجموعه پیاده سازی های نوشته شده، اینترفیس
Bridge را پیاده سازی می کنند و انتزاع (کلاس Abstract) آن را یکپارچه می
کند.
using System;
class BridgePattern
{
// Bridge Pattern Judith Bishop Dec 2006, Aug 2007
// Shows an abstraction and two implementations proceeding independently
class Abstraction
{
Bridge bridge;
public Abstraction (Bridge implementation)
{
bridge = implementation;
}
public string Operation()
{
return "Abstraction" + " <<< BRIDGE >>>> " + bridge.OperationImp();
}
}
interface Bridge
{
string OperationImp( );
}
class ImplementationA : Bridge
{
public string OperationImp ( )
{
return "ImplementationA";
}
}
class ImplementationB : Bridge
{
public string OperationImp ( )
{
return "ImplementationB";
}
}
static void Main ( )
{
Console.WriteLine("Bridge Pattern\n");
Console.WriteLine(new Abstraction (new ImplementationA( )).Operation( ));
Console.WriteLine(new Abstraction (new ImplementationB( )).Operation( ));
}
}
/* Output
Bridge Pattern
Abstraction <<< BRIDGE >>>> ImplementationA
Abstraction <<< BRIDGE >>>> ImplementationB
*/
کاربرد الگوی طراحی Bridge
الگوی طراحی بریج (Bridge) یک الگوی به ظاهر ساده اما بسیار قدرتمند است،
ما اگر پیاده سازی انجام شده در این الگو را در نظر بگیریم، با استفاده از
Bridge و کلاس Abstract می توانیم دومین پیاده سازی را نیز به ساختار
برنامه اضافه کنیم و به یک عمومیت و کلیت قابل توجهی نسبت به طرح اولیه
اصلی دست پیدا کنیم. استفاده مناسب از الگوی طراحی پل، در کار های گرافیکی
نمود پیدا می کند، آنجایی که نمایشگر های گوناگون، امکانات و سیستم های راه
اندازی متفاوتی دارند. اینها می توانند پیاده سازی الگوی Bridge
باشند و Bridge می تواند یک اینترفیس از امکانات و توانایی های اساسی
آنها باشد. استفاده کننده از الگو، کلاس Abstract را فراخوانی می کند تا
چیزی را نمایش دهد، و کلاس Abstract مشخصه های نمونه های ایجاد شده از
Bridge را بررسی کرده و بهترین آنها را برای انجام کار درخواست شده انتخاب
می کند.
از الگوی طراحی Bridge زمانی استفاده می کنیم که:
بتوانیم:
-
تشخیص دهیم، عملیاتی وجود دارد، که همیشه نیازی به اجرای آنها به یک روش
و متد ثابت نداریم و می توانیم نحوه اجرای آن را تغییر دهیم.
می خواهیم:
- نحوه پیاده سازی ها را از استفاده کننده های آن پنهان کنیم.
- از مقید کردن یک پیاده سازی به یک انتزاع (کلاس Abstract) به طور مستقیم
اجتناب کنیم.
- پیاده سازی انجام شده را تغییر دهیم، بدون این که نیاز به کامپایل مجدد
کلاس Abstract باشیم.
- قسمت های مختلف یک سیستم را در زمان اجرا پیوند داده و با هم ترکیب
کنیم.
مثال: OpenBook
الگوی طراحی Bridge، فرصت مناسبی در اختیار ما قرار می دهد تا
بتوانیم مثالی که در الگو های طراحی
Decorator و
Proxy داشتیم را گسترش داده و
تعامل الگوهای مختلف را با هم نشان دهیم. دوباره سیستم SpaceBook را در نظر می
گیریم. فرض می کنیم که برنامه نویس بر روی یک نسخه جدیدی از SpaceBook کار می
کند که نامش OpenBook است. مشخصه اصلی OpenBook این است که نیازی به اعتبار
سنجی رمز عبور در آن وجود ندارد. همچنین ایجاد نمونه ها باعث ایجاد مشکل
نخواهند شد، از آنجاییکه فرض بر این است که کاربران برنامه OpenBook به صورت
مستقیم به سیستم دسترسی پیدا خواهند کرد. بنابراین، OpenBook جایگزین الگوی
پروکسی در مثال SpaceBook شده و ساختار و نتیجه آن را تغییر نخواهد داد، و به
طور مطمئن قصد این است که کاربران هر دو سیستم بتوانند صفحات یکدیگر را ببینند.
بنابراین ما یک طراحی را در نظر گرفته ایم که در آن MyOneBook و MySpaceBook
باید اینترفیس عمومی را پیاده سازی کنند. ما این را اینترفیس Bridge می نامیم.
این پیاده سازی، یک نسخه مناسب از شیء book را نگهداری کرده و هر کدام از متد
های موجود در اینترفیس را به نسخه های موجود در آن شیء book هدایت می کند و سپس
برنامه اصلی تغییرات را منعکس کرده و نشان می دهد:
class BridgePattern : SpaceBookSystem
{
static void Main ()
{
MySpaceBook me = new MySpaceBook();
me.Add("Hello world");
me.Add("Today I worked 18 hours");
Portal tom = new Portal(new MyOpenBook("Tom"));
tom.Poke("Judith");
tom.Add("Judith","Poor you");
tom.Add("Hey, I'm also on OpenBook - it was so easy!");
}
}
کاربرانی مانند Judith می توانند از همان
SpaceBook قدیمی استفاده کنند، انگار که هیچ چیز تغییر نکرده است، اما Tom می
تواند از کلاس Abstract (Portal) استفاده کرده و از طریق Bridge به کلاس جدید
OpenBook متصل شده و به کلاس قدیمی SpaceBook دسترسی پیدا کنند. خروجی برنامه
مانند گذشته آغاز می شود اما در ادامه می بینیم که Tom نیازی به ثبت نام یا
تایید اعتبار ندارد.
Let's register you for SpaceBook
All SpaceBook names must be unique
Type in a user name: Judith
Type in a password: yey
Thanks for registering with SpaceBook
Welcome Judith. Please type in your password: yey
Logged into SpaceBook
======== Judith's SpaceBook =========
Hello world
Today I worked 18 hours
Tom poked you
Tom : hugged you
================================
======== Judith's SpaceBook =========
Hello world
Today I worked 18 hours
Tom poked you
Tom : hugged you
Tom : Poor you
================================
======== Tom-1's SpaceBook =========
Hey, I'm on OpenBook - it was so easy!
حال فرض می کنیم که برنامه نویس تصمیم گرفته است که با اضافه کردن شماره سریال به هر
شناسه کاربر از ایجاد
مغایرت جلوگیری کند. اگر کاربر بعدی Pat است، نامش Pat-2 خواهد بود. در
حال حاظر،خروجی Tom همچنان به SpaceBook اشاره دارد زیرا سیستم جدید نیز همچنان
از امکانات SpaceBook استفاده می کند. به مجرد اینکه OpenBook شخصی سازی
(Customize) گردید ، تغییر حاصل خواهد گردید. بخش جلویی در اینترفیس Bridge
محصور (Encapsulate) شده است.
interface Bridge
{
void Add(string message);
void Add(string friend, string message);
void Poke(string who);
}
Portal بسیار ساده است و وظایف مربوط به انتزاع
(Abstraction) را دقیقا مانند آنچه در کد اولیه در بالا دیدیم انجام می دهد. با
این حال سه عملیات به جای یک عملیات برای ادامه کار وجود دارد.
class Portal
{
Bridge bridge;
public Portal (Bridge aSpaceBook)
{
bridge = aSpaceBook;
}
public void Add(string message)
{
bridge.Add(message);
}
public void Add(string friend, string message)
{
bridge.Add(friend,message);
}
public void Poke(string who)
{
bridge.Poke(who);
}
}
پیاده سازی بعدی اینترفیس Bridge در این مثال، خود به
عنوان Proxy برای SpaceBook عمل می کند. بسیاری از مکانیسم های الگوی پروکسی از
بین رفته اند، و آنچه باقی مانده است، یک الگوی پروکسی هوشمند است که کار
آن نگهداری تعداد شماره سریال کاربران است. حال می توانید OpenBook را در حالت
اصلی ببینید.
public class MyOpenBook : Bridge
{
// Combination of a virtual and authentication proxy
SpaceBook mySpaceBook;
string name;
static int users;
public MyOpenBook (string n)
{
name = n;
users++;
mySpaceBook = new SpaceBook(name+"-"+users);
}
public void Add(string message)
{
mySpaceBook.Add(message);
}
public void Add(string friend, string message)
{
mySpaceBook.Add(friend, name + " said: "+message);
}
public void Poke(string who)
{
mySpaceBook.Poke(who,name);
}
}
در نظر داشته باشید که الگوی Bridge عملیاتی را که توسط همه اعضای کلاس Portal پشتیبانی خواهند شد را مشخص
و تعریف می کند. حال فرض کنید کلاس OpenBook بخواهد چند عملیات دیگری مانند
SuperPoke را به برنامه اضافه کند:
public void SuperPoke (string who, string what)
{
myOpenBook.Add(who, what+" you");
}
کلاس SuperPoke به صورتی نسبتا مبتدی در سطح بالای متد
Add پیاده سازی شده است، به دلیل اینکه، این یک ویژگی است که توسط SpaceBook
پشتیبانی می شود. اگر ما متد SuperPoke را در کلاس OpenBook قرار دهیم،
کامپایلر بدون گرفتن ایراد آن را قبول می کند. اما ما نمی توانیم آن را
فراخوانی کنیم، زیرا در برنامه اصلی Tom به شیء Portal اشاره می کند، در حالیکه
SuperPoke در Portal قرار ندارد. این مشکل را به دو روش می توانیم حل کنیم:
- عملیات جدیدی به کلاس Portal اضافه کنیم (Abstraction)، و با Bridge
کاری نداشته باشیم، بنابراین ، این تغییر روی بقیه پیاده سازی ها اثر
نخواهد گذاشت.
- اگر امکان تغییر در Portal وجود نداشته باشد، می توانیم یک متد از نوع
متد های Extension Method ایجاد کنیم تا بتوانیم کلاس مربوطه را گسترش داده
باشیم.
static class OpenBookExtensions
{
public static void SuperPoke (this Portal me, string who, string what)
{
me.Add(who, what+" you");
}
}
و به همان روشی که بقیه متد ها را فراخوانی می کنیم،
این متد را فراخوانی می کنیم.
tom.SuperPoke("Judith-1","hugged");
برنامه کامل برای کلاس OpenBook در زیر آورده شده است،
کلاس های SpaceBook و MySpaceBook که در بالا دیدیم، به هیچ وجه تغییر نکرده
است. در خروجی برنامه ، Judith از Open-book استفاده می کند، بنابراین نام آن
به Judith-1 تغییر یافته است، و Top نیز به عنوان دومین کاربر OpenBook، نامش
به Tom-2 تغییر یافته است.
using System;
using System.Collections.Generic;
// Bridge Pattern Example Judith Bishop Dec 2006, Aug 2007
// Extending SpaceBook with a second implementation via a Portal
// Abstraction
class Portal
{
Bridge bridge;
public Portal (Bridge aSpaceBook)
{
bridge = aSpaceBook;
}
public void Add(string message)
{
bridge.Add(message);
}
public void Add(string friend, string message)
{
bridge.Add(friend,message);
}
public void Poke(string who)
{
bridge.Poke(who);
}
}
// Bridge
interface Bridge
{
void Add(string message);
void Add(string friend, string message);
void Poke(string who);
}
class SpaceBookSystem
{
// The Subject
private class SpaceBook { ... }
// The Proxy
public class MySpaceBook { ... }
// A Proxy with little to do
// Illustrates an alternative implementation of the Bridge pattern
public class MyOpenBook : Bridge
{
// Combination of a virtual and authentication proxy
SpaceBook myOpenBook;
string name;
static int users;
public MyOpenBook (string n)
{
name = n;
users++;
myOpenBook = new SpaceBook(name+"-"+users);
}
public void Add(string message)
{
myOpenBook.Add(message);
}
public void Add(string friend, string message)
{
myOpenBook.Add(friend, name + " : "+message);
}
public void Poke(string who)
{
myOpenBook.Poke(who,name);
}
}
}
static class OpenBookExtensions
{
public static void SuperPoke (this Portal me, string who, string what)
{
me.Add(who, what+" you");
}
}
// The Client
class BridgePattern : SpaceBookSystem
{
static void Main ( )
{
Portal me = new Portal(new MyOpenBook("Judith"));
me.Add("Hello world");
me.Add("Today I worked 18 hours");
Portal tom = new Portal(new MyOpenBook("Tom"));
tom.Poke("Judith-1");
tom.SuperPoke("Judith-1","hugged");
tom.Add("Judith-1","Poor you");
tom.Add("Hey, I'm on OpenBook - it's cool!");
}
}
/* Output
======== Judith-1's SpaceBook =========
Hello world
===================================
======== Judith-1's SpaceBook =========
Hello world
Today I worked 18 hours
===================================
======== Judith-1's SpaceBook =========
Hello world
Today I worked 18 hours
Tom poked you
Tom : hugged you
===================================
======== Judith-1's SpaceBook =========
Hello world
Today I worked 18 hours
Tom poked you
Tom : hugged you
Tom : Poor you
===================================
======== Tom-2's SpaceBook =========
Hey, I'm on OpenBook - it's cool!
===================================
*/
منبع:
C# 3.0 Design Patterns - Ebook