Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط Strategy

جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 69 من 87.

يُعد نمط الاستراتيجية (Strategy Pattern) نمط تصميم سلوكي يتيح لك تعريف عائلة من الخوارزميات، وتغليف كل منها في فئة (class) خاصة بها، وجعلها قابلة للتبديل فيما بينها. فبدلاً من كتابة سلوك معين بشكل ثابت (hardcoding)، يمكنك تبديل الخوارزميات في وقت التشغيل (runtime) دون تغيير الكود الذي يستخدمها.

تخيل نظام دفع يدعم طرق دفع متعددة. بدون نمط الاستراتيجية (Strategy Pattern)، ستنتهي بمنطق شرطي فوضوي. باستخدامه، تصبح كل طريقة دفع استراتيجية خاصة بها:

interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via Credit Card");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via PayPal");
    }
}

تحتفظ فئة context بمرجع لاستراتيجية معينة وتفوض العمل إليها. النقطة الأساسية هي أن السياق لا يعرف أي استراتيجية ملموسة يستخدمها—فهو يعرف الواجهة فقط:

class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
    
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

الآن يمكنك تغيير سلوك الدفع ديناميكيًا:

ShoppingCart cart = new ShoppingCart();

cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100);  // تم دفع 100 عبر بطاقة الائتمان

cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50);   // تم دفع 50 عبر PayPal

يتألق نمط الاستراتيجية (Strategy Pattern) عندما يكون لديك طرق متعددة لتنفيذ إجراء ما وتريد التبديل بينها بسهولة. إن إضافة وسيلة دفع جديدة تعني ببساطة إنشاء فئة (class) جديدة تقوم بتنفيذ PaymentStrategy—دون الحاجة إلى إجراء أي تغييرات على الكود الحالي.

challenge icon

التحدي

سهل

لنقم ببناء نظام لتنسيق النصوص باستخدام نمط الاستراتيجية (Strategy Pattern)! ستقوم بإنشاء نظام حيث يمكن تبديل استراتيجيات التنسيق المختلفة في وقت التشغيل لتحويل النص بطرق متنوعة — وهو أمر مثالي لتطبيق يحتاج إلى دعم تنسيقات مخرجات متعددة.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • TextFormatter.java: قم بتعريف واجهة TextFormatter التي ستنفذها جميع استراتيجيات التنسيق. يجب أن تصرح عن طريقة واحدة String format(String text) تأخذ سلسلة نصية وتعيد النسخة المنسقة.
  • Formatters.java: قم بإنشاء ثلاث استراتيجيات ملموسة (concrete strategies) تنفذ واجهة TextFormatter الخاصة بك:

    UpperCaseFormatter - يحول النص إلى أحرف كبيرة بالكامل.

    LowerCaseFormatter - يحول النص إلى أحرف صغيرة بالكامل.

    TitleCaseFormatter - يجعل الحرف الأول من كل كلمة كبيراً بينما يجعل باقي الأحرف صغيرة. الكلمات مفصولة بمسافات.

  • TextEditor.java: قم بإنشاء فئة السياق (context class) التي تستخدم استراتيجية التنسيق. يجب أن يحتوي TextEditor الخاص بك على:

    حقل خاص (private field) للاحتفاظ باستراتيجية TextFormatter الحالية.

    طريقة setFormatter(TextFormatter formatter) تقوم بتغيير استراتيجية التنسيق النشطة.

    طريقة publishText(String text) تقوم بتطبيق المنسق الحالي على النص وتطبع Published: [formatted text]. إذا لم يتم تعيين أي منسق، يجب أن تطبع Published: [original text] دون تغيير.

  • Main.java: اجمع نظام الاستراتيجية الخاص بك معاً! ستتلقى مدخلين: النص المراد تنسيقه (String) ونمط التنسيق (String: "upper"، أو "lower"، أو "title").

    قم بإنشاء TextEditor واستدعِ أولاً publishText مع نص الإدخال الخاص بك قبل تعيين أي منسق (لإظهار السلوك الافتراضي).

    بعد ذلك، بناءً على نمط الإدخال، قم بتعيين استراتيجية المنسق المناسبة واستدعِ publishText مرة أخرى بنفس النص لإظهار النتيجة المنسقة.

ستتلقى مدخلين بالترتيب: النص المراد تنسيقه (String) ونمط التنسيق (String).

على سبيل المثال، مع المدخلات hello world و title، سيكون مخرجك:

Published: hello world
Published: Hello World

لاحظ كيف أن TextEditor لا يعرف أي شيء عن منطق التنسيق المحدد — فهو ببساطة يفوض المهمة إلى أي استراتيجية تم تعيينها حالياً. وهذا يجعل من السهل جداً إضافة أنماط تنسيق جديدة دون المساس بكود المحرر!

ورقة مرجعية

يُعد نمط الاستراتيجية (Strategy Pattern) نمط تصميم سلوكي يحدد عائلة من الخوارزميات، ويغلف كل واحدة منها في فئتها الخاصة، ويجعلها قابلة للتبديل. فهو يسمح بتبديل الخوارزميات في وقت التشغيل دون تغيير الكود الذي يستخدمها.

المكونات الرئيسية

واجهة الاستراتيجية (Strategy Interface): تحدد الواجهة المشتركة لجميع الاستراتيجيات الملموسة.

interface PaymentStrategy {
    void pay(int amount);
}

الاستراتيجيات الملموسة (Concrete Strategies): تقوم بتنفيذ واجهة الاستراتيجية بخوارزميات محددة.

class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via Credit Card");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via PayPal");
    }
}

فئة السياق (Context Class): تحتفظ بمرجع للاستراتيجية وتفوض العمل إليها. يعرف السياق الواجهة فقط، وليس التنفيذ الملموس.

class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
    
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

الاستخدام

يمكن تغيير الاستراتيجيات ديناميكيًا في وقت التشغيل:

ShoppingCart cart = new ShoppingCart();

cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100);  // تم دفع 100 عبر بطاقة الائتمان

cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50);   // تم دفع 50 عبر PayPal

الفوائد

يُعد نمط الاستراتيجية مثاليًا عندما يكون لديك طرق متعددة لأداء إجراء ما وتريد التبديل بينها بسهولة. إضافة استراتيجية جديدة تعني ببساطة إنشاء فئة جديدة تنفذ واجهة الاستراتيجية - دون الحاجة إلى إجراء تغييرات على الكود الحالي.

جرّب بنفسك

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String text = scanner.nextLine();
        String style = scanner.nextLine();
        
        // TODO: قم بإنشاء مثيل (instance) لـ TextEditor
        
        // TODO: استدعِ publishText مع النص المدخل (قبل تعيين أي منسق)
        
        // TODO: بناءً على مدخل النمط ("upper" أو "lower" أو "title")،
        // قم بتعيين المنسق المناسب واستدعِ publishText مرة أخرى
        
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming