مع نمو مشاريع البرمجيات، يصبح من الأهمية المتزايدة الحفاظ على تنظيم الشفرة وصيانتها وقابليتها للتوسيع. هنا تأتي أنماط التصميم دورًا مهمًا. توفر أنماط التصميم حلاً مؤكدًا وقابلاً لإعادة الاستخدام لتحديات تصميم البرمجيات الشائعة، مما يجعل شفرتك أكثر كفاءة وسهولة في الإدارة.
في هذا الدليل، سننغمس بعمق في بعض أشهر أنماط التصميم ونريك كيفية تنفيذها في Spring Boot. في النهاية، لن تفهم هذه الأنماط نظريًا فحسب، بل ستكون قادرًا أيضًا على تطبيقها في مشاريعك الخاصة بثقة.
جدول المحتويات
مقدمة إلى أنماط التصميم
أنماط التصميم هي حلول قابلة لإعادة الاستخدام لمشاكل تصميم البرمجيات الشائعة. فكر بها كأفضل الممارسات المقطرة إلى قوالب يمكن تطبيقها لحل تحديات محددة في كودك. ليست مرتبطة بلغة معينة، لكنها يمكن أن تكون قوية بشكل خاص في جافا بسبب طبيعتها الموجهة نحو الكائنات.
في هذا الدليل، سنغطي:
-
نمط الفردي: ضمان أن تحتوي الفئة على حالة واحدة فقط.
-
نمط المصنع: إنشاء كائنات دون تحديد الفئة الدقيقة.
-
نمط الاستراتيجية: السماح باختيار الخوارزميات في وقت التشغيل.
-
نمط المشاهد: إعداد علاقة نشر-اشتراك.
سنغطي ليس فقط كيفية عمل هذه الأنماط ولكن أيضًا نستكشف كيف يمكن تطبيقها في Spring Boot للتطبيقات الواقعية.
كيفية إعداد مشروع Spring Boot الخاص بك
قبل أن نغوص في الأنماط، دعنا نعد مشروع Spring Boot:
المتطلبات الأساسية
تأكد من أن لديك:
-
جافا 11+
-
مافن
-
أداة سطر الأوامر Spring Boot (اختياري)
-
Postman أو curl (للاختبار)
تهيئة المشروع
يمكنك إنشاء مشروع Spring Boot بسرعة باستخدام Spring Initializr:
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d name=DesignPatternsDemo \
-d javaVersion=11 -o design-patterns-demo.zip
unzip design-patterns-demo.zip
cd design-patterns-demo
ما هو نمط الـ Singleton؟
يضمن نمط Singleton أن يكون هناك مثيل واحد فقط من الفئة ويوفر نقطة وصول عالمية إليه. يُستخدم هذا النمط عادة للخدمات مثل تسجيل الأحداث، إدارة التكوين، أو الاتصال بقواعد البيانات.
كيفية تنفيذ نمط الـ Singleton في Spring Boot
جُميع حاويات Spring Boot هي أنماط Singleton بشكل افتراضي، مما يعني أن Spring تدير تلقائيًا دورة حياة هذه الحاويات لضمان وجود مثيل واحد فقط. ومع ذلك، من المهم فهم كيف يعمل نمط الـ Singleton تحت الغطاء، خاصة عندما لا تكون تستخدم حاويات تديرها Spring أو تحتاج إلى مزيد من التحكم في إدارة المثيل.
لنقم بتوضيح تنفيذ يدوي لنمط الـ Singleton لنظهر كيف يمكنك التحكم في إنشاء مثيل واحد داخل تطبيقك.
الخطوة 1: إنشاء فئة LoggerService
في هذا المثال، سننشئ خدمة تسجيل بسيطة باستخدام نمط الـ Singleton. الهدف هو ضمان استخدام جميع أجزاء التطبيق نفس المثيل للتسجيل.
public class LoggerService {
// المتغير الثابت لاحتواء النسخة الوحيدة
private static LoggerService instance;
// البناء الفردي لمنع الانشاء من الخارج
private LoggerService() {
// هذا البناء فارغ بشكل مقصود لمنع الفصائل الأخرى من إنشاء النسخ
}
// الأسلوب العام لتوفير الوصول إلى النسخة الوحيدة
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// طريقة تسجيل المثال
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
-
المتغير الثابت (
instance
): يحتوي على النسخة الوحيدة منخدمة السجل
. -
البناء الفردي: يتم وضع علامة خاصة على البناء كي يمنع الفصائل الأخرى من إنشاء نسخ جديدة مباشرة.
-
متزامن
getInstance()
الأسلوب: يتم تزامن الأسلوب لجعله آمنًا للمواضيع، مضمنًا أن تتم إنشاء نسخة واحدة فقط حتى لو حاولت عدة مواضيع الوصول إليها بشكل متزامن. -
التهيئة الكسولة: يتم إنشاء النسخة فقط عندما يتم طلبها لأول مرة (
التهيئة الكسولة
)، مما يكون كفء من حيث استخدام الذاكرة.
الاستخدام في العالم الحقيقي: يُستخدم هذا النمط بشكل شائع للموارد المشتركة، مثل تسجيل الأحداث، إعدادات التكوين، أو إدارة اتصالات قواعد البيانات، حيث ترغب في التحكم في الوصول وضمان استخدام نسخة واحدة فقط طوال تطبيقك.
الخطوة 2: استخدام النمط الفردي في تحكم Spring Boot
الآن، دعنا نرى كيف يمكننا استخدام خدمة LoggerService
الفريدة داخل تحكم Spring Boot. سيقوم هذا التحكم بتعريض نقطة نهاية تسجل رسالة كلما تم الوصول إليها.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
@GetMapping("/log")
public ResponseEntity<String> logMessage() {
// الوصول إلى النسخة الفريدة من خدمة LoggerService
LoggerService logger = LoggerService.getInstance();
logger.log("This is a log message!");
return ResponseEntity.ok("Message logged successfully");
}
}
-
نقطة النهاية GET: لقد قمنا بإنشاء نقطة نهاية
/log
التي، عند الوصول إليها، تسجل رسالة باستخدامLoggerService
. -
استخدام الفردي: بدلاً من إنشاء نسخة جديدة من
LoggerService
، نقوم بالاتصال بـgetInstance()
لضمان أننا نستخدم نفس النسخة في كل مرة. -
الرد: بعد التسجيل، يُعيد نقطة النهاية استجابة تشير إلى نجاح العملية.
الخطوة 3: اختبار نمط Singleton
الآن، دعنا نختبر هذه النقطة النهائية باستخدام برنامج Postman أو متصفح الويب الخاص بك:
GET http://localhost:8080/log
الإخراج المتوقع:
-
تسجيل الوحدة:
[LOG] هذه رسالة تسجيل!
-
استجابة HTTP:
تم تسجيل الرسالة بنجاح
يمكنك استدعاء النقطة النهائية عدة مرات، وسترى أن نفس النسخة من LoggerService
مستخدمة، كما يظهر في إخراج التسجيل المتسق.
حالات استخدام النمط Singleton في العالم الحقيقي
ها هي الحالات التي قد ترغب في استخدام نمط Singleton في تطبيقات العالم الحقيقي:
-
إدارة التكوين: تأكد من أن تطبيقك يستخدم مجموعة متسقة من إعدادات التكوين، خاصة عندما تُحمل هذه الإعدادات من ملفات أو قواعد بيانات.
-
مجموعات اتصال قاعدة البيانات: التحكم في الوصول إلى عدد محدود من اتصالات قاعدة البيانات، مضمنًا أن نفس المجموعة مشتركة عبر التطبيق.
-
التخزين المؤقت: الحفاظ على مثيل واحد من ذاكرة التخزين المؤقت لتجنب البيانات غير المتسقة.
-
خدمات التسجيل: كما هو موضح في هذا المثال، استخدم خدمة تسجيل واحدة لتركيز إخراج التسجيل عبر وحدات التطبيق المختلفة.
نقاط رئيسية
-
نمط الـ Singleton هو وسيلة سهلة لضمان إنشاء مثيل واحد فقط من فئة معينة.
-
أمان الخيوط أمر حاسم إذا كانت هناك عدة خيوط تصل إلى الـ Singleton، ولذلك استخدمنا
synchronized
في مثالنا. -
تعد فئات Spring Boot بمثابة Singletons بشكل افتراضي، ولكن فهم كيفية تنفيذها يساعدك على الحصول على مزيد من السيطرة عند الحاجة.
يغطي هذا الموضوع تنفيذ واستخدام نمط Singleton. فيما بعد، سنستكشف نمط Factory لنرى كيف يمكن أن يساعد في تبسيط عملية إنشاء الكائنات.
ما هو نمط Factory؟
يسمح نمط Factory بإنشاء الكائنات دون تحديد الصنف الدقيق. يكون هذا النمط مفيدًا عندما يكون لديك أنواع مختلفة من الكائنات التي يجب إنشاؤها بناءً على بعض المدخلات.
كيفية تنفيذ نمط Factory في Spring Boot
يكون نمط Factory مفيدًا بشكل لا يصدق عندما تحتاج إلى إنشاء الكائنات بناءً على معايير معينة ولكن تريد فصل عملية إنشاء الكائن عن منطق التطبيق الرئيسي الخاص بك.
في هذا القسم، سنقوم ببناء NotificationFactory
لإرسال الإشعارات عبر البريد الإلكتروني أو رسائل SMS. يكون هذا مفيدًا بشكل خاص إذا توقعت إضافة مزيد من أنواع الإشعارات في المستقبل، مثل الإشعارات الفورية أو التنبيهات داخل التطبيق، دون تغيير الشيفرة القائمة.
الخطوة الأولى: إنشاء واجهة Notification
الخطوة الأولى هي تحديد واجهة مشتركة ستنفذها جميع أنواع الإشعارات. يضمن هذا أن كل نوع من أنواع الإشعارات (بريد إلكتروني، رسالة SMS، وغيرها) سيكون لديه طريقة send()
متسقة.
public interface Notification {
void send(String message);
}
-
الغرض: تحدد واجهة
Notification
الاتفاقية لإرسال الإشعارات. يجب على أي فئة تنفيذ هذه الواجهة توفير تنفيذ لطريقةsend()
. -
التوسعية: من خلال استخدام واجهة، يمكنك بسهولة توسيع تطبيقك في المستقبل لتضمين أنواع أخرى من الإشعارات دون تعديل الشفرة الموجودة بالفعل.
الخطوة 2: تنفيذ EmailNotification
و SMSNotification
الآن، دعونا ننفذ فئتين محددتين، واحدة لإرسال الرسائل الإلكترونية والأخرى لإرسال رسائل SMS.
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
الخطوة 3: إنشاء NotificationFactory
تتولى فئة NotificationFactory
إنشاء مثيلات من Notification
بناءً على النوع المحدد. يضمن هذا التصميم عدم الحاجة إلى معرفة NotificationController
تفاصيل إنشاء الكائنات.
public class NotificationFactory {
public static Notification createNotification(String type) {
switch (type.toUpperCase()) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SMSNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
طريقة المصنع (createNotification()
):
-
تأخذ طريقة المصنع سلسلة نصية (
type
) كإدخال وتُرجع مثيلًا من الفئة الإشعار المقابلة. -
بيان التحويل: يختار بيان التحويل نوع الإشعار المناسب استنادًا إلى الإدخال.
-
معالجة الأخطاء: إذا لم يتم التعرف على النوع المقدم، فإنه يطرح استثناء
IllegalArgumentException
. يضمن ذلك اصطياد أنواع غير صالحة في وقت مبكر.
لماذا استخدام مصنع؟
-
فصل الارتباط: يفصل نمط المصنع بين إنشاء الكائن والمنطق التجاري. يجعل هذا الكود الخاص بك أكثر تجزؤًا وأسهل في الصيانة.
-
القابلية للتمديد: إذا كنت ترغب في إضافة نوع إشعار جديد، فإنه يكفي تحديث المصنع دون تغيير منطق التحكم.
الخطوة 4: استخدام المصنع في تحكم Spring Boot
الآن، دعنا نضع كل شيء معًا من خلال إنشاء تحكم Spring Boot يستخدم NotificationFactory
لإرسال الإشعارات استنادًا إلى طلب المستخدم.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
@GetMapping("/notify")
public ResponseEntity<String> notify(@RequestParam String type, @RequestParam String message) {
try {
// إنشاء كائن الإشعار المناسب باستخدام المصنع
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
نقطة النهاية GET (/notify
):
-
يقدم المتحكم نقطة نهاية
/notify
تقبل معاملين استعلام:type
(إما “EMAIL” أو “SMS”) وmessage
. -
يستخدم
NotificationFactory
لإنشاء نوع الإشعار المناسب وإرسال الرسالة. -
معالجة الأخطاء: إذا تم تقديم نوع إشعار غير صالح، يقوم المتحكم بالتقاط
IllegalArgumentException
ويعيد استجابة400 Bad Request
.
الخطوة 5: اختبار نمط المصنع
لنختبر نقطة النهاية باستخدام Postman أو متصفح:
-
إرسال إشعار بريد إلكتروني:
GET http://localhost:8080/notify?type=email&message=Hello%20Email
المخرجات:
إرسال البريد الإلكتروني: Hello Email
-
إرسال تنبيه عبر رسالة نصية قصيرة (SMS):
GET http://localhost:8080/notify?type=sms&message=Hello%20SMS
الناتج:
إرسال رسالة نصية: Hello SMS
-
اختبار بنوع غير صالح:
GET http://localhost:8080/notify?type=unknown&message=Test
الناتج:
طلب خاطئ: نوع التنبيه غير معروف: unknown
حالات الاستخدام العملية لنمط المصنع
نمط المصنع مفيد بشكل خاص في السيناريوهات التي تتضمن:
-
إنشاء كائنات ديناميكي: عندما تحتاج إلى إنشاء كائنات استنادًا إلى إدخال المستخدم، مثل إرسال أنواع مختلفة من التنبيهات، وإنشاء تقارير بتنسيقات مختلفة، أو التعامل مع طرق دفع مختلفة.
-
فصل إنشاء الكائنات: من خلال استخدام مصنع، يمكنك الحفاظ على منطق عملك الرئيسي منفصلًا عن إنشاء الكائنات، مما يجعل كودك أكثر صيانةً.
-
التوسعية: يمكنك بسهولة توسيع تطبيقك لدعم أنواع جديدة من الإشعارات دون تعديل الكود الحالي. كل ما عليك فعله هو إضافة فئة جديدة تنفذ واجهة
Notification
وتحديث المصنع.
ما هو نمط الاستراتيجية؟
يعد نمط الاستراتيجية مثاليًا عندما تحتاج إلى التبديل بين عدة خوارزميات أو سلوكيات بشكل ديناميكي. يتيح لك تعريف عائلة من الخوارزميات، وتجميع كل منها داخل فئات منفصلة، وجعلها قابلة للتبديل بسهولة أثناء التشغيل. هذا مفيد بشكل خاص لاختيار خوارزمية استنادًا إلى شروط محددة، مما يحافظ على نظافة الكود وقابليته للتوسيع والمرونة.
حالة استخدام في العالم الحقيقي: تخيل نظام التجارة الإلكترونية الذي يحتاج إلى دعم خيارات دفع متعددة، مثل بطاقات الائتمان، باي بال، أو تحويلات بنكية. من خلال استخدام نمط الاستراتيجية، يمكنك بسهولة إضافة أو تعديل وسائل الدفع دون تعديل الشيفرة الموجودة بالفعل. يضمن هذا النهج استدامة تطبيقك وقابليته للصيانة عند إدخال ميزات جديدة أو تحديث الموجودة.
سنقدم هذا النمط مع مثال على Spring Boot يتعامل مع عمليات الدفع باستخدام إستراتيجية الدفع إما ببطاقة ائتمان أو PayPal.
الخطوة 1: تعريف واجهة PaymentStrategy
نبدأ بإنشاء واجهة مشتركة ستقوم جميع استراتيجيات الدفع بتنفيذها:
public interface PaymentStrategy {
void pay(double amount);
}
الواجهة تعرف عقدًا لجميع طرق الدفع، مما يضمن التوافق في التنفيذات.
الخطوة 2: تنفيذ استراتيجيات الدفع
إنشاء فئات ملموسة لعمليات الدفع ببطاقة ائتمان و PayPal.
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
}
}
تقوم كل فئة بتنفيذ طريقة pay()
بسلوكها المحدد.
الخطوة 3: استخدام الاستراتيجية في وحدة التحكم
إنشاء وحدة تحكم لتحديد استراتيجية الدفع بشكل ديناميكي استنادًا إلى إدخال المستخدم:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/pay")
public ResponseEntity<String> processPayment(@RequestParam String method, @RequestParam double amount) {
PaymentStrategy strategy = selectPaymentStrategy(method);
if (strategy == null) {
return ResponseEntity.badRequest().body("Invalid payment method");
}
strategy.pay(amount);
return ResponseEntity.ok("Payment processed successfully!");
}
private PaymentStrategy selectPaymentStrategy(String method) {
switch (method.toUpperCase()) {
case "CREDIT": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: return null;
}
}
}
يقبل نقطة النهاية method
و amount
كمعلمات الاستعلام ويعالج عملية الدفع باستخدام الاستراتيجية المناسبة.
الخطوة 4: اختبار النقطة النهائية
-
الدفع ببطاقة الائتمان:
GET http://localhost:8080/pay?method=credit&amount=100
الناتج:
تم الدفع $100.0 باستخدام بطاقة الائتمان
-
الدفع عبر باي بال:
GET http://localhost:8080/pay?method=paypal&amount=50
الناتج:
تم الدفع $50.0 عبر باي بال
-
طريقة دفع غير صالحة:
GET http://localhost:8080/pay?method=bitcoin&amount=25
الناتج:
طريقة الدفع غير صالحة
حالات الاستخدام لنمط الاستراتيجية
-
معالجة الدفع: التبديل بين بوابات الدفع المختلفة ديناميكياً.
-
خوارزميات التصنيف: اختيار أفضل طريقة تصنيف بناءً على حجم البيانات.
-
تصدير الملفات: تصدير التقارير بتنسيقات مختلفة (PDF، Excel، CSV).
أفكار رئيسية
-
نمط الاستراتيجية يحافظ على تقسيم الشفرة الخاصة بك ويتبع مبدأ الفتح/الإغلاق.
-
إضافة استراتيجيات جديدة سهلة—فقط قم بإنشاء فئة جديدة تنفذ واجهة
PaymentStrategy
. -
مناسب للسيناريوهات التي تحتاج فيها إلى اختيار خوارزمية مرنة في وقت التشغيل.
بعد ذلك، سنتعرف على نمط المراقب، الذي يعتبر مثاليًا للتعامل مع البنى المعمارية المدفوعة بالأحداث.
ما هو نمط المراقب؟
نمط المراقب مثالي عندما يكون لديك كائن واحد (الموضوع) الذي يحتاج إلى إعلام عدة كائنات أخرى (المراقبون) حول التغييرات في حالته. إنه مثالي للأنظمة التي تعتمد على الأحداث حيث تحتاج التحديثات إلى أن تكون منقولة إلى مكونات مختلفة دون إنشاء ربط صارم بينها. يتيح لك هذا النمط الحفاظ على تصميم نظيف، خاصةً عندما تحتاج أجزاء مختلفة من نظامك إلى التفاعل مع التغييرات بشكل مستقل.
حالة استخدام العالم الحقيقي: يُستخدم هذا النمط بشكل شائع في الأنظمة التي ترسل إشعارات أو تنبيهات، مثل تطبيقات الدردشة أو تتبع أسعار الأسهم، حيث تحتاج التحديثات إلى أن تكون منقولة إلى المستخدمين في الوقت الحقيقي. من خلال استخدام نمط المراقب، يمكنك إضافة أو إزالة أنواع الإشعار بسهولة دون تعديل المنطق الأساسي.
سنقوم بشرح كيفية تنفيذ هذا النمط في Spring Boot عن طريق بناء نظام إشعار بسيط حيث يتم إرسال كل من الإشعارات عبر البريد الإلكتروني والرسائل النصية عند تسجيل مستخدم جديد.
الخطوة 1: إنشاء واجهة Observer
نبدأ بتعريف واجهة مشتركة ستقوم جميع المراقبين بتنفيذها:
public interface Observer {
void update(String event);
}
الواجهة تنشئ عقدًا حيث يجب على جميع المراقبين تنفيذ طريقة update()
، والتي ستُشغل عندما يتغير الموضوع.
الخطوة 2: تنفيذ EmailObserver
و SMSObserver
بعد ذلك، ننشئ تنفيذين ملموسين لواجهة Observer
للتعامل مع إشعارات البريد الإلكتروني والرسائل النصية.
فئة EmailObserver
public class EmailObserver implements Observer {
@Override
public void update(String event) {
System.out.println("Email sent for event: " + event);
}
}
تتعامل EmailObserver
مع إرسال إشعارات البريد الإلكتروني كلما تم إعلامه بحدوث حدث.
فئة SMSObserver
public class SMSObserver implements Observer {
@Override
public void update(String event) {
System.out.println("SMS sent for event: " + event);
}
}
تتعامل فئة SMSObserver
مع إرسال إشعارات الرسائل النصية عند إخطارها.
الخطوة 3: إنشاء فئة UserService
(الموضوع)
سنقوم الآن بإنشاء فئة UserService
التي تعمل كموضوع، وتقوم بإخطار المراقبين المسجلين لديها عند تسجيل مستخدم جديد.
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private List<Observer> observers = new ArrayList<>();
// طريقة لتسجيل المراقبين
public void registerObserver(Observer observer) {
observers.add(observer);
}
// طريقة لإخطار جميع المراقبين المسجلين بحدوث حدث
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
// طريقة لتسجيل مستخدم جديد وإخطار المراقبين
public void registerUser(String username) {
System.out.println("User registered: " + username);
notifyObservers("User Registration");
}
}
-
قائمة المراقبين: تتتبع جميع المراقبين المسجلين.
-
registerObserver()
الطريقة: تضيف مراقبين جدد إلى القائمة. -
notifyObservers()
الطريقة: تخطر جميع المراقبين المسجلين عند حدوث حدث. -
registerUser()
الطريقة: تسجيل مستخدم جديد وتشغيل إشعارات لجميع المراقبين.
الخطوة 4: استخدام نمط المراقب في وحدة التحكم
أخيرًا، سنقوم بإنشاء تحكم Spring Boot لعرض نقطة نهاية لتسجيل المستخدم. سيقوم هذا التحكم بتسجيل كل من EmailObserver
و SMSObserver
مع UserService
.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController() {
this.userService = new UserService();
// تسجيل المراقبين
userService.registerObserver(new EmailObserver());
userService.registerObserver(new SMSObserver());
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String username) {
userService.registerUser(username);
return ResponseEntity.ok("User registered and notifications sent!");
}
}
-
نقطة النهاية (
/register
): تقبل معلمةusername
وتسجل المستخدم، مشيرةً بذلك إلى جميع المراقبين. -
المراقبون: كل من
EmailObserver
وSMSObserver
مسجلان معUserService
، لذا يتم إخطارهما عند تسجيل مستخدم جديد.
اختبار نمط المراقب
الآن، دعنا نقوم باختبار تنفيذنا باستخدام Postman أو متصفح:
POST http://localhost:8080/api/register?username=JohnDoe
النتيجة المتوقعة في وحدة التحكم:
User registered: JohnDoe
Email sent for event: User Registration
SMS sent for event: User Registration
النظام يسجل المستخدم ويخطر كل من المراقبين البريدي والرسائل النصية، مما يظهر مرونة نمط المراقب.
تطبيقات العالم الحقيقي لنمط المراقب
-
أنظمة الإشعار: إرسال تحديثات للمستخدمين عبر قنوات مختلفة (البريد الإلكتروني، الرسائل النصية، الإشعارات المنبثقة) عند حدوث أحداث معينة.
-
هندسة الأحداث المحفزة: إخطار الأنظمة الفرعية المتعددة عند حدوث إجراءات محددة، مثل أنشطة المستخدمين أو تنبيهات النظام.
-
تدفق البيانات: بث تغييرات البيانات إلى مستهلكين مختلفين في الوقت الحقيقي (على سبيل المثال، أسعار الأسهم الحية أو تغذيات وسائل الاعلام الاجتماعية).
كيفية استخدام حقن الإعتمادية في Spring Boot
حتى الآن، كنا نقوم يدويًا بإنشاء الكائنات لتوضيح أنماط التصميم. ومع ذلك، في تطبيقات Spring Boot العملية، الحقن الإعتمادي (DI) هو الطريقة المفضلة لإدارة إنشاء الكائنات. يتيح لك DI لـ Spring التعامل تلقائيًا مع إنشاء وتوصيل فئاتك، مما يجعل كودك أكثر تجزؤًا وقابلية للاختبار وصيانة.
لنقم بإعادة هيكلة مثالنا على نمط الاستراتيجية للاستفادة من إمكانيات حقن الإعتمادية القوية في Spring Boot. سيتيح لنا ذلك التبديل بين استراتيجيات الدفع بشكل ديناميكي، باستخدام تعليمات Spring لإدارة التبعيات.
تحديث نمط الاستراتيجية باستخدام حقن الإعتمادية في Spring Boot
في المثال الذي تمت إعادة تنظيمه لدينا، سنستفيد من تعليمات Spring مثل @Component
، @Service
، و @Autowired
لتبسيط عملية حقن التبعيات.
الخطوة 1: قم بتوسيم إستراتيجيات الدفع بـ @Component
أولاً، سنضع علامة على تنفيذيات الاستراتيجية بتوسيم @Component
حتى يمكن لـ Spring اكتشافها وإدارتها تلقائيًا.
@Component("creditCardPayment")
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
@Component("payPalPayment")
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
-
@Component
التوسيم: من خلال إضافة@Component
، نخبر Spring بمعاملة هذه الفئات كفولاذ Spring المُدارة. القيمة النصية ("creditCardPayment"
و"payPalPayment"
) تعمل كمعرف الفولاذة. -
المرونة: تتيح لنا هذه الإعدادات التبديل بين الاستراتيجيات باستخدام معرف الفولاذة المناسب.
الخطوة 2: إعادة تنظيم PaymentService
لاستخدام حقن التبعيات
من ثم، دعنا نعدل PaymentService
لنقوم بحقن استراتيجية دفع محددة باستخدام @Autowired
و @Qualifier
.
@Service
public class PaymentService {
private final PaymentStrategy paymentStrategy;
@Autowired
public PaymentService(@Qualifier("payPalPayment") PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
-
توسيم
@Service
: يعلمPaymentService
كفولاذ خدمة مُدارة بواسطة Spring. -
@Autowired
: يقوم Spring بحقن التبعية المطلوبة تلقائيًا. -
@Qualifier
: يحدد أي تنفيذ لـPaymentStrategy
يتم حقنه. في هذا المثال، نحن نستخدم"payPalPayment"
. -
سهولة التكوين: من خلال تغيير قيمة
@Qualifier
ببساطة، يمكنك تبديل استراتيجية الدفع دون تعديل أي منطق عمل.
الخطوة 3: استخدام الخدمة المعاد تنظيمها في وحدة تحكم
لرؤية فوائد هذا إعادة الترتيب، دعنا نحدث الوحدة لاستخدام PaymentService
الخاص بنا:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String makePayment(@RequestParam double amount) {
paymentService.processPayment(amount);
return "Payment processed using the current strategy!";
}
}
-
@Autowired
: يتلقى الوحدة تلقائيًاPaymentService
مع استراتيجية الدفع المحقونة. -
نقطة الوصول GET (
/pay
): عند الوصول إليها، تقوم بمعالجة الدفع باستخدام الاستراتيجية المكونة حاليًا (PayPal في هذا المثال).
اختبار نمط الاستراتيجية المعاد تشكيله بواسطة DI
الآن، دعونا نختبر التنفيذ الجديد باستخدام Postman أو متصفح:
GET http://localhost:8080/api/pay?amount=100
النتيجة المتوقعة:
Paid $100.0 using PayPal
إذا قمت بتغيير المحدد في PaymentService
إلى "creditCardPayment"
، ستتغير النتيجة وفقًا لذلك:
Paid $100.0 with Credit Card
فوائد استخدام حقن الاعتماد
-
الارتباط الضعيف: لا يحتاج الخدمة والتحكم إلى معرفة تفاصيل كيفية معالجة الدفعة. إنها ببساطة تعتمد على Spring لحقن التنفيذ الصحيح.
-
التفكيك: يمكنك بسهولة إضافة طرق دفع جديدة (على سبيل المثال،
BankTransferPayment
،CryptoPayment
) عن طريق إنشاء فئات جديدة تحمل التعليقات@Component
وضبط@Qualifier
. -
القابلية للتكوين: من خلال استفادة من ملفات التعريف في Spring، يمكنك تبديل الاستراتيجيات بناءً على البيئة (على سبيل المثال، التطوير مقابل الإنتاج).
مثال: يمكنك استخدام @Profile
لحقن استراتيجيات مختلفة تلقائيًا بناءً على الملف الشخصي النشط:
@Component
@Profile("dev")
public class DevPaymentStrategy implements PaymentStrategy { /* ... */ }
@Component
@Profile("prod")
public class ProdPaymentStrategy implements PaymentStrategy { /* ... */ }
نقاط مهمة
-
من خلال استخدام DI في Spring Boot، يمكنك تبسيط إنشاء الكائنات وتحسين مرونة كودك.
-
نمط الاستراتيجية المجتمع مع DI يتيح لك التبديل بسهولة بين استراتيجيات مختلفة دون تغيير منطق عملك الأساسي.
-
باستخدام
@Qualifier
وملفات تعريف Spring، يمنحك المرونة لتكوين تطبيقك استنادًا إلى بيئات أو متطلبات مختلفة.
هذا النهج ليس فقط يجعل كودك أكثر نظافة ولكنه يجهزه أيضًا لتكوينات متقدمة أكثر وتوسيعه في المستقبل. في القسم القادم، سنبحث في أفضل الممارسات ونصائح الأمان لرفع تطبيقات Spring Boot الخاصة بك إلى المستوى التالي.
أفضل الممارسات ونصائح الأمان
أفضل الممارسات العامة
-
لا تفرط في استخدام الأنماط: استخدمها فقط عند الضرورة. يمكن أن يؤدي الهندسة الزائدة إلى جعل الشيفرة الخاصة بك أصعب في الصيانة.
-
فضل التركيب على الوراثة: الأنماط مثل الاستراتيجية والمراقب هي أمثلة رائعة لهذه المبادئ.
-
احتفظ بأنماطك مرنة: استخدم الواجهات للحفاظ على شيفرتك مفصولة.
اعتبارات الأداء
-
نمط المفرد: تأكد من أمان الخيوط باستخدام
synchronized
أوتصميم المفرد لبيل بيو
. -
نمط المصنع: خزّن الكائنات إذا كانت مكلفة في الإنشاء.
-
نمط المراقب: استخدم المعالجة غير المتزامنة إذا كان لديك العديد من المراقبين لمنع الحجب.
مواضيع متقدمة
-
استخدام Reflection مع نمط Factory لتحميل الصفوف ديناميكيًا.
-
الاستفادة من Spring Profiles لتبديل الاستراتيجيات استنادًا إلى البيئة.
-
إضافة Swagger Documentation لنقاط نهاية واجهة برمجة التطبيقات الخاصة بك.
الاستنتاج والنقاط الرئيسية
في هذا البرنامج التعليمي، استكشفنا بعض أقوى أنماط التصميم – Singleton، Factory، Strategy، و Observer – وأظهرنا كيفية تنفيذها في Spring Boot. دعنا نلخص بإيجاز كل نمط ونسلط الضوء على أفضل استخدام لكل منها:
نمط Singleton:
-
الملخص: يضمن أن يكون لديك صف واحد فقط ويوفر نقطة وصول عالمية إليه.
-
الأفضل لـ: إدارة الموارد المشتركة مثل إعدادات التكوين، واتصالات قواعد البيانات، أو خدمات تسجيل الدخول. إنه مثالي عندما ترغب في التحكم في الوصول إلى نسخة مشتركة عبر تطبيقك بأكمله.
نمط المصنع:
-
ملخص: يوفر وسيلة لإنشاء الكائنات دون تحديد الفئة الدقيقة التي ستتم إنشاؤها. يقوم هذا النمط بفصل إنشاء الكائن عن منطق الأعمال.
-
الأفضل لـ: السيناريوهات التي تحتاج فيها إلى إنشاء أنواع مختلفة من الكائنات استنادًا إلى شروط الإدخال، مثل إرسال الإشعارات عبر البريد الإلكتروني أو الرسائل النصية أو الإشعارات المنبثقة. إنه رائع لجعل كودك أكثر تعددية وقابلية للتوسيع.
نمط الاستراتيجية:
-
ملخص: يتيح لك تحديد عائلة من الخوارزميات، وكبسولة كل منها، وجعلها قابلة للتبديل. يساعدك هذا النمط في اختيار خوارزمية في وقت التشغيل.
-
الأفضل لـ: عندما تحتاج إلى التبديل بين سلوكيات أو خوارزميات مختلفة بشكل ديناميكي، مثل معالجة طرق الدفع المختلفة في تطبيق التجارة الإلكترونية. يحافظ على مرونة كودك ويتماشى مع مبدأ الفتح/الإغلاق.
نمط المراقب:
-
الملخص: يحدد اعتمادًا من واحد إلى العديد بين الكائنات بحيث عندما يتغير كائن واحد في الحالة، يتم إخطار جميع المعتمدين عليه تلقائيًا.
-
الأفضل لـ: الأنظمة المدفوعة بالأحداث مثل خدمات الإشعارات، التحديثات الفورية في تطبيقات الدردشة، أو الأنظمة التي تحتاج إلى الاستجابة للتغييرات في البيانات. إنه مثالي لفصل مكونات النظام وجعل النظام الخاص بك أكثر توسعًا.
ماذا بعد؟
الآن بعد أن تعلمت هذه الأنماط التصميمية الأساسية، جرب دمجها في مشاريعك الحالية لرؤية كيف يمكن أن تحسن هيكل رمزك وتوسعه. إليك بعض الاقتراحات للاستكشاف الأعمق:
-
قم بالتجربة: جرب تنفيذ أنماط تصميمية أخرى مثل الديكورات، الوكيل، و البنّاء لتوسيع مجموعة أدواتك.
-
الممارسة: استخدم هذه الأنماط لإعادة هيكلة المشاريع الحالية وتعزيز قابليتها للصيانة.
-
المشاركة: إذا كان لديك أي أسئلة أو ترغب في مشاركة تجربتك، لا تتردد في التواصل!
آمل أن يساعدك هذا الدليل على فهم كيفية استخدام أنماط التصميم بشكل فعال في لغة الجافا. استمر في التجربة، واستمتع بالبرمجة!
Source:
https://www.freecodecamp.org/news/how-to-use-design-patterns-in-java-with-spring-boot/