CodeGym /وبلاگ جاوا /Random-FA /الگوی طراحی استراتژی
John Squirrels
مرحله
San Francisco

الگوی طراحی استراتژی

در گروه منتشر شد
سلام! در درس امروز در مورد الگوی استراتژی صحبت خواهیم کرد. در درس های قبل به طور خلاصه با مفهوم وراثت آشنا شدیم. اگر فراموش کردید، به شما یادآوری می کنم که این اصطلاح به یک راه حل استاندارد برای یک کار برنامه نویسی رایج اشاره دارد. در CodeGym اغلب می گوییم که تقریباً هر سؤالی را می توانید در گوگل جستجو کنید. این به این دلیل است که وظیفه شما، هر چه که باشد، احتمالا قبلاً توسط شخص دیگری با موفقیت حل شده است. الگوها راه حل های آزمایش شده و واقعی برای رایج ترین کارها یا روش هایی برای حل موقعیت های مشکل ساز هستند. این‌ها مانند «چرخ‌هایی» هستند که نیازی به اختراع مجدد آن‌ها ندارید، اما باید بدانید که چگونه و چه زمانی از آنها استفاده کنید :) هدف دیگر از الگوها، ترویج معماری یکنواخت است. خواندن کد شخص دیگری کار آسانی نیست! همه کدهای متفاوتی می نویسند، زیرا یک کار را می توان به روش های مختلفی حل کرد. اما استفاده از الگوها به برنامه نویسان مختلف کمک می کند تا منطق برنامه نویسی را بدون غوطه ور شدن در هر خط کد (حتی زمانی که آن را برای اولین بار می بینند!) درک کنند. الگوی طراحی: استراتژی - 2تصور کنید که ما در حال نوشتن برنامه ای هستیم که به طور فعال با اشیاء Conveyance کار می کند. واقعاً مهم نیست که برنامه ما دقیقاً چه کاری انجام می دهد. ما یک سلسله مراتب کلاس با یک کلاس والد انتقال و سه کلاس فرزند ایجاد کرده‌ایم: سدان ، کامیون و F1Car .
public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
هر سه کلاس فرزند دو متد استاندارد را از والد به ارث می برند: go() و stop() . برنامه ما بسیار ساده است: اتومبیل های ما فقط می توانند به جلو حرکت کنند و ترمز بگیرند. در ادامه کار، تصمیم گرفتیم که روش جدیدی به خودروها بدهیم: fill() (به معنی پر کردن باک بنزین). ما آن را به کلاس مادر Conveyance اضافه کردیم :
public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }

   public void fill() {
       System.out.println("Refueling!");
   }
}
آیا واقعاً در چنین شرایط ساده ای می توان مشکلاتی ایجاد کرد؟ در واقع آنها قبلا ... الگوی طراحی: استراتژی - 3
public class Stroller extends Conveyance {

   public void fill() {

       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
برنامه ما اکنون دارای یک وسیله نقلیه (کالسکه کودک) است که به خوبی در مفهوم کلی قرار نمی گیرد. این می تواند پدال داشته باشد یا با رادیو کنترل شود، اما یک چیز قطعی است - جایی برای ریختن گاز نخواهد داشت. سلسله مراتب کلاس ما باعث شده است که متدهای رایج توسط کلاس هایی به ارث برده شوند که به آنها نیاز ندارند. در این شرایط چه باید بکنیم؟ خوب، ما می‌توانیم متد fill() را در کلاس Stroller لغو کنیم تا وقتی می‌خواهید کالسکه را سوخت‌گیری کنید هیچ اتفاقی نیفتد:
public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
اما اگر دلیل دیگری جز کد تکراری نباشد، به سختی می توان آن را یک راه حل موفق نامید. به عنوان مثال، اکثر کلاس ها از متد کلاس والد استفاده می کنند، اما بقیه مجبور می شوند آن را لغو کنند. اگر ما 15 کلاس داشته باشیم و باید رفتار را در 5-6 تای آنها لغو کنیم، تکرار کد بسیار گسترده خواهد شد. شاید رابط ها بتوانند به ما کمک کنند؟ به عنوان مثال، مانند این:
public interface Fillable {

   public void fill();
}
ما یک رابط Fillable با یک متد fill() ایجاد می کنیم . سپس، آن دسته از وسایل نقلیه ای که نیاز به سوخت گیری دارند، این رابط را اجرا می کنند، در حالی که سایر وسایل نقلیه (به عنوان مثال، کالسکه کودک ما) این کار را نمی کنند. اما این گزینه برای ما مناسب نیست. در آینده، سلسله مراتب طبقاتی ما ممکن است بسیار بزرگ شود (فقط تصور کنید که چند نوع انتقال مختلف در جهان وجود دارد). ما نسخه قبلی مربوط به وراثت را رها کردیم، زیرا نمی‌خواهیم متد fill() را چندین و چند بار لغو کنیم. حالا باید در هر کلاسی پیاده سازیش کنیم! و اگر 50 داشته باشیم چه؟ و اگر تغییرات مکرری در برنامه ما ایجاد شود (و این تقریباً همیشه برای برنامه های واقعی صدق می کند!)، ما باید در تمام 50 کلاس هجوم بیاوریم و به صورت دستی رفتار هر یک از آنها را تغییر دهیم. پس در نهایت در این شرایط چه باید بکنیم؟ برای حل مشکل خود، راه دیگری را انتخاب می کنیم. یعنی ما رفتار کلاس خود را از خود کلاس جدا خواهیم کرد. معنی آن چیست؟ همانطور که می دانید، هر شی دارای حالت (مجموعه ای از داده ها) و رفتار (مجموعه ای از روش ها) است. رفتار کلاس انتقال ما از سه روش تشکیل شده است: go() ، stop() و fill() . دو روش اول درست مثل خودشان خوب هستند. اما روش سوم را از کلاس Conveyance خارج می کنیم . این رفتار را از کلاس جدا می کند (به طور دقیق تر، تنها بخشی از رفتار را جدا می کند، زیرا دو روش اول در جایی که هستند باقی می مانند). پس متد fill() خود را کجا باید قرار دهیم ؟ چیزی به ذهنم نمیرسه :/ انگار دقیقا همون جاییه که باید باشه. ما آن را به یک رابط جداگانه منتقل می کنیم: FillStrategy !
public interface FillStrategy {

   public void fill();
}
چرا ما به چنین رابطی نیاز داریم؟ همه چیز سرراست است. اکنون می توانیم چندین کلاس ایجاد کنیم که این رابط را پیاده سازی کنند:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
ما سه استراتژی رفتاری ایجاد کردیم: یکی برای خودروهای معمولی، یکی برای خودروهای هیبریدی و دیگری برای خودروهای مسابقه ای فرمول 1. هر استراتژی الگوریتم سوخت‌گیری متفاوتی را پیاده‌سازی می‌کند. در مورد ما، ما به سادگی یک رشته را در کنسول نمایش می دهیم، اما هر روش می تواند حاوی منطق پیچیده ای باشد. بعدش باید چیکار کنیم؟
public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

}
ما از رابط FillStrategy خود به عنوان یک فیلد در کلاس مادر Conveyance استفاده می کنیم . توجه داشته باشید که ما پیاده سازی خاصی را نشان نمی دهیم - ما از یک رابط استفاده می کنیم. کلاس های خودرو به پیاده سازی های خاصی از رابط FillStrategy نیاز دارند :
public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}
بیایید ببینیم چه چیزی به دست آورده ایم!
public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
خروجی کنسول:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
عالی! فرآیند سوخت گیری همانطور که باید انجام می شود! به هر حال، هیچ چیز ما را از استفاده از استراتژی به عنوان پارامتر در سازنده باز نمی دارد! به عنوان مثال، مانند این:
public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
بیایید متد main() خود را اجرا کنیم (که بدون تغییر باقی می ماند). همین نتیجه را می گیریم! خروجی کنسول:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
الگوی طراحی استراتژی خانواده‌ای از الگوریتم‌ها را تعریف می‌کند، هر یک از آن‌ها را کپسوله می‌کند و تضمین می‌کند که قابل تعویض هستند. این به شما امکان می‌دهد الگوریتم‌ها را بدون توجه به نحوه استفاده مشتری از آن‌ها تغییر دهید (این تعریف که از کتاب «الگوهای طراحی اول سر» گرفته شده است، به نظر من عالی است). الگوی طراحی: استراتژی - 4ما قبلاً خانواده الگوریتم‌هایی را که به آن‌ها علاقه مندیم (روش‌های سوخت‌گیری خودروها) در رابط‌های جداگانه با پیاده‌سازی‌های مختلف مشخص کرده‌ایم. آنها را از خود ماشین جدا کردیم. حال اگر بخواهیم در الگوریتم سوخت گیری خاصی تغییراتی ایجاد کنیم، به هیچ وجه بر کلاس های خودروی ما تأثیری نخواهد گذاشت. و برای دستیابی به قابلیت تعویض، ما فقط باید یک متد تک تنظیم کننده را به کلاس Conveyance خود اضافه کنیم :
public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
اکنون می‌توانیم استراتژی‌ها را به سرعت تغییر دهیم:
public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
اگر کالسکه های کودک ناگهان با بنزین شروع به کار کنند، برنامه ما برای رسیدگی به این سناریو آماده خواهد بود :) و همین! شما یک الگوی طراحی دیگر را یاد گرفته اید که بدون شک هنگام کار بر روی پروژه های واقعی ضروری و مفید خواهد بود :) تا دفعه بعد!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION