JavaRush /مدونة جافا /Random-AR /نمط التصميم "الاستراتيجية"

نمط التصميم "الاستراتيجية"

نشرت في المجموعة
مرحبًا! في المحاضرات السابقة، واجهنا بالفعل مفهومًا مثل "نمط التصميم". في حالة نسيانك، دعنا نذكرك: يشير هذا المصطلح إلى حل قياسي معين لمشكلة شائعة في البرمجة. نمط التصميم "الاستراتيجية" - 1في JavaRush غالبًا ما نقول إن الإجابة على أي سؤال تقريبًا يمكن البحث عنها في Google. لذلك، ربما يكون شخص ما قد نجح بالفعل في حل مشكلة مشابهة لمشكلتك. لذا، فإن الأنماط هي حلول تم اختبارها عبر الزمن وتم اختبارها من خلال الممارسة للمشكلات أو الطرق الأكثر شيوعًا لحل مواقف المشكلات. هذه هي "الدراجات" ذاتها التي لا تحتاج بأي حال من الأحوال إلى اختراعها بنفسك، ولكن عليك أن تعرف كيف ومتى يتم تطبيقها :) مهمة أخرى للأنماط هي جلب الهندسة المعمارية إلى مستوى واحد. قراءة كود شخص آخر ليست مهمة سهلة! الجميع يكتبها بشكل مختلف، لأن نفس المشكلة يمكن حلها بعدة طرق. لكن استخدام الأنماط يسمح للمبرمجين المختلفين بفهم منطق البرنامج دون الخوض في كل سطر من التعليمات البرمجية (حتى لو رأوه لأول مرة!) اليوم سنلقي نظرة على أحد الأنماط الأكثر شيوعًا والتي تسمى “الاستراتيجية”. نمط التصميم "الاستراتيجية" - 2لنتخيل أننا نكتب برنامجًا يعمل بنشاط مع كائن السيارة. في هذه الحالة، ليس من المهم بشكل خاص ما يفعله برنامجنا بالضبط. للقيام بذلك، أنشأنا نظام وراثة مع فئة أصل واحدة 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
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 منهم؟ وإذا تم إجراء تغييرات متكررة على برنامجنا (وفي البرامج الحقيقية، سيحدث هذا دائمًا تقريبًا!) ، فسيتعين علينا أن نركض بلساننا بين جميع الفئات الخمسين ونغير سلوك كل منها يدويًا. إذن ماذا يجب أن نفعل في النهاية؟ لحل مشكلتنا، دعونا نختار طريقا مختلفا. وهي أن نفصل سلوك فصلنا عن الفصل نفسه. ماذا يعني ذلك؟ كما تعلم، أي كائن له حالة (مجموعة من البيانات) وسلوك (مجموعة من الأساليب). يتكون سلوك فئة الآلة لدينا من ثلاث طرق 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 пит-стопа!
يحدد نمط الإستراتيجية مجموعة من الخوارزميات، ويغلف كل منها، ويضمن أنها قابلة للتبديل. يسمح لك بتعديل الخوارزميات بغض النظر عن استخدامها من جانب العميل (هذا التعريف مأخوذ من كتاب "استكشاف أنماط التصميم" ويبدو لي ناجحًا للغاية). نمط التصميم "الاستراتيجية" - 4لقد قمنا بعزل عائلة الخوارزميات التي تهمنا (أنواع سيارات التزود بالوقود) في واجهات منفصلة مع العديد من التطبيقات. لقد فصلناهم عن جوهر السيارة. لذلك، الآن، إذا كنا بحاجة إلى إجراء أي تغييرات على عملية التزود بالوقود هذه أو تلك، فلن يؤثر ذلك على فئات سياراتنا بأي شكل من الأشكال. أما بالنسبة لقابلية التبادل، ولتحقيقها نحتاج فقط إلى إضافة طريقة ضبط واحدة إلى صنفنا 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();
   }
}
إذا بدأت سيارات الأطفال التي تجرها الدواب فجأة بالامتلاء بالبنزين، فسيكون برنامجنا جاهزًا لمثل هذا السيناريو :) هذا كل شيء، في الواقع! لقد تعلمت نمط تصميم آخر ستحتاجه بلا شك وسيساعدك أكثر من مرة عند العمل في مشاريع حقيقية :) نراكم مرة أخرى!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION