Factory Pattern

Factory Pattern هو نمط تصميم إبداعي يستخدم لإنشاء الكائنات بطريقة نظيفة ومرنة. بدلاً من إنشاء كائنات مباشرة في أجزاء مختلفة من التطبيق، يقوم Factory Pattern بنقل منطق إنشاء الكائن إلى فئة أو أسلوب مصنع منclass.

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

مقدمة

في البرمجة كائنية التوجه، يعد إنشاء الكائنات باستخدام الكلمة الأساسية الجديدة أمرًا طبيعيًا وبسيطًا. ومع ذلك، مع نمو التطبيقات، يمكن أن يصبح إنشاء الكائنات أكثر تعقيدًا. قد يحتاج الclass إلى تبعيات مختلفة، أو قيم تكوين مختلفة، أو تطبيقات مختلفة حسب الموقف.

إذا تم تكرار منطق إنشاء الكائن عبر وحدات التحكم أو الخدمات أو الأوامر أو فئات الأعمال، يصبح من الصعب الحفاظ على الكود. يجب تحديث أي تغيير في عملية الإنشاء في العديد من الأماكن.

يحل Factory Pattern هذه المشكلة عن طريق مركزية إنشاء الكائن. يطلب التطبيق من المصنع إنشاء الكائن الصحيح، ويقرر المصنع class التي يجب إنشاء مثيل لها.

ما هو Factory Pattern؟

Factory Pattern هو نمط تصميم يوفر طريقة أو فئة مسؤولة عن إنشاء الكائنات. رمز العميل لا يقوم بإنشاء الكائن مباشرة. وبدلاً من ذلك، فإنه يستدعي المصنع ويستقبل كائنًا يتبع نوعًا أو واجهة معروفة أو parent class.

بعبارات بسيطة، المصنع يشبه مركز الإنتاج. يمكنك طلب نوع معين من الكائنات، ويقوم المصنع بإرجاع المثيل الصحيح.

على سبيل المثال، قد يدعم أحد التطبيقات طرق دفع متعددة مثل بطاقة الائتمان وPayPal والتحويل البنكي. بدلاً من كتابة منطق إنشاء الكائن داخل خدمة الدفع، يمكن لـ PaymentFactory إنشاء كائن الدفع الصحيح بناءً على نوع الدفع المحدد.

لماذا Factory Pattern مهم

يعد Factory Pattern مهمًا لأنه يclass بين إنشاء الكائن واستخدام الكائن. وهذا يجعل التطبيق أسهل في الصيانة وأسهل في التوسع.

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

في المصنع، يتم وضع قواعد الإنشاء في مكان واحد. ويعتمد باقي التطبيق على interface أو النوع الشائع، وليس على تفاصيل الإنشاء الدقيقة.

مشكلة بدون Factory Pattern

تخيل نظام الخروج الذي يدعم طرق الدفع المتعددة. بدون مصنع، قد تحتوي خدمة الخروج على منطق مثل هذا:

if ($type === 'credit_card') {
    $payment = new CreditCardPayment();
} elseif ($type === 'paypal') {
    $payment = new PayPalPayment();
} elseif ($type === 'bank_transfer') {
    $payment = new BankTransferPayment();
} else {
    throw new InvalidArgumentException('Invalid payment method.');
}

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

ويمزج هذا أيضًا بين مسؤوليتين: تحديد الكائن المطلوب إنشاء عملية الأعمال ومعالجتها. يclass Factory Pattern بين هذه المسؤوليات.

مثال أساسي Factory Pattern في PHP

يوضح المثال التالي تنفيذًا بسيطًا لـ Factory Pattern في PHP:

interface PaymentMethod
{
    public function pay(float $amount): bool;
}

class CreditCardPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process credit card payment
        return true;
    }
}

class PayPalPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process PayPal payment
        return true;
    }
}

class BankTransferPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process bank transfer payment
        return true;
    }
}

class PaymentFactory
{
    public static function create(string $type): PaymentMethod
    {
        return match ($type) {
            'credit_card' => new CreditCardPayment(),
            'paypal' => new PayPalPayment(),
            'bank_transfer' => new BankTransferPayment(),
            default => throw new InvalidArgumentException('Invalid payment method.'),
        };
    }
}

في هذا المثال، يكون PaymentFactory مسؤولاً عن إنشاء كائن الدفع الصحيح. يمكن لبقية التطبيق استخدام واجهة PaymentMethod دون معرفة منطق إنشاء الclass الدقيق.

استخدام المصنع في كود التطبيق

بعد إنشاء المصنع، يصبح رمز الخروج أكثر بساطة:

$paymentMethod = PaymentFactory::create('paypal');
$paymentMethod->pay(100);

يطلب التطبيق من المصنع طريقة الدفع ثم يستدعي طريقة الدفع. لا يحتاج رمز الخروج إلى كتلة شرطية طويلة.

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

Factory Pattern والinterfaces

يعمل Factory Pattern بشكل جيد جدًا مع interfaces. تحدد interface السلوك الذي يجب أن توفره الكائنات التي تم إنشاؤها، بينما يقرر المصنع التطبيق الذي يجب إرجاعه.

في مثال الدفع، تقوم كافة فئات الدفع بتنفيذ واجهة PaymentMethod. وهذا يعني أن التطبيق يمكنه استخدام أي كائن دفع بنفس الطريقة.

هذا المزيج يدعم polymorphism. يمكن أن تتصرف فئات الدفع المختلفة بشكل مختلف داخليًا، لكن يمكن أن يتعامل معها الكود الرئيسي على أنها نفس النوع.

Factory Pattern وpolymorphism

يسمح polymorphism للكائنات المختلفة بالاستجابة لنفس الطريقة بطرق مختلفة. غالبًا ما يعمل Factory Pattern مع polymorphism لأنه يُرجع كائنات تشترك في واجهة مشتركة أو parent class.

على سبيل المثال، لدى CreditCardPayment وPayPalPayment وBankTransferPayment جميعها طريقة دفع. يقوم المصنع بإرجاع واحد منهم، ويتم الدفع مقابل مكالمات التطبيق دون التحقق من نوع الكائن الدقيق.

وهذا يقلل من المنطق الشرطي ويساعد في إبقاء الكود مفتوحة للتوسيع.

مثال من العالم الحقيقي: مصنع الإشعارات

حالة الاستخدام الشائعة في العالم الحقيقي لـ Factory Pattern هي نظام الإشعارات. قد يرسل التطبيق إشعارات عبر البريد الإلكتروني أو الرسائل القصيرة أو إشعار الدفع.

interface NotificationSender
{
    public function send(string $message): bool;
}

class EmailSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send email
        return true;
    }
}

class SmsSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send SMS
        return true;
    }
}

class PushNotificationSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send push notification
        return true;
    }
}

class NotificationFactory
{
    public static function create(string $channel): NotificationSender
    {
        return match ($channel) {
            'email' => new EmailSender(),
            'sms' => new SmsSender(),
            'push' => new PushNotificationSender(),
            default => throw new InvalidArgumentException('Invalid notification channel.'),
        };
    }
}

يسمح هذا المصنع للتطبيق بإنشاء مرسل الإشعارات الصحيح بناءً على القناة المحددة.

استخدام مصنع الإشعارات

يمكن لخدمة الإشعارات استخدام المصنع على النحو التالي:

$sender = NotificationFactory::create('email');
$sender->send('Your order has been shipped.');

الكود بسيط ولا يحتاج إلى معرفة كيفية إنشاء EmailSender داخليًا. إذا تم تحديد رسالة نصية قصيرة (SMS) أو إشعار دفع، فسيظل رمز الاتصال نفسه يعمل.

نمط طريقة المصنع

يعد نمط طريقة المصنع أحد الأشكال المختلفة حيث يتم تفويض إنشاء الكائن إلى طريقة يمكن تجاوزها بواسطة classes الفرعية. بدلاً من فئة مصنع واحدة مستقلة، تعد طريقة الإنشاء جزءًا من التسلسل الهرمي للفئة.

يكون هذا مفيدًا عندما يقوم parent class بتعريف عملية ما، لكن classes الفرعية تحدد الكائن الذي يجب إنشاؤه أثناء تلك العملية.

على سبيل المثال، قد يحدد constructor التقارير خطوات إنشاء التقرير الشائعة، بينما تقرر classes الفرعية ما إذا كان سيتم إنشاء مصدر PDF أو مصدر Excel أو مصدر CSV.

مثال على طريقة المصنع في PHP

interface Exporter
{
    public function export(array $data): string;
}

class PdfExporter implements Exporter
{
    public function export(array $data): string
    {
        return 'PDF exported';
    }
}

class ExcelExporter implements Exporter
{
    public function export(array $data): string
    {
        return 'Excel exported';
    }
}

abstract class ReportGenerator
{
    abstract protected function createExporter(): Exporter;

    public function generate(array $data): string
    {
        $exporter = $this->createExporter();

        return $exporter->export($data);
    }
}

class PdfReportGenerator extends ReportGenerator
{
    protected function createExporter(): Exporter
    {
        return new PdfExporter();
    }
}

class ExcelReportGenerator extends ReportGenerator
{
    protected function createExporter(): Exporter
    {
        return new ExcelExporter();
    }
}

في هذا المثال، يحدد parent class ReportGenerator عملية الإنشاء. تحدد classes الفرعية المصدر الذي يجب إنشاؤه.

يكون هذا مفيدًا عندما يعتمد منطق الإنشاء على class الفرعية.

طريقة المصنع البسيطة مقابل طريقة المصنع

عادةً ما يكون المصنع البسيط عبارة عن فئة منclassة أو طريقة ثابتة تقوم بإنشاء كائنات بناءً على الإدخال. إنه سهل الفهم وعملي للعديد من التطبيقات.

يستخدم نمط أسلوب المصنع الوراثة ويسمح للفئات الفرعية بالتحكم في إنشاء الكائن. يكون أكثر تنظيمًا وفائدة عندما تكون عملية الإنشاء متصلة بسير عمل أكبر.

كلا النهجين مفيدان. يعتمد الاختيار الأفضل على مدى تعقيد التطبيق وهدف التصميم.

Factory Pattern في لارافيل

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

على سبيل المثال، قد يستخدم تطبيق Laravel خدمة PaymentFactory التي تنشئ كائنات بوابة الدفع بناءً على التكوين أو اختيار المستخدم.

هذا يبقي وحدات التحكم نظيفة. بدلاً من وضع منطق إنشاء الدفع داخل وحدة التحكم، يمكن لوحدة التحكم الاتصال بخدمة تستخدم المصنع داخليًا.

Factory Pattern في سيمفوني

تستفيد تطبيقات Symfony أيضًا من فئات المصنع وتكوين الخدمة. يمكن للمصنع إنشاء خدمات معقدة، أو اختيار تطبيقات، أو إعداد كائنات تتطلب منطق إنشاء خاصًا.

في مشاريع Symfony الكبيرة، غالبًا ما يتم استخدام المصانع مع Dependency Injection وتعريفات الخدمة. وهذا يمنح المطورين تحكمًا قويًا في إنشاء الكائنات مع الحفاظ على تركيز فئات الأعمال على سلوك الأعمال.

متى تستخدم Factory Pattern

يكون Factory Pattern مفيدًا عندما لا يكون إنشاء الكائن بسيطًا أو عندما يحتاج التطبيق إلى الاختيار بين فئات متعددة مرتبطة.

استخدم Factory Pattern عندما:

  • تعتمد class المحددة المراد إنشاؤها على قواعد الإدخال أو التكوين أو العمل.

  • يتكرر منطق إنشاء الكائنات في العديد من الأماكن.

  • constructors معقدون أو يحتاجون إلى تبعيات متعددة.

  • يجب أن يعتمد التطبيق على interfaces بدلاً من concrete classes.

  • يمكن إضافة تطبيقات جديدة في المستقبل.

  • تريد class منطق الإنشاء عن منطق الأعمال.

يعد Factory مفيدًا بشكل خاص في الأنظمة التي تدعم العديد من مقدمي الخدمات أو التنسيقات أو القنوات أو الاستراتيجيات أو عمليات التكامل.

متى لا تستخدم Factory Pattern

Factory Pattern ليس ضروريًا دائمًا. إذا كان إنشاء الكائن بسيطًا ومن غير المرجح أن يتغير، فإن إضافة مصنع قد يؤدي إلى تعقيد غير ضروري.

تجنب Factory Pattern عندما:

  • لا يوجد سوى فئة واحدة بسيطة يمكن إنشاؤها.

  • constructor بسيط ولا يحتاج إلى منطق خاص.

  • سيقوم المصنع بتغليف المنتجات الجديدة فقط دون إضافة قيمة حقيقية.

  • المشروع صغير جدًا ولا يحتاج إلى تجريد إضافي.

  • هذا النمط يجعل الكود أكثر صعوبة في الفهم.

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

فوائد Factory Pattern

يوفر Factory Pattern العديد من الفوائد العملية في تصميم البرامج الموجهة للكائنات.

تشمل الفوائد الرئيسية ما يلي:

  • مركزية منطق إنشاء الكائن.

  • يقلل من رمز الإنشاء المكرر.

  • يclass منطق العمل عن منطق البناء.

  • يدعم polymorphism والتصميم القائم على interface.

  • يجعل إضافة تطبيقات جديدة أسهل.

  • يحسن قابلية الصيانة في المشاريع المتوسطة والكبيرة.

  • يمكن أن يجعل الاختبار أسهل عند دمجه مع Dependency Injection.

هذه الفوائد تجعل Factory Pattern أحد أنماط التصميم الأكثر فائدة لتطبيقات العالم الحقيقي.

Factory Pattern والكود النظيف

يدعم Factory Pattern الكود النظيفة عن طريق إبقاء إنشاء الكائن خارج الأماكن التي لا ينتمي إليها. يجب أن تركز وحدات التحكم والخدمات وفئات المجال عادةً على مسؤوليتها الرئيسية، وليس على قواعد إنشاء الكائنات المعقدة.

على سبيل المثال، يجب أن تقوم خدمة الدفع بمعالجة سلوك الدفع. ولا ينبغي أن يحتوي على كتل طويلة من الكود التي تحدد كيفية إنشاء كل مزود دفع.

يؤدي نقل منطق الإنشاء إلى المصنع إلى تسهيل قراءة الكود وتغييرها.

Factory Pattern والمبدأ المغلق المفتوح

يمكن لـ Factory Pattern أن يدعم المبدأ المفتوح المغلق عند تصميمه بعناية. ينص المبدأ المفتوح المغلق على أن البرنامج يجب أن يكون مفتوحًا للتوسيع ولكنه مغلق للتعديل.

في المصنع، غالبًا ما يمكن إضافة أنواع كائنات جديدة مع إجراء تغييرات محدودة على التطبيق. على سبيل المثال، قد تتطلب إضافة فئة دفع جديدة تحديث المصنع، بينما تظل خدمة الخروج دون تغيير.

في التصميمات الأكثر تقدمًا، يمكن للمصانع استخدام التكوين أو حاويات الخدمة أو الخرائط لتقليل التعديلات المباشرة بشكل أكبر.

Factory Pattern مع خريطة التكوين

بدلاً من استخدام عبارة مطابقة طويلة أو بيان تبديل، يمكن للمصنع استخدام خريطة التكوين التي تربط المفاتيح بأسماء classes.

class PaymentFactory
{
    private array $methods = [
        'credit_card' => CreditCardPayment::class,
        'paypal' => PayPalPayment::class,
        'bank_transfer' => BankTransferPayment::class,
    ];

    public function create(string $type): PaymentMethod
    {
        if (!isset($this->methods[$type])) {
            throw new InvalidArgumentException('Invalid payment method.');
        }

        return new $this->methods[$type]();
    }
}

هذا النهج يمكن أن يجعل المصنع أسهل في التوسع، خاصة عندما تتزايد قائمة التطبيقات.

Factory Pattern مع Dependency Injection

في التطبيقات الحقيقية، قد تحتاج الكائنات التي تم إنشاؤها إلى تبعيات مثل عملاء API أو المسجلين أو كائنات التكوين أو اتصالات قاعدة البيانات. في هذه الحالة، يمكن للمصنع تلقي التبعيات من خلال constructorه وتمريرها إلى الكائنات التي يقوم بإنشائها.

class PaymentFactory
{
    public function __construct(
        private LoggerInterface $logger
    ) {
    }

    public function create(string $type): PaymentMethod
    {
        return match ($type) {
            'credit_card' => new CreditCardPayment($this->logger),
            'paypal' => new PayPalPayment($this->logger),
            default => throw new InvalidArgumentException('Invalid payment method.'),
        };
    }
}

هذا التصميم أفضل من استخدام الأساليب الثابتة عندما يحتاج المصنع نفسه إلى التبعيات. كما أنه يجعل اختبار المصنع أسهل.

طرق المصنع الثابتة

تستخدم بعض المصانع أساليب ثابتة لإنشاء كائن بسيط. يمكن أن تكون أساليب المصنع الثابتة ملائمة عندما لا تكون هناك حاجة إلى تبعيات.

ومع ذلك، يمكن أن يصبح اختبار المصانع الثابتة أكثر صعوبة عندما تصبح عملية الإنشاء معقدة. إذا كان المصنع يحتاج إلى تبعيات أو تكوين، فعادةً ما يكون المصنع القائم على الكائن أفضل.

كقاعدة عامة، تعتبر أساليب المصنع الثابتة مقبولة للحالات البسيطة، في حين أن خدمات المصنع المحقونة بالتبعية أفضل للتطبيقات الأكبر حجمًا.

الأخطاء الشائعة مع Factory Pattern

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

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

الخطأ الثالث هو إرجاع أشياء غير ذات صلة من نفس المصنع. يجب على المصنع إنشاء كائنات تنتمي إلى نفس العائلة أو تتبع نفس interface.

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

أفضل الممارسات لـ Factory Pattern

لاستخدام Factory Pattern بشكل صحيح، يجب على المطورين إبقاء التصميم مركزًا وعمليًا.

تتضمن أفضل الممارسات المفيدة ما يلي:

  • قم بإنشاء المصانع فقط عندما يكون إنشاء الكائنات معقدًا للغاية.

  • قم بإرجاع interfaces أو الأنواع الأصلية عندما يكون ذلك ممكنًا.

  • حافظ على تركيز فئات المصنع على منطق الإنشاء.

  • تجنب وضع سير العمل داخل المصانع.

  • استخدم Dependency Injection للمصانع التي تحتاج إلى الخدمات.

  • استخدم أسماء واضحة مثل PaymentFactory أو NotificationFactory.

  • احتفظ بأنواع الكائنات ذات الصلة في نفس المصنع.

  • إعادة بناء المصانع الكبيرة عندما يصعب صيانتها.

تساعد هذه الممارسات في الحفاظ على Factory Pattern نظيفًا ومفيدًا.

Factory Pattern مقابل Strategy Pattern

إن Factory Pattern وStrategy Pattern مختلفان، لكن يمكنهما العمل معًا. يركز Factory Pattern على إنشاء الكائنات. يركز Strategy Pattern على اختيار واستخدام السلوك القابل للتبديل.

على سبيل المثال، قد يقوم المصنع بإنشاء إستراتيجية خصم بناءً على نوع العميل. ثم تقوم الإستراتيجية التي تم إنشاؤها بحساب الخصم. في هذه الحالة، يتولى المصنع عملية الإنشاء، بينما تتولى الإستراتيجية التعامل مع السلوك.

يساعد فهم الفرق المطورين على اختيار النمط المناسب للمشكلة الصحيحة.

Factory Pattern مقابل Singleton Pattern

يتحكم Singleton Pattern في عدد المثيلات ويضمن وجود مثيل واحد فقط. يتحكم Factory Pattern في كيفية إنشاء الكائنات وقد ينشئ مثيلات مختلفة أو فئات مختلفة بناءً على الموقف.

Singleton يدور حول كائن مشترك واحد. المصنع يدور حول إنشاء كائنات مرنة.

في التطبيقات الحديثة، غالبًا ما يكون Factory Pattern أكثر أمانًا ومرونة من Singleton لأنه لا يقدم بالضرورة حالة عالمية.

قائمة مرجعية عملية قبل استخدام Factory Pattern

قبل استخدام المصنع، يمكن للمطورين طرح هذه الأسئلة:

  • هل يتكرر إنشاء الكائن في عدة أماكن؟

  • هل يعتمد الclass المراد إنشاؤه على الإدخال أو التكوين؟

  • هل تشترك classes التي تم إنشاؤها في نفس interface؟

  • هل سيتم إضافة تطبيقات جديدة لاحقا؟

  • هل سيقوم المصنع بجعل الكود الرئيسي أكثر نظافة؟

  • هل إنشاء الكائنات معقد بما يكفي ليستحق إنشاء مصنع؟

  • هل يمكن أن يساعد Dependency Injection أو حاوية الخدمة؟

إذا كانت الإجابة بنعم على العديد من هذه الأسئلة، فقد يكون Factory Pattern اختيارًا جيدًا للتصميم.

خاتمة

Factory Pattern هو نمط تصميم إبداعي يعمل على مركزية إنشاء الكائنات وclassها عن منطق العمل الرئيسي. فهو يساعد المطورين على إنشاء كائنات بناءً على الإدخال أو التكوين أو قواعد العمل دون نشر منطق الإنشاء عبر التطبيق.

يعمل Factory Pattern بشكل جيد مع interfaces وpolymorphism. فهو يعمل على تحسين قابلية الصيانة وتقليل الازدواجية وتسهيل توسيع التطبيقات عند إضافة تطبيقات جديدة.

ومع ذلك، يجب استخدام المصنع فقط عندما يحل مشكلة حقيقية. لا يحتاج إنشاء الكائنات البسيطة دائمًا إلى مصنع. عند تطبيقه بشكل صحيح، يعد Factory Pattern واحدًا من أكثر أنماط التصميم عملية وإفادة لتطوير البرمجة كائنية التوجه النظيف وتطوير البرامج في العالم الحقيقي.