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

يعتبر نموذج الإستراتيجية (نمط الاستراتيجية) (بالإنجليزية: 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) ومخطط التتابع

A sample UML class and sequence diagram for the Strategy design pattern.[4]

.في مخطط الصنف 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)‏.

مخطط الفئة

Strategy Pattern in UML

[5]

Strategy pattern in LePUS3 (legend)

مثال

#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

مخطط الصنف UML للمثال
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"
    }
}

مراجع

  1. ^ "The Strategy design pattern - Structure and Collaboration". w3sDesign.com. مؤرشف من الأصل في 2017-08-24. اطلع عليه بتاريخ 2017-08-12.
  2. ^ "The Strategy design pattern - Problem, Solution, and Applicability". w3sDesign.com. مؤرشف من الأصل في 2017-08-13. اطلع عليه بتاريخ 2017-08-12.
  3. ^ 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. مؤرشف من الأصل في 2019-12-15.{{استشهاد بكتاب}}: صيانة الاستشهاد: أسماء متعددة: قائمة المؤلفين (link)
  4. ^ "The Strategy design pattern - Structure and Collaboration". w3sDesign.com. مؤرشف من الأصل في 2017-08-24. اطلع عليه بتاريخ 2017-08-12.
  5. ^ http://www.mcdonaldland.info/2007/11/28/40/ نسخة محفوظة 2020-04-14 على موقع واي باك مشين.