طراحی رفتارهای اردک
و اما چگونه مجموعه رفتارهای fly و quack را پیاده سازی کنیم ؟
از اینجا به بعد رفتارهای Duck در کلاس های جداگانه زندگی می کنند. کلاس هایی که یک Interface مشخصی را پیاده سازی می کنند. با این روش ، هر کدام از اردک ها نیازی نیست در مورد پیاده سازی جزییات رفتارهای خودشان بدانند.پیاده سازی آن به صورت زیر خواهد بود :
در طراحی جدید ، زیر مجموعه های کلاس Duck از Interface مربوط به FlyBehavior یا QuackBehavior استفاده می کنند و جزییات پیاده سازی هر کدام از رفتارها در کلاس های زیر مجموعه مربوط به Interface ها پیاده سازی می شود . در اینجا محدود به پیاده سازی رفتارها در کلاس های زیرمجموعه Duck (انواع اردک ها) نیستیم. مثال زیر را با طراحی بالا مطابقت می دهیم : یک SuperClass مربوط به Animal داریم و زیر مجموعه آن کلاس های Dog و Cat هستند.
کد نویسی با استفاده از کلاس های زیر مجموعه به صورت زیر خواهد بود :
Dog d=new Dog();
d.makeSound();
اما کد نویسی با استفاده از SuperType / Interface به صورت زیر خواهد بود :
Animal animal=new Dog();
animal.makeSound();
در تعریف بالا ، کلاس Dog راداریم که از حالت Polymorphism مربوط به کلاس Animal استفاده می کند و مزیتی که ایجاد می کند، می توانیم هنگام RunTime هر کدام از زیرمجموعه هارا به کلاس Animal انتصاب دهیم.
اصل طراحی : برروی Interface کد بزنید نه زیرمجموعه ها.
و اما از مثال بالا ، می توان طراحی رفتارهای اردک را هم متوجه شد. طراحی دو رفتار اردک به صورت زیر خواهد بود :
با طراحی فوق ، انواع اردک ها می توانند این دو رفتار را reuse کنند و می توانیم رفتارهای جدیدی را به این طراحی اضافه کنیم بدون اینکه نیاز به تغییر در کلاس های موجود بالا یا کلاس هایی که هم اکنون از این رفتارها استفاده می کنند ، باشد. بنابراین مافواید Reuse را به دست آورده ایم بدون اینکه بار اضافه Inheritance را متحمل شویم.
پیاده سازی کلاس Duck
دیاگرام کلاس به صورت زیر می باشد :
پیاده سازی کد کلاس بالا به صورت زیر می باشد :
public abstract class Duck(){
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();
public void performFly() { flyBehavior.fly(); }
public void performQuack() { QuackBehavior.quack(); }
public void swim () { System.out.println("All Ducks Float, even Decoys!"); }
و شبه کدهای مربوط به Fly به صورت زیر می باشد :
public interface FlyBehavior(){
public void fly(); }
public class FlyWithWings implements FlyBehavior {
public void fly() { print("I m flying..."); } }
public class FlyNoWay implements FlyBehavior {
public void fly() { print("I can t fly"); } }
و شبه کدهای پیاده سازی زیرمجموعه های Quack هم همانند کدهای Fly می باشد .
و شبه کد یکی از کلاس هایی که از Duck ارث بری می کنند به صورت زیر می باشد :
public class MallardDuck extends Duck {
public MallardDuck () {
quackBehavior=new Quack();
flyBehavior=new FlyWithWings(); }
public void display() { print("I m a real Mallard Duck"); } }
و نحوه تست کدهای بالا به صورت زیر می باشد :
Duck mallard=new MallardDuck(); mallard.performQuack(); mallard.performFly();
با اجرای کد بالا ، پیغام هایی که در متدهای Quack و FlyWithWings وجود داشت، چاپ خواهد شد.
افزودن رفتارها به صورت پویا
مرحله اول : افزودن دو متد جدید به کلاس پایه Duck به صورت زیر :
public void setFlyBehavior(FlyBehavior fb) { flyBehavior=fb; }
public void setQuackBehavior(QuackBehavior qb) { quackBehavior=qb; }
هر موقع می خواهیم در حین اجرا رفتارها را به اردک ها اضافه کنیم ، از این متدها استفاده می کنیم. و تست آن به صورت زیر می باشد :
Duck model=new MallardDuck(); model.setFlyBehavior(new FlyNoWay ()); model.performFly();
با افزودن رفتار پرواز هنگام اجرا ، توسط شبه کد بالا پیغام I can t fly چاپ می گردد.
ساختار کلی کلاس های استفاده شده در این مثال به صورت زیر نمایش داده می شود :
توجه : نمادهایی که در بالا استفاده شده است ، با استفاده از نمادهای uml می باشد.
در مثال بالا ، کلاس اردک از رابطه HAS-A استفاده کرده است. هر اردک یک FlyBehavior و یک QuackBehavior دارد. این تکنیک مهم Composition نام دارد. و در واقع ما از یک اصل طراحی استفاده می کنیم:
اصل طراحی : از Composition بیشتر کمک بگیرید تا Inheritance .
همانطور که مشاهده نمودید ،در ایجاد سیستم ها استفاده از Composition به شما انعظاف پذیری بیشتری می دهد. نه تنها اجازه میدهد مجموعه ای از الگوریتم ها را در یک سری از کلاس ها Encapsulate نمایید بلکه می توانید رفتارها را در زمان اجرا تغییر دهید.
در پترن استراتژی مجموعه ای از الگوریتم هارا داریم که هرکدام از آنهارا Encapsulate می نماید و قابلیت تغییر به آنها میدهد بدون اینکه نیاز باشد قسمت های دیگر تغییر یابد. استراتژی به الگوریتم ها اجازه میدهد به صورت مستقل از Client هایی که آنهارا استفاده می کنند ، تغییر یابد.
توجه کنید که فقط دانستن مفاهیم abstraction ، inheritance ، polymorphism و encapsulation (مبانی طراحی شی گرا) از شما یک طراح خوب نمی سازد .بلکه یک طراح خوب می داند که چگونه یک طراحی انعطاف پذیر را ایجاد کند که هم قابلیت نگهداری داشته باشد و هم با تغییرات سازگار باشد.
سوال : اگر نتوانستم برای مسایل نرم افزاری خود یکی از پترن ها را پیدا کنم چه کار کنم؟
جواب : یک سری اصول شی گرایی وجود دارد که زمینه ساز تشکیل پترن ها هستند و دانستن آنها به شما کمک می کند مسئله خود را حل کنید وقتی که نمی توانید پترن متناظر با مسئله خود را حل کنید.
اصول طراحی مطرح شده را با هم مرور می کنیم :
هرآنچه که تغییر می کند را Encapsulate نمایید.
بیشتر از composition بهره بگیرید تا inheritance.
برروی Interface کد بزنید نه زیرمجموعه ها.
تا اینجا چند ابزار به جعبه ابزار اصول طراحی ما افزوده شد .
قسمت اول از پترن استراتژی را می توانید در لینک زیر دنبال نمایید :