نموذج الإستراتيجية

يعتبر نموذج الإستراتيجية (نمط الاستراتيجية)  أو (خطة العمل) (The policy pattern) واحداً من أنماط تصميم البرمجيات التصرفية (السلوكية) في مجال هندسة البرمجيات التي وضعها جماعة الأربعة في كتابهم المعروف (نماذج التصميم). يستعمل هذا النموذج (النمط) بالتحديد كي يتم اختيار الخوارزمية المناسبة أثناء تشغيل البرنامج . بدلا من تنفيذ خوارزمية واحدة مباشرة، الكود يستقبل التعليمات اثناء التشغيل  ليحدد الخوارزمية المناسبة لاستخدامها. بعبارة أخرى، فإن هذا النموذج يعرّف عددا من الخوارزميات ويجعلهم مغلفين  بحيث يمكن أن تحل إحداها محل الأخرى.

على سبيل المثال، يمكن تطبيق نموذج الإستراتيجية في حالة صنف (Class) يقوم بعملية التحقق من صحة البيانات المدخلة . حيث يمكن أن يكتب البرنامج بطريقة تجعله يختار الخوارزمية المناسبة تلقائيا بناءً على نوع البيانات المدخلة أو على مصدر هذه البيانات أو على أي عامل آخر. ما يهم هنا هو أن هذا العامل لا تتم معرفته، وبالتالي تحديد الخوارزمية، إلا أثناء تشغيل البرنامج.
يمكن استخدام خوارزميات التحقق (الاستراتيجيات)، المغلفة  بشكل منفصل عن كائن التحقق  ، من قبل كائنات التحقق الأخرى في مجالات مختلفة من النظام (أو حتى أنظمة مختلفة) دون تكرار الكود البرمجي .
عادة يقوم نمط الإستراتيجية بتخزين عنوان مرجعي  لبعض الكود في بنية بيانات  ويستردها . يمكن تحقيق ذلك من خلال آليات مثل مؤشر الدالة الأصلية  ، أو دالة من الصنف الأول  ، أو الاصناف أو مثيلات الصنف  في لغات البرمجة الكائنية  ، أو الوصول إلى التخزين الداخلي  لتطبيق اللغة  الخاص بالكود عبر الانعكاس .

هيكل

مخطط الصنف في لغة النمذجة الموحدة (UML) ومخطط التتابع 

.في مخطط الصنف UML  أعلاه، لا يطبق صنف السياق  خوارزمية مباشرة. بدلاً من ذلك، يشير السياقContext إلى واجهة الإستراتيجية Strategy interface لتنفيذ خوارزمية (()Strategy.algorithm)، مما يجعل السياقContext مستقلاً عن كيفية تطبيق  الخوارزمية. صنفا الإستراتيجية Strategy1  والاستراتيجية 2  ينفذا  واجهة الإستراتيجية ، أي تنفيذ (تغليف)  خوارزمية.
يعرض مخطط التتابع UML  تفاعلات وقت التشغيل  : يقوم كائن السياق  بتفويض  خوارزمية لكائنات إستراتيجية  مختلفة. أولاً، يستدعيContext السياق خوارزمية ()algorithm على كائن Strategy1 ، الذي ينفذ الخوارزمية ويعيدreturns النتيجة إلى السياقContext. بعد ذلك، يغير السياقContext استراتيجيته ويستدعي ()algorithm على كائن Strategy2 ، الذي ينفذ الخوارزمية ويعيد  النتيجة إلى السياق.

مخطط الفئة

مثال

#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();
    }
}

إستراتيجية ومبدأ مفتوح/مغلق 
إستراتيجية ومبدأ مفتوح مغلق  وفقًا لنمط الإستراتيجية، لا ينبغي أن تُتوارث  سلوكيات الصنف  . بدلاً من ذلك، يجب تغليفها  باستخدام الواجهات  . هذا متوافق مع مبدأ مفتوح / مغلق ، الذي يقترح أن الأصناف  يجب أن تكون مفتوحة للتمديد  ولكن مغلقة للتعديل .

كمثال، ضع في اعتبارك صنف السيارة. وظيفتان محتملتان للسيارة  هما الفرملة  والتسارع . نظرًا لأن سلوكيات التسارع  والفرملة  تتغير بشكل متكرر  بين النماذج ، فإن النهج المشترك  هو تنفيذ  هذه السلوكيات  في الأصنف الفرعية . هذا النهج  له عيوب كبيرة : يجب الإعلان  عن سلوكيات التسارع والفرامل  في كل طراز سيارة جديد . يزداد عمل إدارة  هذه السلوكيات  بشكل كبير مع زيادة عدد النماذج  ، ويتطلب تكرار الكود عبر النماذج . بالإضافة إلى ذلك، ليس من السهل تحديد الطبيعة الدقيقة  للسلوك  لكل نموذج  دون التحقق من الكود في كل نموذج.

يستخدم نمط الاستراتيجية  التركيب بدلاً من الوراثة . في نمط الاستراتيجية، يتم تعريف السلوكيات  على أنها واجهات منفصلة  وأصناف محددة  تنفذ هذه الواجهات . هذا يسمح بفصل  أفضل بين السلوك  والصنف  الذي يستخدم السلوك . يمكن تغيير السلوك  دون كسر الاصناف  التي تستخدمه، ويمكن للأصناف  التبديل بين السلوكيات  عن طريق تغيير التنفيذ المحدد  المستخدم دون الحاجة إلى أي تغييرات كبيرة في الكود . يمكن أيضًا تغيير السلوكيات  في وقت التشغيل  وكذلك في وقت التصميم . على سبيل المثال، يمكن تغيير سلوك فرامل كائن السيارة  من دالة الفرملة مع ABS  إلى دالة الفرملة  عن طريق تغيير عضو سلوك الفرامل brakeBehavior  إلى: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"
    }
}

مراجع 

برمجة كائنية التوجه
نماذج تصميم البرمجيات