سلام! در سخنرانی های قبلی، ما قبلاً با مفهومی به عنوان "الگوی طراحی" مواجه شده بودیم. اگر فراموش کردید، به شما یادآوری می کنیم: این عبارت به یک راه حل استاندارد معین برای یک مشکل رایج در برنامه نویسی اشاره می کند.
در JavaRush اغلب می گوییم که پاسخ تقریباً به هر سؤالی را می توان در گوگل جستجو کرد. بنابراین، احتمالاً کسی قبلاً مشکلی مشابه شما را با موفقیت حل کرده است. بنابراین، الگوها راه حل های آزمایش شده و آزمایش شده با زمان برای رایج ترین مشکلات یا روش های حل موقعیت های مشکل هستند. اینها همان "دوچرخه هایی" هستند که به هیچ وجه نیازی به اختراع خود ندارید، اما باید بدانید که چگونه و چه زمانی آنها را به کار ببرید :) یکی دیگر از وظایف الگوها این است که معماری را به یک استاندارد واحد برسانید. خواندن کد شخص دیگری کار آسانی نیست! هر کس آن را متفاوت می نویسد، زیرا همان مشکل را می توان به روش های مختلفی حل کرد. اما استفاده از الگوها به برنامه نویسان مختلف اجازه می دهد تا منطق برنامه را بدون غوطه ور شدن در هر خط کدی درک کنند (حتی اگر برای اولین بار آن را ببینند!) امروز به یکی از رایج ترین الگوها به نام "استراتژی" خواهیم پرداخت.
بیایید تصور کنیم که در حال نوشتن برنامه ای هستیم که به طور فعال با شی Car کار می کند. در این مورد، حتی مهم نیست که برنامه ما دقیقاً چه کاری انجام می دهد. برای انجام این کار، ما یک سیستم ارثی با یک کلاس والد
ما خانواده الگوریتمهایی را که مورد علاقه ما هستند (انواع خودروهای سوخترسان) را در رابطهای جداگانه با چندین پیادهسازی جدا کردهایم. ما آنها را از اصل ماشین جدا کرده ایم. بنابراین، در حال حاضر، اگر ما نیاز به ایجاد هر گونه تغییر در این یا آن فرآیند سوخت گیری داشته باشیم، این امر به هیچ وجه بر کلاس خودروهای ما تأثیر نخواهد گذاشت. در مورد قابلیت تعویض، برای دستیابی به آن، فقط باید یک متد setter را به کلاس خود اضافه کنیم
![الگوی طراحی "استراتژی" - 2](https://cdn.javarush.com/images/article/d36666bd-a280-4878-8d6f-ab5cff873cdd/800.jpeg)
Auto
و سه کلاس فرزند ایجاد کردیم Sedan
: Truck
و F1Car
.
public class Auto {
public void gas() {
System.out.println("Едем вперед");
}
public void stop() {
System.out.println("Тормозим!");
}
}
public class Sedan extends Auto {
}
public class Truck extends Auto {
}
public class F1Car extends Auto {
}
هر سه کلاس فرزند دو روش استاندارد را از والدین به ارث می برند - gas()
و stop()
برنامه ما بسیار ساده است: اتومبیل ها فقط می توانند به جلو حرکت کنند و ترمز کنند. در ادامه کار تصمیم گرفتیم روش جدیدی را به خودروها اضافه کنیم - fill()
(سوخت گیری). بیایید آن را به کلاس والد اضافه کنیم Auto
:
public class Auto {
public void gas() {
System.out.println("Едем вперед");
}
public void stop() {
System.out.println("Тормозим!");
}
public void fill() {
System.out.println("Заправить бензин!");
}
}
به نظر می رسد که در چنین شرایط ساده ای ممکن است مشکلات ایجاد شود؟ خوب، در واقع، آنها قبلاً به وجود آمده اند ... ![الگوی طراحی "استراتژی" - 3](https://cdn.javarush.com/images/article/a4a7d554-f812-47f0-8652-6d958446528d/800.jpeg)
public class ChildrenBuggies extends Auto {
public void fill() {
//хм... Это детский багги, его не надо заправлять :/
}
}
ماشینی در برنامه ما ظاهر شده است که در مفهوم کلی نمی گنجد - کالسکه کودکان. ممکن است با پدال یا رادیو کنترل باشد، اما یک چیز مطمئن است - جایی برای ریختن بنزین در آن وجود ندارد. طرح وراثت ما باعث شده است که روشهای رایج را حتی به کلاسهایی که به آنها نیاز ندارند، بدهیم. در چنین شرایطی چه باید بکنیم؟ خوب، برای مثال، میتوانید متد را fill()
در کلاس لغو کنید ChildrenBuggies
تا وقتی میخواهید باگی را سوختگیری کنید، هیچ اتفاقی نمیافتد:
public class ChildrenBuggies extends Auto {
@Override
public void fill() {
System.out.println("Игрушечную машину нельзя заправить!");
}
}
اما این راه حل را به سختی می توان موفق نامید، حداقل به دلیل تکرار کد. برای مثال، اکثر کلاسها از متدی از کلاس والد استفاده میکنند، اما کلاسهای دیگر مجبور به لغو آن هستند. اگر 15 کلاس داشته باشیم و در 5-6 کلاس مجبور شویم رفتار را نادیده بگیریم، تکرار کد بسیار گسترده خواهد شد. شاید رابط ها بتوانند به ما کمک کنند؟ مثلا این یکی:
public interface Fillable {
public void fill();
}
ما یک رابط Fillable
با یک روش ایجاد خواهیم کرد fill()
. بر این اساس، آن دسته از خودروهایی که نیاز به سوخت گیری دارند، این رابط را اجرا می کنند، اما خودروهای دیگر (مثلاً باگی ما) این کار را نمی کنند. اما این گزینه برای ما هم مناسب نیست. سلسله مراتب طبقاتی ما ممکن است در آینده به تعداد بسیار زیادی افزایش یابد (تصور کنید چند نوع خودرو در جهان وجود دارد). ما گزینه ارث بری قبلی را رها کردیم زیرا نمیخواستیم آن را لغو کنیم fill()
. در اینجا ما باید آن را در هر کلاس پیاده سازی کنیم! اگر 50 عدد از آنها داشته باشیم چه؟ و اگر تغییرات مکرر در برنامه ما ایجاد شود (و در برنامه های واقعی تقریباً همیشه این اتفاق می افتد!)، ما باید با زبان خود بین هر 50 کلاس بدویم و رفتار هر یک از آنها را به صورت دستی تغییر دهیم. پس در نهایت چه باید بکنیم؟ برای حل مشکلمان راه دیگری را انتخاب کنیم. یعنی بیایید رفتار کلاس خود را از خود کلاس جدا کنیم. چه مفهومی داره؟ همانطور که می دانید، هر شی دارای یک حالت (مجموعه ای از داده ها) و یک رفتار (مجموعه ای از روش ها) است. رفتار کلاس ماشین ما از سه روش تشکیل شده است gas()
- stop()
و fill()
. دو روش اول خوب است. اما متد سوم را به خارج از کلاس منتقل می کنیم Auto
. این جدایی رفتار از کلاس خواهد بود (به طور دقیق تر، ما فقط بخشی از رفتار را جدا می کنیم - دو روش اول باقی می مانند). روش خود را به کجا منتقل کنیم fill()
؟ هیچی بلافاصله به ذهنم نمیرسه :/ انگار کاملا سر جای خودش بود. ما آن را به یک رابط جداگانه منتقل می کنیم - FillStrategy
!
public interface FillStrategy {
public void fill();
}
چرا به این رابط نیاز داریم؟ ساده است. اکنون می توانیم چندین کلاس ایجاد کنیم که این رابط را پیاده سازی کنند:
public class HybridFillStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Заправляем бензином or электричеством на выбор!");
}
}
public class F1PitstopStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
}
}
public class StandartFillStrategy implements FillStrategy {
@Override
public void fill() {
System.out.println("Просто заправляем бензин!");
}
}
ما سه استراتژی رفتاری ایجاد کرده ایم - برای خودروهای معمولی، برای خودروهای هیبریدی و برای خودروهای فرمول 1. هر استراتژی یک الگوریتم سوختگیری جداگانه را پیادهسازی میکند. در مورد ما، این فقط خروجی کنسول است، اما ممکن است منطق پیچیده ای در داخل متد وجود داشته باشد. بعدش با این چیکار کنیم؟
public class Auto {
FillStrategy fillStrategy;
public void fill() {
fillStrategy.fill();
}
public void gas() {
System.out.println("Едем вперед");
}
public void stop() {
System.out.println("Тормозим!");
}
}
ما از رابط خود FillStrategy
به عنوان یک فیلد در کلاس والد استفاده می کنیم Auto
. لطفا توجه داشته باشید: ما پیاده سازی خاصی را مشخص نمی کنیم، بلکه از رابط استفاده می کنیم. و ما به پیاده سازی های خاصی از رابط FillStrategy
در کلاس های ماشین کودک نیاز داریم:
public class F1Car extends Auto {
public F1Car() {
this.fillStrategy = new F1PitstopStrategy();
}
}
public class HybridAuto extends Auto {
public HybridAuto() {
this.fillStrategy = new HybridFillStrategy();
}
}
public class Sedan extends Auto {
public Sedan() {
this.fillStrategy = new StandartFillStrategy();
}
}
بیایید ببینیم چه چیزی بدست آوردیم:
public class Main {
public static void main(String[] args) {
Auto sedan = new Sedan();
Auto hybrid = new HybridAuto();
Auto f1car = new F1Car();
sedan.fill();
hybrid.fill();
f1car.fill();
}
}
خروجی کنسول:
Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
عالی است، فرآیند سوخت گیری همانطور که باید کار می کند! به هر حال، هیچ چیز ما را از استفاده از استراتژی به عنوان پارامتر در سازنده باز نمی دارد! به عنوان مثال، مانند این:
public class Auto {
private FillStrategy fillStrategy;
public Auto(FillStrategy fillStrategy) {
this.fillStrategy = fillStrategy;
}
public void fill() {
this.fillStrategy.fill();
}
public void gas() {
System.out.println("Едем вперед");
}
public void stop() {
System.out.println("Тормозим!");
}
}
public class Sedan extends Auto {
public Sedan() {
super(new StandartFillStrategy());
}
}
public class HybridAuto extends Auto {
public HybridAuto() {
super(new HybridFillStrategy());
}
}
public class F1Car extends Auto {
public F1Car() {
super(new F1PitstopStrategy());
}
}
بیایید روش خود را اجرا کنیم main()
(بدون تغییر می ماند) و همان نتیجه را بگیریم! خروجی کنسول:
Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
الگوی Strategy خانواده ای از الگوریتم ها را تعریف می کند، هر یک از آنها را محصور می کند و تضمین می کند که آنها قابل تعویض هستند. این به شما امکان می دهد الگوریتم ها را بدون توجه به کاربرد آنها در سمت مشتری تغییر دهید (این تعریف از کتاب "کاوش الگوهای طراحی" گرفته شده است و به نظر من بسیار موفق است). ![الگوی طراحی "استراتژی" - 4](https://cdn.javarush.com/images/article/6d9ab390-1860-4470-ae46-384250d4d119/512.jpeg)
Auto
:
public class Auto {
FillStrategy fillStrategy;
public void fill() {
fillStrategy.fill();
}
public void gas() {
System.out.println("Едем вперед");
}
public void stop() {
System.out.println("Тормозим!");
}
public void setFillStrategy(FillStrategy fillStrategy) {
this.fillStrategy = fillStrategy;
}
}
اکنون میتوانیم استراتژیها را به سرعت تغییر دهیم:
public class Main {
public static void main(String[] args) {
ChildrenBuggies buggies = new ChildrenBuggies();
buggies.setFillStrategy(new StandartFillStrategy());
buggies.fill();
}
}
اگر ناگهان ماشین های کالسکه بچه ها شروع به پر شدن از بنزین کنند، برنامه ما برای چنین سناریویی آماده خواهد شد :) در واقع همه چیز همین است! شما الگوی طراحی دیگری را یاد گرفته اید که بدون شک به آن نیاز خواهید داشت و بیش از یک بار هنگام کار بر روی پروژه های واقعی به شما کمک می کند :) دوباره شما را می بینیم!
GO TO FULL VERSION