CodeGym /مدونة جافا /Random-AR /نمط تصميم الاستراتيجية
John Squirrels
مستوى
San Francisco

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

نشرت في المجموعة
أهلاً! في درس اليوم، سنتحدث عن نمط الإستراتيجية. لقد تعرفنا في الدروس السابقة بشكل مختصر على مفهوم الميراث. في حالة نسيانك، سأذكرك أن هذا المصطلح يشير إلى حل قياسي لمهمة برمجة شائعة. في CodeGym، غالبًا ما نقول إنه يمكنك العثور على إجابة لأي سؤال تقريبًا على Google. وذلك لأن مهمتك، مهما كانت، ربما تم حلها بنجاح بواسطة شخص آخر. الأنماط هي حلول مجربة وحقيقية للمهام الأكثر شيوعًا، أو طرق لحل المواقف الإشكالية. إنها مثل "العجلات" التي لا تحتاج إلى إعادة اختراعها بنفسك، ولكنك تحتاج إلى معرفة كيف ومتى تستخدمها :) الغرض الآخر للأنماط هو تعزيز البنية الموحدة. قراءة كود شخص آخر ليست مهمة سهلة! كل شخص يكتب كودًا مختلفًا، لأن نفس المهمة يمكن حلها بعدة طرق. لكن استخدام الأنماط يساعد المبرمجين المختلفين على فهم منطق البرمجة دون الخوض في كل سطر من التعليمات البرمجية (حتى عند رؤيته لأول مرة!) اليوم ننظر إلى أحد أنماط التصميم الأكثر شيوعًا والذي يسمى "الاستراتيجية". نمط التصميم: الإستراتيجية - 2تخيل أننا نكتب برنامجًا سيعمل بشكل فعال مع كائنات النقل. لا يهم حقًا ما يفعله برنامجنا بالضبط. لقد أنشأنا تسلسلًا هرميًا للفئة مع فئة رئيسية واحدة للنقل وثلاث فئات فرعية: سيدان وشاحنة و 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() (بمعنى "ملء خزان الوقود"). أضفناها إلى فئة الوالدين النقل :
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() ‎ في فئة عربة الأطفال بحيث لا يحدث أي شيء عندما تحاول إعادة تزويد عربة الأطفال بالوقود:
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();
}
سنقوم بإنشاء واجهة قابلة للتعبئة باستخدام طريقة fill() واحدة . بعد ذلك، ستطبق وسائل النقل التي تحتاج إلى التزود بالوقود هذه الواجهة، في حين لن تفعل وسائل النقل الأخرى (على سبيل المثال، عربة الأطفال الخاصة بنا). لكن هذا الخيار لا يناسبنا. في المستقبل، قد ينمو التسلسل الهرمي الطبقي لدينا ليصبح كبيرًا جدًا (فقط تخيل عدد أنواع وسائل النقل المختلفة الموجودة في العالم). لقد تخلينا عن الإصدار السابق الذي يتضمن الوراثة، لأننا لا نريد تجاوز طريقة fill() عدة مرات. والآن علينا أن نطبقه في كل فصل! وماذا لو كان لدينا 50؟ وإذا تم إجراء تغييرات متكررة في برنامجنا (وهذا ينطبق دائمًا تقريبًا على البرامج الحقيقية!)، فسيتعين علينا الإسراع خلال جميع الفئات الخمسين وتغيير سلوك كل منها يدويًا. فماذا يجب علينا أن نفعل في هذه الحالة في النهاية؟ لحل مشكلتنا، سنختار طريقة مختلفة. أي أننا سنفصل سلوك فصلنا عن الفصل نفسه. ماذا يعني ذالك؟ كما تعلم، كل كائن له حالة (مجموعة من البيانات) وسلوك (مجموعة من الأساليب). يتكون سلوك فئة النقل الخاصة بنا من ثلاث طرق: go() و stop() و fill() . الطريقتان الأوليان جيدتان تمامًا كما هما. ولكننا سنقوم بنقل الطريقة الثالثة من فئة النقل . سيؤدي هذا إلى فصل السلوك عن الفصل (بشكل أكثر دقة، سيفصل جزءًا فقط من السلوك، حيث ستبقى الطريقتان الأوليان في مكانهما). فأين يجب أن نضع طريقة fill() الخاصة بنا؟ لا شيء يتبادر إلى ذهني :/ يبدو أنه المكان الذي يجب أن يكون فيه بالضبط. سنقوم بنقله إلى واجهة منفصلة: استراتيجية التعبئة !
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 الخاصة بنا كحقل في فئة النقل الأصلية. لاحظ أننا لا نشير إلى تنفيذ محدد، بل نستخدم واجهة. ستحتاج فئات السيارات إلى تطبيقات محددة لواجهة FileStrategy :
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());
   }
}
لنقم بتشغيل طريقتنا الرئيسية () (والتي تظل دون تغيير). نحصل على نفس النتيجة! إخراج وحدة التحكم:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
يحدد نمط تصميم الإستراتيجية عائلة من الخوارزميات، ويغلف كل منها، ويضمن أنها قابلة للتبديل. فهو يتيح لك تعديل الخوارزميات بغض النظر عن كيفية استخدامها من قبل العميل (يبدو هذا التعريف مأخوذًا من كتاب "Head First Design Patterns" ممتازًا بالنسبة لي). نمط التصميم: الإستراتيجية - 4لقد حددنا بالفعل مجموعة الخوارزميات التي نهتم بها (طرق تزويد السيارات بالوقود) في واجهات منفصلة مع تطبيقات مختلفة. لقد فصلناهم عن السيارة نفسها. الآن، إذا أردنا إجراء أي تغييرات على خوارزمية معينة للتزود بالوقود، فلن يؤثر ذلك على فئات سياراتنا بأي شكل من الأشكال. ولتحقيق قابلية التبادل، نحتاج فقط إلى إضافة طريقة ضبط واحدة إلى صنف النقل الخاص بنا :
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