الفرق بين المراجعتين لصفحة: «Design Patterns/strategy»
لا ملخص تعديل |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:نمط الخطة Strategy}}</noinclude> | <noinclude>{{DISPLAYTITLE:نمط الخطة Strategy}}</noinclude> | ||
نمط الخطة هو نمط تصميم سلوكي يسمح لك بتحديد عائلة من الخوارزميات ووضع كل واحدة منها داخل فئة منفصلة، ومن ثم جعل كائناتها تقبل التبادل (interchangeable). | نمط الخطة (Strategy) هو نمط تصميم سلوكي يسمح لك بتحديد عائلة من الخوارزميات ووضع كل واحدة منها داخل فئة منفصلة، ومن ثم جعل كائناتها تقبل التبادل (interchangeable). | ||
== المشكلة == | == المشكلة == | ||
[[ملف:dpst.problem.png|تصغير|(ش.1) شيفرة المستكشف Navigator صارت فوضوية.]] | [[ملف:dpst.problem.png|تصغير|(ش.1) شيفرة المستكشف Navigator صارت فوضوية.]] |
مراجعة 13:48، 4 مارس 2020
نمط الخطة (Strategy) هو نمط تصميم سلوكي يسمح لك بتحديد عائلة من الخوارزميات ووضع كل واحدة منها داخل فئة منفصلة، ومن ثم جعل كائناتها تقبل التبادل (interchangeable).
المشكلة
لنفرض أنك كتبت برنامج ملاحة للمسافرين والسياح يدور حول خريطة جميلة تساعدة المستخدمين على توجيه أنفسهم بسرعة في أي مدينة. وقد وجدت أن إحدى أكثر المزايا طلبًا من المستخدمين هي الإنشاء الآلي للمسار بحيث يدخل المستخدم العنوان ليرى أسرع مسار إليه معروضًا على الخريطة. وفي أول إصدار لم يدعم البرنامج إنشاء المسارات سوى للمسارات التي على الشوارع فقط، وقد أسعد ذلك المستخدمين المسافرين بسياراتهم، ثم في التحديث التالي أضفت دعم إنشاء المسارات على طرق المشاة، ثم في الذي يليه أضفت إنشاء المسارات بالمواصلات العامة. ووجدت نفسك بعد ذلك تخطط لإضافة إمكانية إنشاء مسارات للدراجين، وبعده إمكانية إنشاء مسارات آلية لكل المزارات السياحية في المدينة.
ورغم أن البرنامج ناجح من المنظور التجاري، إلا أن الجزء الفني سبب لك متاعب إذ يتضاعف حجم الفئة الرئيسية للمستكشف كلما أضفت خوارزمية مسار جديدة، إلى أن تصل إلى نقطة لا يمكن صيانة الشيفرة فيها أو تعديلها. ذلك أن أي تغيير في إحدى الخوارزميات سواء كان مجرد تصحيح بسيط أو تعديل طفيف في درجة شارع ما (Street score) سيؤثر في الفئة كلها مما يزيد فرصة حدوث خطأ داخل الشيفرة العاملة. إضافة إلى هذا فإن العمل الجماعي صار غير مريح، فزملاءك الذين وظفتهم مع أول إصدارة ناجحة صاروا يشتكون أنهم ينفقون وقتًا طويلًا في حل مشاكل الدمج (merge conflicts)، وصار إدخال مزايا جديدة يتطلب منك تغيير نفس الفئة الضخمة تلك، مما يعارض الشيفرة التي كتبها غيرك.
الحل
يقترح نمط الخطة أن تأخذ فئة تنفذ شيئًا محددًا بطرق كثيرة مختلفة، ثم تستخرج خوارزمياتها إلى فئات منفصلة تدعى الاستراتيجيات أو الخطط (strategies). ويجب أن تحتوي الفئة الأصلية التي تدعى بالسياق (context) على حقل لتخزين مرجع لواحدة من الخطط، ويفوض السياق العمل إلى كائن الخطة المرتبط به بدلًا من تنفيذه بنفسه. ولا يكون السياق مسؤولًا عن اختيار خوارزمية مناسبة للمهمة، بل يمرر العميلُ الخطةَ المرغوب فيها إلى السياق، وفي الواقع فإن السياق لا يعرف الكثير عن الخطط، فهو يعمل معها جميعًا من خلال نفس الواجهة العامة التي تكشف أسلوبًا واحدًا فقط لبدء الخوارزمية المغلفة داخل الخطة المختارة. ويصبح السياق بتلك الطريقة مستقلًا عن الخطط الحقيقية، لذا تستطيع إضافة خوارزميات جديدة أو تعدل الموجودة فعلًا دون تغيير شيفرة السياق أو الخطط الأخرى.
يمكن أن تُستخرج كل خوارزمية تخطيط لمسار داخل البرنامج في مثالنا إلى فئتها الخاصة مع أسلوب buildRoute
، ويقبل الأسلوب مكان بداية ووجهة ثم يعيد مجموعة من النقاط المرحلية (checkpoints) على المسار. وكل فئةِ تحديدِ مسارٍ يمكنها بناء مسار مختلف رغم إعطائها نفس الوسائط (arguments) إلا أن فئة المستكشف الأساسية لا تهتم أي خوارزمية تم اختيارها طالما أن مهمتها الأساسية هي إخراج مجموعة نقاط مرحلية على الخريطة. والفئة بها أسلوب لتبديل استراتيجية التوجيه النشطة كي يتمكم عملاؤها -كالأزرار في واجهة المستخدم- من تبديل سلوك التوجيه الحالي بواحد غيره.
مثال واقعي
تخيل أنك تريد الوصول إلى المطار، يمكنك الوصول بالحافلة أو التاكسي أو حتى على الدراجة، تلك خطط تنقلك، وتستطيع اختيار إحداها وفقًا لعوامل مثل الميزانية أو الوقت.
البنية
- يحتفظ السياق Context بمرجع إلى إحدى الخطط الحقيقية ويتواصل مع هذا الكائن من خلال واجهة الخطة فقط.
- تكون واجهة الخطة Strategy مشتركة لكل الخطط الحقيقية، وتصرح عن أسلوب يستخدمه السياق لتنفيذ الخطط.
- تستخدم الخطط الحقيقية Concrete Strategies أشكالًا مختلفة من الخوارزمية التي يستخدمها السياق.
- يستدعي السياق أسلوب التنفيذ على كائن الخطة المرتبط به في كل مرة يحتاج إلى تشغيل الخوارزمية، ولا يعرف السياق أي نوع من الخطط يعمل معه أو كيف تُنفًّذ الخوارزمية.
- ينشئ العميل Client كائن خطة محدد ويمرره إلى السياق، ويكشف السياق محدِّدًا يسمح للعملاء باستبدال الخطة المرتبطة بالسياق أثناء وقت التشغيل.
مثال توضيحي
يستخدم السياق في هذا المثال عدة خطط لتنفيذ عدة عمليات حسابية.
// تصرح واجهة الخطة عن العمليات المشتركة لكل الإصدارات المدعومة لخوارزمية ما
// ويستخدم السياق تلك الواجهة لاستدعاء الخوارزمية التي تحددها الخطط
// الحقيقية.
interface Strategy is
method execute(a, b)
// تطبق الخطط الحقيقيةُ الخوارزميةَ مع اتباع واجهة الخطة الأساسية.
// داخل السياق interchangeable وتجعل الواجهة تلك الخطط تبادلية.\
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b
class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b
class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b
// يحدد السياق الواجهة التي يطلبها العملاء.
class Context is
// يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
// ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
private strategy: Strategy
// يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
// كي يمكن تبديل الخطة أثناء التشغيل.
method setStrategy(Strategy strategy) is
this.strategy = strategy
// يفوض السياق بعض العمل إلى كائن الخطة بدلًا من استخدام عدة إصدارات
// للخوارزمية بنفسه.
method executeStrategy(int a, int b) is
return strategy.execute(a, b)
// تلتقط شيفرة العميل خطة حقيقية وتمررها إلى السياق، وينبغي أن يكون
// العميل مدركًا للفروق بين الخطط من أجل اتخاذ القرار المناسب .
class ExampleApplication is
method main() is
Create context object.
Read first number.
Read last number.
Read the desired action from user input.
if (action == addition) then
context.setStrategy(new ConcreteStrategyAdd())
if (action == subtraction) then
context.setStrategy(new ConcreteStrategySubtract())
if (action == multiplication) then
context.setStrategy(new ConcreteStrategyMultiply())
result = context.executeStrategy(First number, Second number)
Print result.
قابلية التطبيق
- استخدم نمط الخطة حين تريد استخدام عدة صور من خوارزمية داخل كائن وتكون قادرًا على التبديل من خوارزمية لأخرى أثناء التشغيل.
يسمح لك نمط الخطة بتغيير سلوك الكائن أثناء التشغيل بشكل غير مباشر من خلال ربطه بكائنات فرعية مختلفة تستطيع تنفيذ مهام فرعية محددة بطرق مختلفة.
- استخدم نمط الخطة حين يكون لديك فئات كثيرة متشابهة لا تختلف إلا في طريقة تنفيذ سلوك ما.
يسمح لك نمط الخطة باستخراج السلوك المتغير إلى هرمية فئات منفصلة وجمع الفئات الأصلية في فئة واحدة، ومن ثم تقليل الشيفرة المكررة.
- استخدم النمط لعزل منطق العمل لفئة ما عن تفاصيل تطبيق الخوارزمية الذي قد لا يكون مهمًا بقدر سياق المنطق نفسه.
يسمح لك نمط الخطة بعزل الشيفرة والبيانات الداخلية واعتماديات العديد من الخوارزميات من بقية الشيفرة، ويحصل عدة عملاء على واجهة بسيطة لتنفيذ الخوارزميات والتبديل بينهم أثناء التشغيل.
- استخدم النمط حين تحتوي فئتك على معامِل شرطي كبير يبدل بين الأشكال المختلفة لنفس الخوارزمية.
يسمح لك نمط الخطة باستخدام شَرطية كتلك من خلال استخراج جميع الخوارزميات إلى فئات منفصلة، تطبق جميعها نفس الواجهة، ويفوض الكائن الأصلي التنفيذ إلى أحد تلك الكائنات بدلًا من تطبيق كل أشكال الخوارزمية.
كيفية الاستخدام
- داخل فئة السياق، حدد الخوارزمية التي تميل إلى التغير، وقد تكون شرطية كبيرة تختار وتنفذ شكلًا من نفس الخوارزمية أثناء التشغيل.
- صرح عن واجهة الخطة المشتركة لجميع أشكال الخوارزمية.
- استخرج جميع الخوارزميات واحدة واحدة إلى فئاتها الخاصة، يجب أن تستخدم تلك الخوارزميات واجهة الخطة.
- داخل فئة السياق، أضف حقلًا لتخزين مرجع إلى كائن خطة، ووفر محدِّدًا لاستبدال قيم ذلك الحقل. يجب أن يعمل السياق مع كائن الخطة من خلال واجهة الخطة فقط. وقد يحدد السياق واجهة تسمح للخطة بالوصول إلى بياناته.
- يجب أن يربط عملاء السياق بين السياق وبين خطة مناسبة تطابق الكيفية التي يتوقعون السياق أن ينفذ مهمته عليها.
المزايا والعيوب
المزايا
- تستطيع تبديل الخوارزميات المستخدمة داخل كائن أثناء التشغيل.
- تستطيع عزل تفاصيل الاستخدام لخوارزمية من الشيفرة التي تستخدمها.
- تستطيع تبديل الاكتساب inheritance وإحلال التركيب Compositing مكانه.
- مبدأ المفتوح/المغلق. تستطيع إدخال خطط جديدة دون الحاجة إلى تغيير السياق.
العيوب
- إن لم يكن لديك سوى بضعة خوارزميات لا تتغير إلا نادرًا فليست هناك حاجة إلى تعقيد البرنامج بفئات جديدة وواجهات تأتي مع النمط.
- يجب أن تدرك العملاء الاختلافات بين الخطط لتستطيع اختيار واحدة مناسبة.
- تدعم كثير من لغات البرمجة الحديثة نوع الدالة الذي يسمح لك باستخدام إصدارات مختلفة من خوارزمية داخل مجموعة دوال مجهولة، ثم يمكنك استخدام تلك الدوال كما استخدمت كائنات الخطة تمامًا، لكن دون تضخيم شيفرتك بفئات وواجهات إضافية.
العلاقات مع الأنماط الأخرى
- تتشابه أنماط الجسر والحالة والخطة (والمحول إلى حد ما) في بنياتها، فكل هذه الأنماط مبنية على التركيب (composition) الذي يفوض المهام إلى كائنات أخرى، لكنها كذلك تحل مشاكل مختلفة، فالنمط ليس وصفة لهيكلة شيفرتك بشكل معين، فهو كذلك يوصل المشكلة التي يحلها إلى المطورين الآخرين.
- قد يبدو نمط الأمر مشابهًا لنمط الخطة (Strategy) إذ تستطيع استخدام كليهما لوصف كائن بإجراء ما، لكن هدف كل منهما يختلف كما يلي:
- تستطيع استخدام نمط الأمر لتحويل أي أمرٍ (command) إلى كائن، وتصبح معامِلات العملية حقولًا لذلك الكائن. ويسمح لك هذا التحويل بتأجيل تنفيذ العملية أو وضعها في صف أو تخزين سجل الأوامر أو إرسال الأوامر إلى خدمات بعيدة (Remote Services)، إلخ
- من الناحية الأخرى، يصف نمط الخطة (Strategy) عادة طرقًا مختلفة لتنفيذ نفس الشيء مما يسمح لك بتبديل تلك الخوارزميات داخل فئة سياقية واحدة.
- يسمح لك المزخرف بتغيير سمة الكائن، بينما يسمح لك نمط الخطة بتغيير محتواه.
- يبنى نمط أسلوب القالب على الاكتساب، إذ يسمح لك بتغيير أجزاء من خوارزمية من خلال توسيع أجزائه في فئات فرعية، أما نمط الخطة فيبنى على التركيب، فيمكنك تغيير أجزاء من سلوك الكائن من خلال إمداده بخطط مختلفة متوافقة لذلك السلوك.
ويعمل أسلوب القالب على مستوى الفئة لذا يُعد نمطًا ساكنًا (static)، بينما يعمل نمط الخطة على مستوى الكائن مما يسمح لك بتبديل السلوك أثناء التشغيل.
- يمكن النظر إلى نمط الحالة على أنه امتداد لنمط الخطة، فكلاهما مبنيان على التركيب إذ يغيران سلوك السياق من خلال تفويض بعض المهام إلى كائناتٍ مساعدة، فيجعل نمط الخطة تلك الكائنات مستقلة تمامًا وغير مدركة لوجود بعضها البعض، أما نمط الحالة فلا يقيد الاعتماديات بين الحالات الحقيقية (concrete states) مما يسمح لهم بتغيير حالة السياق في أي وقت.
الاستخدام في لغة جافا
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام:يكثر استخدام نمط الخطة في لغة جافا، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها. وقد دعمت جافا ثمان دوال من دوال lambda التي يمكن أن تكون بدائل أبسط لنمط الخطة. إليك بعض الأمثلة على نمط الخطة في مكتبات جافا:
- تُستدعى ()java.util.Comparator#compare من
()Collections#sort
- javax.servlet.http.HttpServlet: أسلوب
()service
، إضافة إلى جميع أساليب()doXXX
التي تقبل كائنات HttpServletRequest و HttpServletRequest كوسائط. - ()javax.servlet.Filter#doFilter
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
أسلوب الدفع في برنامج تجارة إلكترونية
يُستخدم نمط الخطة في هذا المثال لتطبيق أساليب دفع متعددة في برنامج تجارة إلكترونية، ويختار المستخدم طريقة الدفع بعد اختيار المنتج الذي يريد شراءه، إما عن طريقة باي بال أو البطاقة الائتمانية.
ولا تنفذ الخطط الحقيقية الدفع الفعلي فحسب، بل تغير سلوك استمارة الحساب أيضًا (checkout form) لتضيف حقولًا مناسبة لتسجيل تفاصيل الدفع.
الخطط (strategies)
strategies/PayStrategy.java: الواجهة المشتركة لطرق الدفع.
package refactoring_guru.strategy.example.strategies;
/**
* الواجهة المشتركة لكل الخطط.
*/
public interface PayStrategy {
boolean pay(int paymentAmount);
void collectPaymentDetails();
}
strategies/PayByPayPal.java: الدفع من خلال باي بال
package refactoring_guru.strategy.example.strategies;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* الخطة المشتركة، تطبق أسلوب الدفع من باي بال.
*/
public class PayByPayPal implements PayStrategy {
private static final Map<String, String> DATA_BASE = new HashMap<>();
private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
private String email;
private String password;
private boolean signedIn;
static {
DATA_BASE.put("amanda1985", "amanda@ya.com");
DATA_BASE.put("qwerty", "john@amazon.eu");
}
/**
* تجمع بيانات المستخدم.
*/
@Override
public void collectPaymentDetails() {
try {
while (!signedIn) {
System.out.print("Enter the user's email: ");
email = READER.readLine();
System.out.print("Enter the password: ");
password = READER.readLine();
if (verify()) {
System.out.println("Data verification has been successful.");
} else {
System.out.println("Wrong email or password!");
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
private boolean verify() {
setSignedIn(email.equals(DATA_BASE.get(password)));
return signedIn;
}
/**
* تحفظ بيانات المستخدم من أجل عمليات الشراء التالية.
*/
@Override
public boolean pay(int paymentAmount) {
if (signedIn) {
System.out.println("Paying " + paymentAmount + " using PayPal.");
return true;
} else {
return false;
}
}
private void setSignedIn(boolean signedIn) {
this.signedIn = signedIn;
}
}
strategies/PayByCreditCard.java: الدفع من خلال البطاقة الائتمانية
package refactoring_guru.strategy.example.strategies;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* الخطة المشتركة، تطبق أسلوب الدفع من خلال بطاقة الائتمان.
*/
public class PayByCreditCard implements PayStrategy {
private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
private CreditCard card;
/**
* اجمع بيانات بطاقة الائتمان.
*/
@Override
public void collectPaymentDetails() {
try {
System.out.print("Enter the card number: ");
String number = READER.readLine();
System.out.print("Enter the card expiration date 'mm/yy': ");
String date = READER.readLine();
System.out.print("Enter the CVV code: ");
String cvv = READER.readLine();
card = new CreditCard(number, date, cvv);
// تحقق من رقم بطاقة الائتمان...
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* نستطيع طلب المبلغ من البطاقة بعد التحقق منها.
*/
@Override
public boolean pay(int paymentAmount) {
if (cardIsPresent()) {
System.out.println("Paying " + paymentAmount + " using Credit Card.");
card.setAmount(card.getAmount() - paymentAmount);
return true;
} else {
return false;
}
}
private boolean cardIsPresent() {
return card != null;
}
}
strategies/CreditCard.java: فئة بطاقة ائتمان
package refactoring_guru.strategy.example.strategies;
/**
* فئة بطاقة ائتمان وهمية للتوضيح.
*/
public class CreditCard {
private int amount;
private String number;
private String date;
private String cvv;
CreditCard(String number, String date, String cvv) {
this.amount = 100_000;
this.number = number;
this.date = date;
this.cvv = cvv;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
}
order/Order.java: فئة الطلب Order Class
package refactoring_guru.strategy.example.order;
import refactoring_guru.strategy.example.strategies.PayStrategy;
/**
* فئة الطلب، لا تعرف أسلوب الدفع الحقيقي (الخطة) الذي اختاره
* المستخدم، وتستخدم واجهة الخطة المشتركة لتفويض جمع بيانات الدفع
* إلى كائن الخطة، ويمكن استخدامها لحفظ الطلب إلى قاعدة بيانات.
*/
public class Order {
private int totalCost = 0;
private boolean isClosed = false;
public void processOrder(PayStrategy strategy) {
strategy.collectPaymentDetails();
// هنا نستطيع جميع وتخزين بيانات الدفع من الخطة.
}
public void setTotalCost(int cost) {
this.totalCost += cost;
}
public int getTotalCost() {
return totalCost;
}
public boolean isClosed() {
return isClosed;
}
public void setClosed() {
isClosed = true;
}
}
Demo.java: شيفرة العميل
package refactoring_guru.strategy.example;
import refactoring_guru.strategy.example.order.Order;
import refactoring_guru.strategy.example.strategies.PayByCreditCard;
import refactoring_guru.strategy.example.strategies.PayByPayPal;
import refactoring_guru.strategy.example.strategies.PayStrategy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* في العالم Console أول برنامج تجارة إلكتروني للمنصات.
*/
public class Demo {
private static Map<Integer, Integer> priceOnProducts = new HashMap<>();
private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
private static Order order = new Order();
private static PayStrategy strategy;
static {
priceOnProducts.put(1, 2200);
priceOnProducts.put(2, 1850);
priceOnProducts.put(3, 1100);
priceOnProducts.put(4, 890);
}
public static void main(String[] args) throws IOException {
while (!order.isClosed()) {
int cost;
String continueChoice;
do {
System.out.print("Please, select a product:" + "\n" +
"1 - Mother board" + "\n" +
"2 - CPU" + "\n" +
"3 - HDD" + "\n" +
"4 - Memory" + "\n");
int choice = Integer.parseInt(reader.readLine());
cost = priceOnProducts.get(choice);
System.out.print("Count: ");
int count = Integer.parseInt(reader.readLine());
order.setTotalCost(cost * count);
System.out.print("Do you wish to continue selecting products? Y/N: ");
continueChoice = reader.readLine();
} while (continueChoice.equalsIgnoreCase("Y"));
if (strategy == null) {
System.out.println("Please, select a payment method:" + "\n" +
"1 - PalPay" + "\n" +
"2 - Credit Card");
String paymentMethod = reader.readLine();
// ينشئ العميل خططًا مختلفة بناءً على مدخلات المستخدم
// وإعدادات التطبيق، إلخ... .
if (paymentMethod.equals("1")) {
strategy = new PayByPayPal();
} else {
strategy = new PayByCreditCard();
}
// جمع بيانات الدفع إلى كائن order object يفوض كائن الطلب
// الخطة، بما أن كائنات الخطة فقط هي التي تعرف أي البيانات
// التي تحتاجها لمعالجة عملية الدفع.
order.processOrder(strategy);
System.out.print("Pay " + order.getTotalCost() + " units or Continue shopping? P/C: ");
String proceed = reader.readLine();
if (proceed.equalsIgnoreCase("P")) {
// وأخيرًا، تسلِّم الخطة عملية الدفع.
if (strategy.pay(order.getTotalCost())) {
System.out.println("Payment has been successful.");
} else {
System.out.println("FAIL! Please, check your data.");
}
order.setClosed();
}
}
}
}
}
OutputDemo.txt: نتائج التنفيذ
Please, select a product:
1 - Mother board
2 - CPU
3 - HDD
4 - Memory
1
Count: 2
Do you wish to continue selecting products? Y/N: y
Please, select a product:
1 - Mother board
2 - CPU
3 - HDD
4 - Memory
2
Count: 1
Do you wish to continue selecting products? Y/N: n
Please, select a payment method:
1 - PalPay
2 - Credit Card
1
Enter the user's email: user@example.com
Enter the password: qwerty
Wrong email or password!
Enter user email: amanda@ya.com
Enter password: amanda1985
Data verification has been successful.
Pay 6250 units or Continue shopping? P/C: p
Paying 6250 using PayPal.
Payment has been successful.
الاستخدام في لغة #C
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة #C، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
using System.Collections.Generic;
namespace RefactoringGuru.DesignPatterns.Strategy.Conceptual
{
// يحدد السياق الواجهة التي يطلبها العملاء.
class Context
{
// يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
// ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
private IStrategy _strategy;
public Context()
{ }
// يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
// كي يمكن تبديل الخطة أثناء التشغيل
public Context(IStrategy strategy)
{
this._strategy = strategy;
}
// يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
public void SetStrategy(IStrategy strategy)
{
this._strategy = strategy;
}
// يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات.
// من الخوارزمية بنفسه
public void DoSomeBusinessLogic()
{
Console.WriteLine("Context: Sorting data using the strategy (not sure how it'll do it)");
var result = this._strategy.DoAlgorithm(new List<string> { "a", "b", "c", "d", "e" });
string resultStr = string.Empty;
foreach (var element in result as List<string>)
{
resultStr += element + ",";
}
Console.WriteLine(resultStr);
}
}
// تصرح واجهة الخطة عن العمليات المشتركة بين كل إصدارات إحدى الخوارزميات.
//
// يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
public interface IStrategy
{
object DoAlgorithm(object data);
}
// تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
// داخل السياق (interchangeable) تبادلية.
class ConcreteStrategyA : IStrategy
{
public object DoAlgorithm(object data)
{
var list = data as List<string>;
list.Sort();
return list;
}
}
class ConcreteStrategyB : IStrategy
{
public object DoAlgorithm(object data)
{
var list = data as List<string>;
list.Sort();
list.Reverse();
return list;
}
}
class Program
{
static void Main(string[] args)
{
// تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
// الخطط لاتخاذ القرار المناسب.
var context = new Context();
Console.WriteLine("Client: Strategy is set to normal sorting.");
context.SetStrategy(new ConcreteStrategyA());
context.DoSomeBusinessLogic();
Console.WriteLine();
Console.WriteLine("Client: Strategy is set to reverse sorting.");
context.SetStrategy(new ConcreteStrategyB());
context.DoSomeBusinessLogic();
}
}
}
Output.txt: نتائج التنفيذ
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
الاستخدام في لغة PHP
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة PHP، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Strategy\Conceptual;
/**
* يحدد السياق الواجهة التي يطلبها العملاء.
*/
class Context
{
/**
* @var Strategy يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
* ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
*/
private $strategy;
/**
* يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
* كي يمكن تبديل الخطة أثناء التشغيل
*/
public function __construct(Strategy $strategy)
{
$this->strategy = $strategy;
}
/**
* يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
*/
public function setStrategy(Strategy $strategy)
{
$this->strategy = $strategy;
}
/**
* يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات من الخوارزمية بنفسه.
*/
public function doSomeBusinessLogic(): void
{
// ...
echo "Context: Sorting data using the strategy (not sure how it'll do it)\n";
$result = $this->strategy->doAlgorithm(["a", "b", "c", "d", "e"]);
echo implode(",", $result) . "\n";
// ...
}
}
/**
* تصرح واجهة الخطة عن العمليات المشتركة بين كل الإصدارات المدعومة
* لإحدى الخوارزميات.
*
* يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
*/
interface Strategy
{
public function doAlgorithm(array $data): array;
}
/**
* تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
* داخل السياق (interchangeable) تبادلية.
*/
class ConcreteStrategyA implements Strategy
{
public function doAlgorithm(array $data): array
{
sort($data);
return $data;
}
}
class ConcreteStrategyB implements Strategy
{
public function doAlgorithm(array $data): array
{
rsort($data);
return $data;
}
}
/**
* تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
* الخطط لاتخاذ القرار المناسب.
*/
$context = new Context(new ConcreteStrategyA);
echo "Client: Strategy is set to normal sorting.\n";
$context->doSomeBusinessLogic();
echo "\n";
echo "Client: Strategy is set to reverse sorting.\n";
$context->setStrategy(new ConcreteStrategyB);
$context->doSomeBusinessLogic();
Output.txt: نتائج التنفيذ
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
مثال واقعي
يُستخدم نمط الخطة في المثال التالي لتوضيح أساليب الدفع في برنامج تجارة إلكترونية. تستطيع كل طريقة دفع أن تعرف استمارة دفع لجمع تفاصيل الدفع المطلوبة من المستخدم وإرسالها إلى شركة معالجة الدفع، ثم بعد أن توجه شركة معالجة الدفع تلك المستخدم إلى موقعنا فإن أسلوب الدفع يتحقق من معامِلات الإعادة (return parameters) ليساهم بعدها في قرار ما إن كان الطلب قد اكتمل أم لا.
index.php: مثال واقعي
<?php
namespace RefactoringGuru\Strategy\RealWorld;
/**
* هذا الموجه والمتحكم في برنامجنا، تقرر هذه الفئة عند استلام الطلب
* أي سلوك يجب تنفيذه، وحين يستلم البرنامج طلب الدفع فإن
* تقرر أي طريقة تدفع يجب أن تستخدمها لمعالجة OrderController فئة
* الطلب، وعليه تتصرف الفئة كأنها السياق والعميل في نفس الوقت.
*/
class OrderController
{
/**
* Handle POST requests.
*
* @param $url
* @param $data
* @throws \Exception
*/
public function post(string $url, array $data)
{
echo "Controller: POST request to $url with " . json_encode($data) . "\n";
$path = parse_url($url, PHP_URL_PATH);
if (preg_match('#^/orders?$#', $path, $matches)) {
$this->postNewOrder($data);
} else {
echo "Controller: 404 page\n";
}
}
/**
* GET عالج طلبات.
*
* @param $url
* @throws \Exception
*/
public function get(string $url): void
{
echo "Controller: GET request to $url\n";
$path = parse_url($url, PHP_URL_PATH);
$query = parse_url($url, PHP_URL_QUERY);
parse_str($query, $data);
if (preg_match('#^/orders?$#', $path, $matches)) {
$this->getAllOrders();
} elseif (preg_match('#^/order/([0-9]+?)/payment/([a-z]+?)(/return)?$#', $path, $matches)) {
$order = Order::get($matches[1]);
// يُختار أسلوب الدفع وفقًا للقيمة المُمرَّرة مع الطلب.
$paymentMethod = PaymentFactory::getPaymentMethod($matches[2]);
if (!isset($matches[3])) {
$this->getPayment($paymentMethod, $order, $data);
} else {
$this->getPaymentReturn($paymentMethod, $order, $data);
}
} else {
echo "Controller: 404 page\n";
}
}
/**
* POST /order {data}
*/
public function postNewOrder(array $data): void
{
$order = new Order($data);
echo "Controller: Created the order #{$order->id}.\n";
}
/**
* GET /orders
*/
public function getAllOrders(): void
{
echo "Controller: Here's all orders:\n";
foreach (Order::get() as $order) {
echo json_encode($order, JSON_PRETTY_PRINT) . "\n";
}
}
/**
* GET /order/123/payment/XX
*/
public function getPayment(PaymentMethod $method, Order $order, array $data): void
{
// يٌفوَّض العمل الفعلي إلى كائن طريقة الدفع.
$form = $method->getPaymentForm($order);
echo "Controller: here's the payment form:\n";
echo $form . "\n";
}
/**
* GET /order/123/payment/XXX/return?key=AJHKSJHJ3423&success=true
*/
public function getPaymentReturn(PaymentMethod $method, Order $order, array $data): void
{
try {
// نوع آخر من العمل المفوَّض إلى أسلوب الدفع.
if ($method->validateReturn($order, $data)) {
echo "Controller: Thanks for your order!\n";
$order->complete();
}
} catch (\Exception $e) {
echo "Controller: got an exception (" . $e->getMessage() . ")\n";
}
}
}
/**
* تمثيل مبسط لفئة الطلب.
*/
class Order
{
/**
* سنخزن جميع الطلبات المنشأة هنا بداعي التبسيط.
*
* @var array
*/
private static $orders = [];
/**
* ...ونصل إليها من هنا.
*
* @param int $orderId
* @return mixed
*/
public static function get(int $orderId = null)
{
if ($orderId === null) {
return static::$orders;
} else {
return static::$orders[$orderId];
}
}
/**
* يحدد منشئ الطلب فيمًا لحقول الطلب، وليس هناك تحقق من أي
* نوع من أجل إبقاء الأمور بسيطة قدر الإمكان.
*
* @param array $attributes
*/
public function __construct(array $attributes)
{
$this->id = count(static::$orders);
$this->status = "new";
foreach ($attributes as $key => $value) {
$this->{$key} = $value;
}
static::$orders[$this->id] = $this;
}
/**
* The method to call when an order gets paid.
*/
public function complete(): void
{
$this->status = "completed";
echo "Order: #{$this->id} is now {$this->status}.";
}
}
/**
* This class helps to produce a proper strategy object for handling a payment.
*/
class PaymentFactory
{
/**
* Get a payment method by its ID.
*
* @param $id
* @return PaymentMethod
* @throws \Exception
*/
public static function getPaymentMethod(string $id): PaymentMethod
{
switch ($id) {
case "cc":
return new CreditCardPayment;
case "paypal":
return new PayPalPayment;
default:
throw new \Exception("Unknown Payment Method");
}
}
}
/**
* تصف واجهة الخطة كيف يستطيع العميل استخدام الخطط الحقيقية.
* لاحظ أنه في أغلب الأمثلة التي تستطيع إيجادها في الويب فإن الخطط
* تميل إلى تنفيذ أشياء صغيرة داخل أسلوب واحد.
* لكن في الحقيقة فإن خططك يمكنها أن تكون أقوى، بأن يكون لها عدة أساليب مثلًا.
*/
interface PaymentMethod
{
public function getPaymentForm(Order $order): string;
public function validateReturn(Order $order, array $data): bool;
}
/**
* توفر هذه الخطة الحقيقية استمارة دفع وتتحقق من الإعادات على مدفوعات
* البطاقة الائتمانية.
*/
class CreditCardPayment implements PaymentMethod
{
static private $store_secret_key = "swordfish";
public function getPaymentForm(Order $order): string
{
$returnURL = "https://our-website.com/" .
"order/{$order->id}/payment/cc/return";
return <<<FORM
<form action="https://my-credit-card-processor.com/charge" method="POST">
<input type="hidden" id="email" value="{$order->email}">
<input type="hidden" id="total" value="{$order->total}">
<input type="hidden" id="returnURL" value="$returnURL">
<input type="text" id="cardholder-name">
<input type="text" id="credit-card">
<input type="text" id="expiration-date">
<input type="text" id="ccv-number">
<input type="submit" value="Pay">
</form>
FORM;
}
public function validateReturn(Order $order, array $data): bool
{
echo "CreditCardPayment: ...validating... ";
if ($data['key'] != md5($order->id . static::$store_secret_key)) {
throw new \Exception("Payment key is wrong.");
}
if (!isset($data['success']) || !$data['success'] || $data['success'] == 'false') {
throw new \Exception("Payment failed.");
}
// ...
if (floatval($data['total']) < $order->total) {
throw new \Exception("Payment amount is wrong.");
}
echo "Done!\n";
return true;
}
}
/**
* توفر هذه الخطة الحقيقية استمارة دفع وتتحقق من الإعادات على مدفوعات
* باي بال.
*/
class PayPalPayment implements PaymentMethod
{
public function getPaymentForm(Order $order): string
{
$returnURL = "https://our-website.com/" .
"order/{$order->id}/payment/paypal/return";
return <<<FORM
<form action="https://paypal.com/payment" method="POST">
<input type="hidden" id="email" value="{$order->email}">
<input type="hidden" id="total" value="{$order->total}">
<input type="hidden" id="returnURL" value="$returnURL">
<input type="submit" value="Pay on PayPal">
</form>
FORM;
}
public function validateReturn(Order $order, array $data): bool
{
echo "PayPalPayment: ...validating... ";
// ...
echo "Done!\n";
return true;
}
}
/**
* شيفرة العميل.
*/
$controller = new OrderController;
echo "Client: Let's create some orders\n";
$controller->post("/orders", [
"email" => "me@example.com",
"product" => "ABC Cat food (XL)",
"total" => 9.95,
]);
$controller->post("/orders", [
"email" => "me@example.com",
"product" => "XYZ Cat litter (XXL)",
"total" => 19.95,
]);
echo "\nClient: List my orders, please\n";
$controller->get("/orders");
echo "\nClient: I'd like to pay for the second, show me the payment form\n";
$controller->get("/order/1/payment/paypal");
echo "\nClient: ...pushes the Pay button...\n";
echo "\nClient: Oh, I'm redirected to the PayPal.\n";
echo "\nClient: ...pays on the PayPal...\n";
echo "\nClient: Alright, I'm back with you, guys.\n";
$controller->get("/order/1/payment/paypal/return" .
"?key=c55a3964833a4b0fa4469ea94a057152&success=true&total=19.95");
Output.txt: نتائج التنفيذ
Client: Let's create some orders
Controller: POST request to /orders with {"email":"me@example.com","product":"ABC Cat food (XL)","total":9.95}
Controller: Created the order #0.
Controller: POST request to /orders with {"email":"me@example.com","product":"XYZ Cat litter (XXL)","total":19.95}
Controller: Created the order #1.
Client: List my orders, please
Controller: GET request to /orders
Controller: Here's all orders:
{
"id": 0,
"status": "new",
"email": "me@example.com",
"product": "ABC Cat food (XL)",
"total": 9.95
}
{
"id": 1,
"status": "new",
"email": "me@example.com",
"product": "XYZ Cat litter (XXL)",
"total": 19.95
}
Client: I'd like to pay for the second, show me the payment form
Controller: GET request to /order/1/payment/paypal
Controller: here's the payment form:
<form action="https://paypal.com/payment" method="POST">
<input type="hidden" id="email" value="me@example.com">
<input type="hidden" id="total" value="19.95">
<input type="hidden" id="returnURL" value="https://our-website.com/order/1/payment/paypal/return">
<input type="submit" value="Pay on PayPal">
</form>
Client: ...pushes the Pay button...
Client: Oh, I'm redirected to the PayPal.
Client: ...pays on the PayPal...
Client: Alright, I'm back with you, guys.
Controller: GET request to /order/1/payment/paypal/return?key=c55a3964833a4b0fa4469ea94a057152&success=true&total=19.95
PayPalPayment: ...validating... Done!
Controller: Thanks for your order!
Order: #1 is now completed.
الاستخدام في لغة بايثون
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة بايثون، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Context():
"""
يحدد السياق الواجهة التي يطلبها العملاء.
"""
def __init__(self, strategy: Strategy) -> None:
"""
يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
كي يمكن تبديل الخطة أثناء التشغيل
"""
self._strategy = strategy
@property
def strategy(self) -> Strategy:
"""
يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
"""
return self._strategy
@strategy.setter
def strategy(self, strategy: Strategy) -> None:
"""
يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
"""
self._strategy = strategy
def do_some_business_logic(self) -> None:
"""
يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات من الخوارزمية بنفسه.
"""
# ...
print("Context: Sorting data using the strategy (not sure how it'll do it)")
result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"])
print(",".join(result))
# ...
class Strategy(ABC):
"""
تصرح واجهة الخطة عن العمليات المشتركة بين كل الإصدارات المدعومة
لإحدى الخوارزميات.
يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
"""
@abstractmethod
def do_algorithm(self, data: List):
pass
"""
تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
داخل السياق (interchangeable) تبادلية.
"""
class ConcreteStrategyA(Strategy):
def do_algorithm(self, data: List) -> List:
return sorted(data)
class ConcreteStrategyB(Strategy):
def do_algorithm(self, data: List) -> List:
return reversed(sorted(data))
if __name__ == "__main__":
# تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
# الخطط لاتخاذ القرار المناسب.
context = Context(ConcreteStrategyA())
print("Client: Strategy is set to normal sorting.")
context.do_some_business_logic()
print()
print("Client: Strategy is set to reverse sorting.")
context.strategy = ConcreteStrategyB()
context.do_some_business_logic()
Output.txt: نتائج التنفيذ
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
الاستخدام في لغة روبي
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة روبي، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# يحدد السياق الواجهة التي يطلبها العملاء.
class Context
# يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
# ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
attr_writer :strategy
# يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
# كي يمكن تبديل الخطة أثناء التشغيل
def initialize(strategy)
@strategy = strategy
end
# يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
def strategy=(strategy)
@strategy = strategy
end
# يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات من الخوارزمية بنفسه.
def do_some_business_logic
# ...
puts 'Context: Sorting data using the strategy (not sure how it\'ll do it)'
result = @strategy.do_algorithm(%w[a b c d e])
print result.join(',')
# ...
end
end
# تصرح واجهة الخطة عن العمليات المشتركة بين كل الإصدارات المدعومة
# لإحدى الخوارزميات.
#
# يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
class Strategy
# @abstract
#
# @param [Array] data
def do_algorithm(_data)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
# داخل السياق (interchangeable) تبادلية.
class ConcreteStrategyA < Strategy
# @param [Array] data
#
# @return [Array]
def do_algorithm(data)
data.sort
end
end
class ConcreteStrategyB < Strategy
# @param [Array] data
#
# @return [Array]
def do_algorithm(data)
data.sort.reverse
end
end
# تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
# الخطط لاتخاذ القرار المناسب.
context = Context.new(ConcreteStrategyA.new)
puts 'Client: Strategy is set to normal sorting.'
context.do_some_business_logic
puts "\n\n"
puts 'Client: Strategy is set to reverse sorting.'
context.strategy = ConcreteStrategyB.new
context.do_some_business_logic
output.txt: مثال تصوري
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
الاستخدام في لغة Swift
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة Swift، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
Example.swift: Conceptual Example
import XCTest
/// يحدد السياق الواجهة التي يطلبها العملاء.
class Context {
/// يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
/// ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
private var strategy: Strategy
/// يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
/// كي يمكن تبديل الخطة أثناء التشغيل
init(strategy: Strategy) {
self.strategy = strategy
}
/// يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
func update(strategy: Strategy) {
self.strategy = strategy
}
/// يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات من الخوارزمية بنفسه.
func doSomeBusinessLogic() {
print("Context: Sorting data using the strategy (not sure how it'll do it)\n")
let result = strategy.doAlgorithm(["a", "b", "c", "d", "e"])
print(result.joined(separator: ","))
}
}
/// تصرح واجهة الخطة عن العمليات المشتركة بين كل الإصدارات المدعومة
/// لإحدى الخوارزميات.
///
/// يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
protocol Strategy {
func doAlgorithm<T: Comparable>(_ data: [T]) -> [T]
}
/// تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
/// داخل السياق (interchangeable) تبادلية.
class ConcreteStrategyA: Strategy {
func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
return data.sorted()
}
}
class ConcreteStrategyB: Strategy {
func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
return data.sorted(by: >)
}
}
/// لنرى الآن كيف سيعمل كل ذلك...
class StrategyConceptual: XCTestCase {
func test() {
/// تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
/// الخطط لاتخاذ القرار المناسب.
let context = Context(strategy: ConcreteStrategyA())
print("Client: Strategy is set to normal sorting.\n")
context.doSomeBusinessLogic()
print("\nClient: Strategy is set to reverse sorting.\n")
context.update(strategy: ConcreteStrategyB())
context.doSomeBusinessLogic()
}
}
Output.txt: Execution result
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
مثال واقعي
Example.swift: Real World Example
import XCTest
class StrategyRealWorld: XCTestCase {
/// يظهر هذ المثال تطبيقًا بسيطًا لمتحكم قوائم يستطيع إظهار
/// الموديلات من مصادر بيانات مختلفة.
///
/// (MemoryStorage, CoreDataStorage, RealmStorage)
func test() {
let controller = ListController()
let memoryStorage = MemoryStorage<User>()
memoryStorage.add(usersFromNetwork())
clientCode(use: controller, with: memoryStorage)
clientCode(use: controller, with: CoreDataStorage())
clientCode(use: controller, with: RealmStorage())
}
func clientCode(use controller: ListController, with dataSource: DataSource) {
controller.update(dataSource: dataSource)
controller.displayModels()
}
private func usersFromNetwork() -> [User] {
let firstUser = User(id: 1, username: "username1")
let secondUser = User(id: 2, username: "username2")
return [firstUser, secondUser]
}
}
class ListController {
private var dataSource: DataSource?
func update(dataSource: DataSource) {
/// ... أعد ضبط الحالات الراهنة ...
self.dataSource = dataSource
}
func displayModels() {
guard let dataSource = dataSource else { return }
let models = dataSource.loadModels() as [User]
/// list view ربط الموديلات بخلايا من نوع عرض القائمة...
print("\nListController: Displaying models...")
models.forEach({ print($0) })
}
}
protocol DataSource {
func loadModels<T: DomainModel>() -> [T]
}
class MemoryStorage<Model>: DataSource {
private lazy var items = [Model]()
func add(_ items: [Model]) {
self.items.append(contentsOf: items)
}
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
return items as! [T]
}
}
class CoreDataStorage: DataSource {
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
let firstUser = User(id: 3, username: "username3")
let secondUser = User(id: 4, username: "username4")
return [firstUser, secondUser] as! [T]
}
}
class RealmStorage: DataSource {
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
let firstUser = User(id: 5, username: "username5")
let secondUser = User(id: 6, username: "username6")
return [firstUser, secondUser] as! [T]
}
}
protocol DomainModel {
var id: Int { get }
}
struct User: DomainModel {
var id: Int
var username: String
}
Output.txt: Execution result
ListController: Displaying models...
User(id: 1, username: "username1")
User(id: 2, username: "username2")
ListController: Displaying models...
User(id: 3, username: "username3")
User(id: 4, username: "username4")
ListController: Displaying models...
User(id: 5, username: "username5")
User(id: 6, username: "username6")
الاستخدام في لغة TypeScript
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط الخطة في لغة TypeScript، إذ يستخدم عادة في أطر عمل عديدة لتزويد المستخدمين بطريقة لتغيير سلوك الفئة دون توسيعها.
يمكن ملاحظة نمط الخطة من خلال أسلوب يسمح للكائنات المتداخلة أن تنفذ المهام الحقيقية إضافة إلى محدِّد (setter) يسمح باستبدال ذلك الكائن بغيره.
مثال تصوري
يوضح هذا المثال بنية نمط الخطة (Strategy)، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: Conceptual Example
/**
* يحدد السياق الواجهة التي يطلبها العملاء.
*/
class Context {
/**
* @type {Strategy} يحافظ السياق على مرجع لأحد كائنات الخطط، ولا يعرف الفئة الحقيقية للخطة.
* ينبغي أن يعمل السياق مع جميع الخطط من خلال واجهة الخطة.
*/
private strategy: Strategy;
/**
* يقبل السياق في العادة خطةً من خلال المنشئ، ويوفر محدِّدًا
* كي يمكن تبديل الخطة أثناء التشغيل
*/
constructor(strategy: Strategy) {
this.strategy = strategy;
}
/**
* يسمح السياق عادة باستبدال كائن الخطة أثناء التشغيل.
*/
public setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
/**
* يفوض السياق بعض المهام إلى كائن الخطة بدلًا من تطبيق عدة إصدارات من الخوارزمية بنفسه.
*/
public doSomeBusinessLogic(): void {
// ...
console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
console.log(result.join(','));
// ...
}
}
/**
* تصرح واجهة الخطة عن العمليات المشتركة بين كل الإصدارات المدعومة
* لإحدى الخوارزميات.
*
* يستخدم السياق هذه الواجهة لاستدعاء الخوارزمية التي تحددها الخطط الحقيقية.
*/
interface Strategy {
doAlgorithm(data: string[]): string[];
}
/**
* تطبق الخطط الحقيقية الخوارزمية مع اتباع واجهة الخطة، والواجهة تجعلها
* داخل السياق (interchangeable) تبادلية.
*/
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort();
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse();
}
}
/**
* تختار شيفرة العميل خطة حقيقية وتمررها إلى السياق، ويجب أن يدرك العميل الاختلافات بين
* الخطط لاتخاذ القرار المناسب.
*/
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();
console.log('');
console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();
Output.txt: Execution result
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a