نموذج الإستراتيجية
يعتبر نموذج الإستراتيجية (نمط الاستراتيجية) (بالإنجليزية: 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
المثال التالي بلغة برمجة سي شارب (بالإنجليزية: #C):
public class StrategyPatternWiki
{
public static void Main(String[] args)
{
// Prepare strategies
var normalStrategy = new NormalStrategy();
var happyHourStrategy = new HappyHourStrategy();
var firstCustomer = new Customer(normalStrategy);
// Normal billing
firstCustomer.Add(1.0, 1);
// Start Happy Hour
firstCustomer.Strategy = happyHourStrategy;
firstCustomer.Add(1.0, 2);
// New Customer
Customer secondCustomer = new Customer(happyHourStrategy);
secondCustomer.Add(0.8, 1);
// The Customer pays
firstCustomer.PrintBill();
// End Happy Hour
secondCustomer.Strategy = normalStrategy;
secondCustomer.Add(1.3, 2);
secondCustomer.Add(2.5, 1);
secondCustomer.PrintBill();
}
}
class Customer
{
private IList<double> drinks;
// Get/Set Strategy
public IBillingStrategy Strategy { get; set; }
public Customer(IBillingStrategy strategy)
{
this.drinks = new List<double>();
this.Strategy = strategy;
}
public void Add(double price, int quantity)
{
this.drinks.Add(this.Strategy.GetActPrice(price * quantity));
}
// Payment of bill
public void PrintBill()
{
double sum = 0;
foreach (var drinkCost in this.drinks)
{
sum += drinkCost;
}
Console.WriteLine($"Total due: {sum}.");
this.drinks.Clear();
}
}
interface IBillingStrategy
{
double GetActPrice(double rawPrice);
}
// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice) => rawPrice;
}
// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice) => rawPrice * 0.5;
}
جافا Java
مثال بلغة برمجة جافا Java
import java.util.ArrayList;
interface BillingStrategy {
// Use a price in cents to avoid floating point round-off error
int getActPrice(int rawPrice);
// Normal billing strategy (unchanged price)
static BillingStrategy normalStrategy() {
return rawPrice -> rawPrice;
}
// Strategy for Happy hour (50% discount)
static BillingStrategy happyHourStrategy() {
return rawPrice -> rawPrice / 2;
}
}
class Customer {
private final List<Integer> drinks = new ArrayList<>();
private BillingStrategy strategy;
public Customer(BillingStrategy strategy) {
this.strategy = strategy;
}
public void add(int price, int quantity) {
this.drinks.add(this.strategy.getActPrice(price*quantity));
}
// Payment of bill
public void printBill() {
int sum = this.drinks.stream().mapToInt(v -> v).sum();
System.out.println("Total due: " + sum);
this.drinks.clear();
}
// Set Strategy
public void setStrategy(BillingStrategy strategy) {
this.strategy = strategy;
}
}
public class StrategyPattern {
public static void main(String[] arguments) {
// Prepare strategies
BillingStrategy normalStrategy = BillingStrategy.normalStrategy();
BillingStrategy happyHourStrategy = BillingStrategy.happyHourStrategy();
Customer firstCustomer = new Customer(normalStrategy);
// Normal billing
firstCustomer.add(100, 1);
// Start Happy Hour
firstCustomer.setStrategy(happyHourStrategy);
firstCustomer.add(100, 2);
// New Customer
Customer secondCustomer = new Customer(happyHourStrategy);
secondCustomer.add(80, 1);
// The Customer pays
firstCustomer.printBill();
// End Happy Hour
secondCustomer.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 = new Brake();
مثال بلغة جافا Java
/* Encapsulated family of Algorithms
* Interface and its implementations
*/
public interface IBrakeBehavior {
public void brake();
}
public class BrakeWithABS implements IBrakeBehavior {
public void brake() {
System.out.println("Brake with ABS applied");
}
}
public class Brake implements IBrakeBehavior {
public void brake() {
System.out.println("Simple Brake applied");
}
}
/* Client that can use the algorithms above interchangeably */
public abstract class Car {
private IBrakeBehavior brakeBehavior;
public Car(IBrakeBehavior brakeBehavior) {
this.brakeBehavior = brakeBehavior;
}
public void applyBrake() {
brakeBehavior.brake();
}
public void setBrakeBehavior(IBrakeBehavior brakeType) {
this.brakeBehavior = brakeType;
}
}
/* Client 1 uses one algorithm (Brake) in the constructor */
public class Sedan extends Car {
public Sedan() {
super(new Brake());
}
}
/* Client 2 uses another algorithm (BrakeWithABS) in the constructor */
public class SUV extends Car {
public SUV() {
super(new BrakeWithABS());
}
}
/* Using the Car example */
public class CarExample {
public static void main(final String[] arguments) {
Car sedanCar = new Sedan();
sedanCar.applyBrake(); // This will invoke class "Brake"
Car suvCar = new SUV();
suvCar.applyBrake(); // This will invoke class "BrakeWithABS"
// set brake behavior dynamically
suvCar.setBrakeBehavior( new Brake() );
suvCar.applyBrake(); // This will invoke class "Brake"
}
}
مراجع
- "The Strategy design pattern - Structure and Collaboration"، w3sDesign.com، مؤرشف من الأصل في 24 أغسطس 2017، اطلع عليه بتاريخ 12 أغسطس 2017.
- "The Strategy design pattern - Problem, Solution, and Applicability"، w3sDesign.com، مؤرشف من الأصل في 13 أغسطس 2017، اطلع عليه بتاريخ 12 أغسطس 2017.
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994)، Design Patterns: Elements of Reusable Object-Oriented Software، Addison Wesley، ص. 315ff، ISBN 0-201-63361-2، مؤرشف من الأصل في 15 ديسمبر 2019.
{{استشهاد بكتاب}}
: صيانة CS1: أسماء متعددة: قائمة المؤلفون (link) - "The Strategy design pattern - Structure and Collaboration"، w3sDesign.com، مؤرشف من الأصل في 24 أغسطس 2017، اطلع عليه بتاريخ 12 أغسطس 2017.
- http://www.mcdonaldland.info/2007/11/28/40/ نسخة محفوظة 2020-04-14 على موقع واي باك مشين.
- بوابة تقانة
- بوابة تقنية المعلومات
- بوابة علم الحاسوب