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

نموذج الإستراتيجية
يعتبر نموذج الإستراتيجية (نمط الاستراتيجية) أو (خطة العمل) (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"
}
}
مراجع
برمجة كائنية التوجه
نماذج تصميم البرمجيات