يعتبر نموذج الإستراتيجية (نمط الاستراتيجية) (بالإنجليزية: Strategy Pattern) أو (خطة العمل) (The policy pattern) واحداً من أنماط تصميم البرمجيات التصرفية (السلوكية) في مجال هندسة البرمجيات التي وضعها جماعة الأربعة في كتابهم المعروف (نماذج التصميم).[1][2][3] يستعمل هذا النموذج (النمط) بالتحديد كي يتم اختيار الخوارزمية المناسبة أثناء تشغيل البرنامج (بالإنجليزية: runtime ). بدلا من تنفيذ خوارزمية واحدة مباشرة، الكود يستقبل التعليمات اثناء التشغيل (بالإنجليزية: run-time) ليحدد الخوارزمية المناسبة لاستخدامها. بعبارة أخرى، فإن هذا النموذج يعرّف عددا من الخوارزميات ويجعلهم مغلفين (بالإنجليزية: encapsulated) بحيث يمكن أن تحل إحداها محل الأخرى.
على سبيل المثال، يمكن تطبيق نموذج الإستراتيجية في حالة صنف (Class) يقوم بعملية التحقق من صحة البيانات المدخلة (بالإنجليزية: validation ). حيث يمكن أن يكتب البرنامج بطريقة تجعله يختار الخوارزمية المناسبة تلقائيا بناءً على نوع البيانات المدخلة أو على مصدر هذه البيانات أو على أي عامل آخر. ما يهم هنا هو أن هذا العامل لا تتم معرفته، وبالتالي تحديد الخوارزمية، إلا أثناء تشغيل البرنامج.
يمكن استخدام خوارزميات التحقق (الاستراتيجيات)، المغلفة (بالإنجليزية: encapsulated) بشكل منفصل عن كائن التحقق (بالإنجليزية: validating object) ، من قبل كائنات التحقق الأخرى في مجالات مختلفة من النظام (أو حتى أنظمة مختلفة) دون تكرار الكود البرمجي (بالإنجليزية: code duplication).
عادة يقوم نمط الإستراتيجية بتخزين عنوان مرجعي (بالإنجليزية: reference) لبعض الكود في بنية بيانات (بالإنجليزية: data structure) ويستردها (بالإنجليزية: retrieves ). يمكن تحقيق ذلك من خلال آليات مثل مؤشر الدالة الأصلية (بالإنجليزية: native function pointer) ، أو دالة من الصنف الأول (بالإنجليزية: first-class function) ، أو الاصناف أو مثيلات الصنف (بالإنجليزية: class instances) في لغات البرمجة الكائنية (بالإنجليزية: object-oriented) ، أو الوصول (بالإنجليزية: accessing )إلى التخزين الداخلي (بالإنجليزية: internal storage) لتطبيق اللغة (بالإنجليزية: the language implementation) الخاص بالكود عبر الانعكاس (بالإنجليزية: reflection).
هيكل
مخطط الصنف في لغة النمذجة الموحدة (UML) ومخطط التتابع
.في مخطط الصنف UML (بالإنجليزية: UML class diagram) أعلاه، لا يطبق صنف السياق (بالإنجليزية: Context class) خوارزمية مباشرة. بدلاً من ذلك، يشير السياقContext إلى واجهة الإستراتيجية Strategy interface لتنفيذ خوارزمية (()Strategy.algorithm)، مما يجعل السياقContext مستقلاً عن كيفية تطبيق (بالإنجليزية: implemented) الخوارزمية. صنفا الإستراتيجية Strategy1 (بالإنجليزية: Strategy1) والاستراتيجية 2 (بالإنجليزية: Strategy2) ينفذا (بالإنجليزية: implement) واجهة الإستراتيجية (بالإنجليزية: Strategy interface)، أي تنفيذ (تغليف) (بالإنجليزية: implement (encapsulate)) خوارزمية.
يعرض مخطط التتابع UML (بالإنجليزية: UML sequence diagram) تفاعلات وقت التشغيل (بالإنجليزية: run-time interactions) : يقوم كائن السياق (بالإنجليزية: Context object) بتفويض (بالإنجليزية: delegates) خوارزمية لكائنات إستراتيجية (بالإنجليزية: Strategy objects) مختلفة. أولاً، يستدعيContext السياق خوارزمية ()algorithm على كائن Strategy1 ، الذي ينفذ الخوارزمية ويعيدreturns النتيجة إلى السياقContext. بعد ذلك، يغير السياقContext استراتيجيته ويستدعي ()algorithm على كائن Strategy2 ، الذي ينفذ الخوارزمية ويعيد (بالإنجليزية: returns) النتيجة إلى السياق(بالإنجليزية: Context).
المثال التالي بلغة برمجة سي شارب (بالإنجليزية: #C):
publicclassStrategyPatternWiki{publicstaticvoidMain(String[]args){// Prepare strategiesvarnormalStrategy=newNormalStrategy();varhappyHourStrategy=newHappyHourStrategy();varfirstCustomer=newCustomer(normalStrategy);// Normal billingfirstCustomer.Add(1.0,1);// Start Happy HourfirstCustomer.Strategy=happyHourStrategy;firstCustomer.Add(1.0,2);// New CustomerCustomersecondCustomer=newCustomer(happyHourStrategy);secondCustomer.Add(0.8,1);// The Customer paysfirstCustomer.PrintBill();// End Happy HoursecondCustomer.Strategy=normalStrategy;secondCustomer.Add(1.3,2);secondCustomer.Add(2.5,1);secondCustomer.PrintBill();}}classCustomer{privateIList<double>drinks;// Get/Set StrategypublicIBillingStrategyStrategy{get;set;}publicCustomer(IBillingStrategystrategy){this.drinks=newList<double>();this.Strategy=strategy;}publicvoidAdd(doubleprice,intquantity){this.drinks.Add(this.Strategy.GetActPrice(price*quantity));}// Payment of billpublicvoidPrintBill(){doublesum=0;foreach(vardrinkCostinthis.drinks){sum+=drinkCost;}Console.WriteLine($"Total due: {sum}.");this.drinks.Clear();}}interfaceIBillingStrategy{doubleGetActPrice(doublerawPrice);}// Normal billing strategy (unchanged price)classNormalStrategy:IBillingStrategy{publicdoubleGetActPrice(doublerawPrice)=>rawPrice;}// Strategy for Happy hour (50% discount)classHappyHourStrategy:IBillingStrategy{publicdoubleGetActPrice(doublerawPrice)=>rawPrice*0.5;}
جافا Java
مثال بلغة برمجة جافا Java
importjava.util.ArrayList;interfaceBillingStrategy{// Use a price in cents to avoid floating point round-off errorintgetActPrice(intrawPrice);// Normal billing strategy (unchanged price)staticBillingStrategynormalStrategy(){returnrawPrice->rawPrice;}// Strategy for Happy hour (50% discount)staticBillingStrategyhappyHourStrategy(){returnrawPrice->rawPrice/2;}}classCustomer{privatefinalList<Integer>drinks=newArrayList<>();privateBillingStrategystrategy;publicCustomer(BillingStrategystrategy){this.strategy=strategy;}publicvoidadd(intprice,intquantity){this.drinks.add(this.strategy.getActPrice(price*quantity));}// Payment of billpublicvoidprintBill(){intsum=this.drinks.stream().mapToInt(v->v).sum();System.out.println("Total due: "+sum);this.drinks.clear();}// Set StrategypublicvoidsetStrategy(BillingStrategystrategy){this.strategy=strategy;}}publicclassStrategyPattern{publicstaticvoidmain(String[]arguments){// Prepare strategiesBillingStrategynormalStrategy=BillingStrategy.normalStrategy();BillingStrategyhappyHourStrategy=BillingStrategy.happyHourStrategy();CustomerfirstCustomer=newCustomer(normalStrategy);// Normal billingfirstCustomer.add(100,1);// Start Happy HourfirstCustomer.setStrategy(happyHourStrategy);firstCustomer.add(100,2);// New CustomerCustomersecondCustomer=newCustomer(happyHourStrategy);secondCustomer.add(80,1);// The Customer paysfirstCustomer.printBill();// End Happy HoursecondCustomer.setStrategy(normalStrategy);secondCustomer.add(130,2);secondCustomer.add(250,1);secondCustomer.printBill();}}
إستراتيجية ومبدأ مفتوح/مغلق
إستراتيجية ومبدأ مفتوح مغلق (بالإنجليزية: Strategy and open/closed principle) وفقًا لنمط الإستراتيجية، لا ينبغي أن تُتوارث (بالإنجليزية: inherited ) سلوكيات الصنف (بالإنجليزية: the behaviors of a class) . بدلاً من ذلك، يجب تغليفها (بالإنجليزية: encapsulated) باستخدام الواجهات (بالإنجليزية: interfaces) . هذا متوافق مع مبدأ مفتوح / مغلق (بالإنجليزية: OCP)، الذي يقترح أن الأصناف (بالإنجليزية: classes) يجب أن تكون مفتوحة للتمديد (بالإنجليزية: open for extension) ولكن مغلقة للتعديل (بالإنجليزية: closed for modification).
كمثال، ضع في اعتبارك صنف السيارة(بالإنجليزية: car class). وظيفتان محتملتان للسيارة (بالإنجليزية: functionalities ) هما الفرملة (بالإنجليزية: brake) والتسارع (بالإنجليزية: accelerate). نظرًا لأن سلوكيات التسارع (بالإنجليزية: accelerate ) والفرملة (بالإنجليزية: brake ) تتغير بشكل متكرر (بالإنجليزية: frequently) بين النماذج (بالإنجليزية: models)، فإن النهج المشترك (بالإنجليزية: common approach) هو تنفيذ (بالإنجليزية: implement ) هذه السلوكيات (بالإنجليزية: behaviors ) في الأصنف الفرعية (بالإنجليزية: subclasses). هذا النهج (بالإنجليزية: approach ) له عيوب كبيرة (بالإنجليزية: significant drawbacks): يجب الإعلان (بالإنجليزية: declared ) عن سلوكيات التسارع (بالإنجليزية: accelerate )والفرامل (بالإنجليزية: brake) في كل طراز سيارة جديد (بالإنجليزية: new Car model). يزداد عمل إدارة (بالإنجليزية: work of managing) هذه السلوكيات (بالإنجليزية: behaviors ) بشكل كبير مع زيادة عدد النماذج (بالإنجليزية: as the number of models increases) ، ويتطلب تكرار الكود (بالإنجليزية: duplicated )عبر النماذج (بالإنجليزية: models). بالإضافة إلى ذلك، ليس من السهل تحديد الطبيعة الدقيقة (بالإنجليزية: exact nature) للسلوك (بالإنجليزية: behavior ) لكل نموذج (بالإنجليزية: model ) دون التحقق من الكود في كل نموذج.
يستخدم نمط الاستراتيجية (بالإنجليزية: strategy pattern) التركيب بدلاً من الوراثة (بالإنجليزية: composition over inheritance). في نمط الاستراتيجية، يتم تعريف السلوكيات (بالإنجليزية: behaviors are defined) على أنها واجهات منفصلة (بالإنجليزية: interfaces ) وأصناف محددة (بالإنجليزية: specific classes) تنفذ (بالإنجليزية: implement )هذه الواجهات (بالإنجليزية: interfaces). هذا يسمح بفصل (بالإنجليزية: better decoupling) أفضل بين السلوك (بالإنجليزية: behavior ) والصنف (بالإنجليزية: class ) الذي يستخدم السلوك (بالإنجليزية: ). يمكن تغيير السلوك (بالإنجليزية: behavior ) دون كسر الاصناف (بالإنجليزية: breaking the classes) التي تستخدمه، ويمكن للأصناف (بالإنجليزية: classes ) التبديل بين السلوكيات (بالإنجليزية: switch between behaviors) عن طريق تغيير التنفيذ المحدد (بالإنجليزية: by changing the specific implementation) المستخدم دون الحاجة إلى أي تغييرات كبيرة في الكود (بالإنجليزية: Code). يمكن أيضًا تغيير السلوكيات (بالإنجليزية: Behaviors ) في وقت التشغيل (بالإنجليزية: run-time) وكذلك في وقت التصميم (بالإنجليزية: design-time). على سبيل المثال، يمكن تغيير سلوك فرامل كائن السيارة (بالإنجليزية: car object's brake behavior) من دالة الفرملة مع ABS (بالإنجليزية: ()BrakeWithABS) إلى دالة الفرملة (بالإنجليزية: ()Brake ) عن طريق تغيير عضو سلوك الفرامل brakeBehavior (بالإنجليزية: by changing the brakeBehavior member ) إلى:
brakeBehavior=newBrake();
مثال بلغة جافا Java
/* Encapsulated family of Algorithms * Interface and its implementations */publicinterfaceIBrakeBehavior{publicvoidbrake();}publicclassBrakeWithABSimplementsIBrakeBehavior{publicvoidbrake(){System.out.println("Brake with ABS applied");}}publicclassBrakeimplementsIBrakeBehavior{publicvoidbrake(){System.out.println("Simple Brake applied");}}/* Client that can use the algorithms above interchangeably */publicabstractclassCar{privateIBrakeBehaviorbrakeBehavior;publicCar(IBrakeBehaviorbrakeBehavior){this.brakeBehavior=brakeBehavior;}publicvoidapplyBrake(){brakeBehavior.brake();}publicvoidsetBrakeBehavior(IBrakeBehaviorbrakeType){this.brakeBehavior=brakeType;}}/* Client 1 uses one algorithm (Brake) in the constructor */publicclassSedanextendsCar{publicSedan(){super(newBrake());}}/* Client 2 uses another algorithm (BrakeWithABS) in the constructor */publicclassSUVextendsCar{publicSUV(){super(newBrakeWithABS());}}/* Using the Car example */publicclassCarExample{publicstaticvoidmain(finalString[]arguments){CarsedanCar=newSedan();sedanCar.applyBrake();// This will invoke class "Brake"CarsuvCar=newSUV();suvCar.applyBrake();// This will invoke class "BrakeWithABS"// set brake behavior dynamicallysuvCar.setBrakeBehavior(newBrake());suvCar.applyBrake();// This will invoke class "Brake"}}