أنماط التصميم في جافا

نمط المصنع الافتراضي والمصنع المجرد

15 دقيقة الدرس 3 من 13

نمط المصنع الافتراضي والمصنع المجرد

يعالج كلا النمطين جذر المشكلة ذاتها: لا ينبغي لكود العميل أن يعرف الصنف الملموس الذي يُنشئه. بإخفاء الكلمة المحجوزة new خلف تابع أو كائن، يمكنك استبدال التنفيذات وإضافة متغيرات جديدة وإجراء الاختبارات بمعزل — كل ذلك دون المساس بالكود الذي يستهلك الكائنات.

لماذا تهمّ طريقة إنشاء الكائنات؟

حين يستدعي صنف new ConcreteProduct() مباشرةً، فهو مرتبط ارتباطًا وثيقًا بذلك التنفيذ. كل موضع يوجد فيه هذا الارتباط يصبح نقطة ألم عند تغيّر المتطلبات. تنقل أنماط المصنع هذا القرار إلى موضع واحد محدد بوضوح، محقّقةً مبدأ الفتح/الإغلاق — مفتوح للتوسّع، مغلق للتعديل.

تمييز جوهري يجب استيعابه: Factory Method يعتمد على الوراثة — يقرّر الصنف الفرعي ما يُنشأ. أما Abstract Factory فيعتمد على التركيب — كائن مصنع مُحقَن يُنشئ مجموعات من الكائنات المترابطة.

نمط المصنع الافتراضي (Factory Method)

عرّف واجهةً لإنشاء كائن، لكن اترك للـصنف الفرعي قرار تحديد الصنف الذي يُنشأ. يملك المُنشئ تابعًا — "تابع المصنع" — مُعلَنًا كـabstract (أو بتنفيذ افتراضي) تُعيد الأصناف الفرعية تعريفه.

// واجهة المنتَج public interface Notification { void send(String message); } // المنتجات الملموسة public class EmailNotification implements Notification { @Override public void send(String message) { System.out.println("Email: " + message); } } public class SmsNotification implements Notification { @Override public void send(String message) { System.out.println("SMS: " + message); } } // المُنشئ — يُعلن عن تابع المصنع public abstract class NotificationSender { // تابع المصنع — تُعيد الأصناف الفرعية تعريفه protected abstract Notification createNotification(); // قالب يستخدم تابع المصنع public void notify(String message) { Notification n = createNotification(); n.send(message); } } // المُنشئون الملموسون — واحد لكل متغيّر public class EmailSender extends NotificationSender { @Override protected Notification createNotification() { return new EmailNotification(); } } public class SmsSender extends NotificationSender { @Override protected Notification createNotification() { return new SmsNotification(); } }

يعمل كود العميل حصرًا مع NotificationSender، ولا يستورد قط EmailNotification أو SmsNotification:

NotificationSender sender = new EmailSender(); // أو SmsSender — تغيير سطر واحد sender.notify("تم شحن طلبك.");
متى تختار Factory Method: عندما تمتلك هرمية منتَج واحدة وتتوقع وصول متغيرات جديدة مع الوقت. كل متغيّر يحصل على صنف منشئ ملموس خاص به. إضافة PushNotification لاحقًا تعني إضافة صنفين فقط — دون تغيير أي كود موجود.

نمط المصنع المجرد (Abstract Factory)

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

تخيّل حزمة واجهة مستخدم يجب أن تعمل على ثيمَي فاتح وداكن. كل ثيم يحتاج إلى Button وCheckbox متطابقَين. مزج Button داكن مع Checkbox فاتح سيبدو مكسورًا — المصنع يمنع هذا الخطأ:

// واجهات المنتجات public interface Button { void render(); } public interface Checkbox { void render(); } // منتجات الثيم الفاتح public class LightButton implements Button { @Override public void render() { System.out.println("[ Light Button ]"); } } public class LightCheckbox implements Checkbox { @Override public void render() { System.out.println("☐ Light Checkbox"); } } // منتجات الثيم الداكن public class DarkButton implements Button { @Override public void render() { System.out.println("[ Dark Button ]"); } } public class DarkCheckbox implements Checkbox { @Override public void render() { System.out.println("■ Dark Checkbox"); } } // المصنع المجرد — تابع لكل نوع منتَج public interface ThemeFactory { Button createButton(); Checkbox createCheckbox(); } // المصانع الملموسة — واحد لكل مجموعة public class LightThemeFactory implements ThemeFactory { @Override public Button createButton() { return new LightButton(); } @Override public Checkbox createCheckbox() { return new LightCheckbox(); } } public class DarkThemeFactory implements ThemeFactory { @Override public Button createButton() { return new DarkButton(); } @Override public Checkbox createCheckbox() { return new DarkCheckbox(); } }

تتلقّى فئة التطبيق المصنع عبر بانيها — حقن التبعية الكلاسيكي:

public class Application { private final Button button; private final Checkbox checkbox; public Application(ThemeFactory factory) { this.button = factory.createButton(); this.checkbox = factory.createCheckbox(); } public void renderUI() { button.render(); checkbox.render(); } } // الربط — هذا السطر الوحيد هو من يقرر الثيم ThemeFactory factory = new DarkThemeFactory(); Application app = new Application(factory); app.renderUI();
قراءة الثيم من الإعدادات: في تطبيق حقيقي تقرأ خاصية (مثل theme=dark) من application.properties أو متغير بيئة، ثم تُنشئ المصنع الصحيح مرةً واحدة في Bootstrap أو حاوي DI. كل صنف في المصبّ يبقى غير مدرك لاختيار الثيم.

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

  • Factory Method — منتَج واحد، التنويع عبر توريث المُنشئ. مناسب لعدد معتدل من المتغيرات.
  • Abstract Factory — منتجات متعددة مترابطة يجب أن تظل متسقة؛ التنويع عبر استبدال كائن المصنع بالكامل. أفضل لمجموعات المنصات/الثيمات/البيئات.
  • غالبًا ما يستخدم Abstract Factory توابع Factory Method داخليًا (كل تابع create* هو فعليًا تابع مصنع).

توابع المصنع الساكنة — قريب خفيف الوزن

تستخدم واجهة برمجة Java نفسها توابع مصنع ساكنة (مثل List.of() وOptional.of() وPath.of()) كبديل أبسط حين لا تكون الوراثة ضرورية. إنها ليست نمط GoF لكنها تشترك في نفس الهدف المتمثل في إخفاء البناء:

public final class Money { private final long cents; private final String currency; private Money(long cents, String currency) { // باني خاص this.cents = cents; this.currency = currency; } public static Money of(long cents, String currency) { if (cents < 0) throw new IllegalArgumentException("Negative money"); return new Money(cents, currency); } public static Money ofDollars(double amount) { return new Money(Math.round(amount * 100), "USD"); } } // المستدعون لا يكتبون new Money(...) أبدًا Money price = Money.ofDollars(9.99);
فخ شائع: الإفراط في استخدام المصانع لكل صنف يُضيف تعقيدًا غير ضروري. طبّق النمط حين تتوقع فعلًا تعدد التنفيذات أو تحتاج لفصل موضع الإنشاء عن تفاصيل البناء. إن وُجد صنف ملموس واحد فقط ولن يتغير، فـnew المباشر أبسط وأوضح.

المقايضات وملاحظات مهنية

  • تتألق أنماط المصنع في تصميم الأطر والمكتبات حيث لا تستطيع المكتبة معرفة الصنف الملموس الذي سيوفره المستهلك.
  • في تطبيقات Spring، حاوي IoC نفسه هو مصنع مجرد — توابع @Bean هي توابع مصنع تستدعيها Spring لبناء سياق التطبيق.
  • يزيد Abstract Factory عدد الواجهات والأصناف. اجعل حدود المجموعات صغيرة ومسمّاة بوضوح لتجنب الالتباس.
  • فضّل معاملات المصنع المبنية على الواجهة (ThemeFactory factory) على الأنواع الملموسة — هذا يُبقي البانيَ قابلًا للمحاكاة في اختبارات الوحدة.

الخلاصة

يفصل Factory Method المُنشئَ عن منتَجه الملموس بتفويض الإنشاء إلى صنف فرعي. يمتد Abstract Factory هذا ليُنشئ مجموعات من الكائنات المترابطة عبر واجهة مشتركة، ضامنًا توافق كل منتجات المجموعة. كلا النمطين يُقلّل الاقتران في موضع البناء ويجعل كودك مفتوحًا للمتغيرات الجديدة دون تعديل المنطق الموجود.