כשהפרויקטים של תוכנה מתרחבים, חשוב יותר ויותר לשמור על הקוד שלך מאורגן, ניתן לתחזוקה וניתן להרחבה. כאן נכנסים לתמונה תבניות העיצוב. תבניות עיצוב מספקות פתרונות מוכחים וניתנים לשימוש חוזר לאתגרים נפוצים בעיצוב תוכנה, מה שהופך את הקוד שלך ליעיל יותר וקל יותר לניהול.

במדריך זה, נצלול לעומק של חלק מתבניות העיצוב הפופולריות ביותר ונראה לך כיצד ליישם אותן ב-Spring Boot. בסוף, לא רק שתבין את התבניות הללו בצורה מושגית אלא גם תוכל ליישם אותן בפרויקטים שלך בביטחון.

תוכן עניינים

מבוא לדפוסי עיצוב

תבניות עיצוב הן פתרונות ניתנים לשימוש חוזר לבעיות עיצוב תוכנה נפוצות. חשבו עליהן כמיטב השיטות מזוקקות לתבניות שניתן ליישם כדי לפתור אתגרים ספציפיים בקוד שלכם. הן אינן ספציפיות לשפה כלשהי, אך הן יכולות להיות חזקות במיוחד בג'אווה בזכות הטבע המונחה-אובייקטים שלהן.

במדריך זה, נעסוק ב:

  • תבנית סינגלטון: הבטחת קיום מופע אחד בלבד של מחלקה.

  • תבנית מפעל: יצירת אובייקטים מבלי לציין את המחלקה המדויקת.

  • תבנית אסטרטגיה: מאפשרת לבחור אלגוריתמים בזמן ריצה.

  • תבנית צופה: הקמת מערכת יחסים של פרסום-מנוי.

לא רק שנעסוק כיצד התבניות הללו פועלות, אלא גם נחקור כיצד ניתן ליישם אותן ב-Spring Boot עבור יישומים בעולם האמיתי.

כיצד להקים את פרויקט ה-Spring Boot שלך

לפני שנצלול לתבניות, נקים פרויקט Spring Boot:

דרישות מוקדמות

ודאו שיש לכם:

  • ג'אווה 11+

  • מאבן

  • Spring Boot CLI (אופציונלי)

  • 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 הם סינגלטונים כברירת מחדל, כלומר, 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): זה מחזיק את המופע היחיד של LoggerService.

  • קונסטרקטור פרטי: הקונסטרקטור מסומן כפרטי כדי למנוע ממחלקות אחרות ליצור מופעים חדשים ישירות.

  • מתודת getInstance(): המתודה מסונכרנת כדי להפוך אותה בטוחה לשרשורים, ומבטיחה שרק מופע אחד ייווצר גם אם מספר שרשורים מנסים לגשת אליה בו זמנית.

  • אתחול עצל: המופע נוצר רק כאשר הוא מבקשו לראשונה (אתחול עצל), שהוא יעיל מבחינת שימוש בזיכרון.

שימוש בעולם האמיתה: תבנית זו נמצאת בשימוש נרחב למשאבים משותפים, כגון רישומים, הגדרות הגדרות או ניהול חיבורי בסיס נתונים, כאשר ברצונך לשלוט בגישה ולוודא כי מופע אחד בלבד משמש בכל אפליקציה שלך.

שלב 2: שימוש ב-Singleton בבקר Spring Boot

עכשיו, בואו נראה איך אפשר להשתמש ב-Singleton שלנו 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() {
        // גישה למופע ה-Singleton של LoggerService
        LoggerService logger = LoggerService.getInstance();
        logger.log("This is a log message!");
        return ResponseEntity.ok("Message logged successfully");
    }
}
  • נקודת גישה GET: יצרנו נקודת גישה /log שכאשר מוגשת, מבצעת רישום באמצעות LoggerService.

  • שימוש ב-Singleton: במקום ליצור מופע חדש של LoggerService, אנו קוראים ל-getInstance() כדי לוודא כי אנו משתמשים באותו מופע בכל פעם.

  • תגובה: לאחר הלוגינג, נקודת הקצה מחזירה תגובה שמציינת הצלחה.

שלב 3: בדיקת תבנית ה-Singleton

עכשיו, בואו נבדוק את נקודת הקצה הזו באמצעות Postman או הדפדפן שלכם:

GET http://localhost:8080/log

פלט צפוי:

  • לוג בקונסולה: [LOG] זהו הודעת לוג!

  • תגובת HTTP: ההודעה נרשמה בהצלחה

אפשר לקרוא לנקודת הקצה מספר פעמים, ותראו שאותו מופע של LoggerService משמש, כפי שמוצג על ידי פלט הלוג עקבי.

מקרים של שימוש ב-Singleton בעולם האמיתי

כאן כיצד ייתכן שתרצו להשתמש בתבנית ה-Singleton ביישומים בעולם האמיתי:

  1. ניהול הגדרות: הבטיחו שיישמה שלכם משתמשת בסט קבוע של הגדרות, במיוחד כאשר ההגדרות הללו נטענות מקבצים או מבסיסי נתונים.

  2. בריכות חיבור למסד נתונים: שליטה בגישה למספר מוגבל של חיבורים למסד נתונים, מבטיחים כי הבריכה תשותפה בכל היישום.

  3. מטמון: שמירה על אינסטנס מטמון אחת כדי למנוע נתונים חסרי עקביות.

  4. שירותי יומן: כפי שמוצג בדוגמה זו, השתמשו בשירות יומן יחיד כדי למרכז את פלטי היומן בין מודולים שונים של היישום שלכם.

נקודות מרכזיות

  • התבנית של ה- Singleton היא דרך קלה להבטיח כי רק אינסטנס אחת של מחלקה תיווצר.

  • בטיחות התהליך חיונית אם ישנם תהליכים מרובים שמגיעים אל ה- Singleton, ולכן השתמשנו ב- synchronized בדוגמה שלנו.

  • ה- beans של Spring Boot כבר הם singletons כברירת מחדל, אך הבנת כיצד ליישם זאת באופן ידני עוזרת לכם להשיג יותר שליטה כשזה נדרש.

זה מכסה את המימוש ושימוש בתבנית הסינגלטון. לאחר מכן, נחקור את תבנית המפעל כדי לראות כיצד היא עשויה לעזור לך לשפר את תהליך יצירת האובייקטים.

מהו דפוס המפעל?

תבנית המפעל מאפשרת לך ליצור אובייקטים מבלי לציין את המחלקה המדויקת. תבנית זו שימושית כאשר יש לך סוגים שונים של אובייקטים שיש ליצור על סמך קלט מסוים.

כיצד ליישם מפעל ב-Spring Boot

תבנית המפעל מאוד שימושית כאשר נדרש ליצור אובייקטים על סמך קריטריונים מסוימים אך רוצים לנתק את תהליך יצירת האובייקט מהלוגיקה העיקרית של היישום שלך.

במקטע זה, נלמד כיצד לבנות "מפעל הודעות" כדי לשלוח הודעות דרך דוא"ל או SMS. זה מאוד שימושי במיוחד אם אתה מעריך להוסיף סוגים נוספים של התראות בעתיד, כמו התראות דחיפה או התראות באפליקציה, מבלי לשנות את הקוד הקיים שלך.

שלב 1: צור את ממשק "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) כקלט ומחזירה מופע של מחלקת ההתראה המתאימה.

  • הצהרת Switch: ההצהרת switch בוחרת את סוג ההתראה המתאים בהתאם לקלט.

  • טיפול בשגיאות: אם הסוג שסופק לא מוכר, זה יזרוק 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 או דפדפן:

  1. שליחת התראת אימייל:

     GET http://localhost:8080/notify?type=email&message=Hello%20Email
    

    פלט:

     שולח אימייל: Hello Email
    
  2. שליחת הודעת SMS:

     GET http://localhost:8080/notify?type=sms&message=Hello%20SMS
    

    פלט:

     שולח SMS: Hello SMS
    
  3. בדיקה עם סוג לא חוקי:

     GET http://localhost:8080/notify?type=unknown&message=Test
    

    פלט:

     בקשה לא תקינה: סוג הודעה לא ידוע: unknown
    

מקרים שימושיים במצב חיים אמיתיים עבור תבנית המפעל

תבנית המפעל עוזרת במיוחד במקרים בהם:

  1. יצירת אובייקטים דינמיים: כאשר נדרש ליצור אובייקטים בהתאם לקלט מהמשתמש, כמו שליחת סוגי הודעות שונים, יצירת דוחות בפורמטים שונים או טיפול בשיטות תשלום שונות.

  2. נפרדת בין יצירת עצמים: על ידי שימוש במפענח, תוכל לשמור על הלוגיקה העסקית הראשית שלך מופרדת מיצירת העצמים, וכך לשפר את ניתוח הקוד שלך.
  3. קידמות: אפשר להרחיב בקלות את היישום שלך כך שיתמוך בסוגים חדשים של התראות בלי לשנות את הקוד הקיים. פשוט הוסף מחלקה חדשה שמיישמת את ממשק ה־Notification ועדכן את המפענח.

מהו דפוס האסטרטגיה?

דפוס האסטרטגיה הוא מושלם כאשר תצטרך להחליף בין מספר אלגוריתמים או התנהגויות באופן דינמי. הוא מאפשר לך להגדיר משפחת אלגוריתמים, לאחסן כל אחד מהם במחלקות נפרדות, ולהפוך אותם לניתנים להחלפה בזמן ריצה. דבר זה מאוד שימושי לבחירת אלגוריתם בהתאם לתנאים ספציפיים, ובכך לשמור על קוד נקי, מודולרי וגמיש.

מקרה שימוש בעולם האמיתי: דמיין מערכת מסחר אלקטרוני שצריכה לתמוך באפשרויות תשלום מרובות, כמו כרטיסי אשראי, PayPal או העברות בנקאיות. על ידי שימוש בתבנית ה-Strategy, ניתן להוסיף או לשנות בקלות אמצעי תשלום ללא לשנות את הקוד הקיים. הגישה הזו מבטיחה שהיישום שלך יישאר נתמך ונתחזק כאשר אתה מוסיף תכונות חדשות או מעדכן קיימות.

נדגים את התבנית הזו עם דוגמה ב-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: בדיקת הנקודת קצה

  1. תשלום בכרטיס אשראי:

     GET http://localhost:8080/pay?method=credit&amount=100
    

    פלט: שולם $100.0 באמצעות כרטיס אשראי

  2. תשלום ב-PayPal:

     GET http://localhost:8080/pay?method=paypal&amount=50
    

    פלט: שולם $50.0 דרך PayPal

  3. שיטה לא חוקית:

     GET http://localhost:8080/pay?method=bitcoin&amount=25
    

    פלט: שיטת תשלום לא חוקית

מקרי שימוש עבור תבנית האסטרטגיה

  • עיבוד תשלומים: החלפה דינמית בין שערי תשלום שונים.

  • אלגוריתמי מיון: בחירת השיטת מיון הטובה ביותר בהתאם לגודל הנתונים.

  • ייצוא קבצים: ייצוא דוחות בפורמטים שונים (PDF, Excel, CSV).

מידע מרכזי

  • התבנית האסטרטגית שומרת על קוד מודולרי ועוקבת אחר עקרון הפתוח/סגור.

  • הוספת אסטרטגיות חדשות היא קלה – רק צריך ליצור מחלקה חדשה שמיישמת את ממשק ה־PaymentStrategy.

  • זה אידיאלי לתרחישים בהם נדרשת בחירה גמישה באלגוריתם בזמן ריצה.

בהמשך, נחקור את התבנית המעקב, שמושלמת לטיפול בארכיטקטורות המבוססות על אירועים.

מהו תבנית המעקב?

דפוס ה-Observer אידיאלי כאשר יש לך אובייקט אחד (הנושא) שצריך להודיע על שינויים במדינה שלו למספר אובייקטים אחרים (צופים). זה מושלם עבור מערכות מונעות אירועים שבהן עדכונים צריכים להידחף לרכיבים שונים מבלי ליצור חיבור הדוק ביניהם. דפוס זה מאפשר לך לשמור על ארכיטקטורה נקייה, במיוחד כאשר חלקים שונים של המערכת שלך צריכים להגיב לשינויים באופן עצמאי.

מקרה שימוש בעולם האמיתי: דפוס זה בשימוש נפוץ במערכות ששולחות התראות או הודעות, כגון יישומי צ'אט או מעקבי מחירי מניות, שבהם עדכונים צריכים להידחף למשתמשים בזמן אמת. באמצעות דפוס ה-Observer, אתה יכול להוסיף או להסיר סוגי התראות בקלות מבלי לשנות את הלוגיקה המרכזית.

נמחיש איך ליישם דפוס זה ב-Spring Boot על ידי בניית מערכת התראות פשוטה שבה נשלחות התראות בדואר אלקטרוני וב-SMS בכל פעם שמשתמש נרשם.

שלב 1: צור ממשק Observer

נبدأ בהגדרת ממשק משותף שכל הצופים יישמו:

public interface Observer {
    void update(String event);
}

הממשק קובע חוזה שבו כל הצופים חייבים ליישם את המתודה update(), שתופעל בכל פעם שהנושא משתנה.

שלב 2: יישם את EmailObserver ואת SMSObserver

לאחר מכן, אנו יוצרים שתי יישומים קונקרטיים של ממשק Observer כדי לטפל בהתראות בדואר אלקטרוני וב-SMS.

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 טופלת בשליחת התראות SMS בכל פעם שהיא מתריעה.

שלב 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: שימוש בתבנית ה-Observable בבקר

לבסוף, ניצור בקר 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

המערכת מרשה את המשתמש ומתריעה גם לצופי האימייל וה-SMS, מדגימה את הגמישות של תבנית הצופה.

יישומים בעולם האמיתי של תבנית הצופה

  1. מערכות התראה: שליחת עדכונים למשתמשים דרך ערוצים שונים (אימייל, SMS, התראות דחיפה) כאשר אירועים מסוימים מתרחשים.

  2. ארכיטקטורות שמופעלות על פי אירועים: הודעה לכמה מערכות משניות כאשר פעולות ספציפיות מתרחשות, כגון פעילויות משתמש או התראות מערכתיות.

  3. זרימת נתונים: שידור שינויים בנתונים לצרכנים שונים בזמן אמת (לדוגמה, מחירי מניות חיים או עדכוני מדייה חברתית).

כיצד להשתמש בהזנה בפעולת התלות של 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): כאשר נגיש, הוא מעבד תשלום באמצעות האסטרטגיה שמוגדרת כעת (פייפאל בדוגמה זו).

בדיקת תבנית האסטרטגיה המשופצת עם DI

עכשיו, בואו נבדוק את המימוש החדש באמצעות Postman או דפדפן:

GET http://localhost:8080/api/pay?amount=100

פלט צפוי:

Paid $100.0 using PayPal

אם תשנו את ה-qualifier ב-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 עם תבנית המפעל להעמסת מחלקות דינמית.

  • ניצול פרופילים של Spring כדי להחליף אסטרטגיות על פי הסביבה.

  • הוספת תיעוד Swagger לנקודות הקצה של ה-API שלך.

סיכום ומסקנות מרכזיות

במדריך זה, חקרנו כמה מהתבניות העיצוב החזקות ביותר—סינגלטון, מפעל, אסטרטגיה וצופה—והדגמנו כיצד ליישם אותן ב-Spring Boot. בואו נסכם בקצרה כל תבנית ונדגיש עבור מה היא מתאימה ביותר:

תבנית סינגלטון:

  • סיכום: מבטיחה שלמחלקה יש רק מופע אחד ומספקת נקודת גישה גלובלית אליו.

  • הטוב ביותר עבור: ניהול משאבים משותפים כמו הגדרות קונפיגורציה, חיבורי מסדי נתונים או שירותי רישום. זה אידיאלי כשאתה רוצה לשלוט בגישה למופע משותף בכל יישום שלך.

תבנית המפעל:

  • סיכום: מספקת אופן ליצירת אובייקטים מבלי לציין את המחלקה המדויקת שיש ליצור. תבנית זו מבדילה בין יצירת האובייקט מלוגיקת העסק.

  • מתאים ביותר ל: תרחישים בהם יש צורך ליצור סוגים שונים של אובייקטים בהתאם לתנאי הקלט, כמו שליחת הודעות דרך דואר אלקטרוני, הודעות SMS או התראות דחיפות. זה מצוין להפוך את הקוד שלך לקיים ולהרחיב.

תבנית האסטרטגיה:

  • סיכום: מאפשרת לך להגדיר משפחת אלגוריתמים, לאטום כל אחד מהם ולהפוך אותם לניתנים להחלפה. תבנית זו עוזרת לך לבחור אלגוריתם בזמן ריצה.

  • מתאים ביותר ל: כאשר יש צורך להחליף בין התנהגויות או אלגוריתמים שונים באופן דינמי, כגון עיבוד שיטות תשלום שונות באפליקציה למסחר אלקטרוני. זה משמר את הגמישות של הקוד שלך ומצטיין במתקפל/סגור.

עיקר התבנית "עוקב":

  • סיכום: מגדיר תלות של אחד לרבים בין אובייקטים כך שכאשר אובייקט אחד משנה מצב, כל התלויים בו מתרעים באופן אוטומטי.

  • מתאים ביותר ל: מערכות המונעות את האירועים כמו שירותי התראה, עדכונים בזמן אמת באפליקציות צ'אט, או מערכות שצריכות להגיב לשינויים בנתונים. זה אידיאלי לפרידת רכיבים ולהפעלת המערכת שלך באופן יותר קליט וגמיש.

מה הבא?

עכשיו שלמדת את תבניות העיצוב החיוניות הללו, נסה לשלב אותן בפרויקטים הקיימים שלך כדי לראות איך הן יכולות לשפר את מבנה הקוד והקיבולת שלך. הנה כמה הצעות לחקירה נוספת:

  • נסה: ליישם תבניות עיצוב נוספות כמו Decorator, Proxy, ו-Builder כדי להרחיב את הכלי שלך.

  • תרגול: השתמשו בדפוסים אלו כדי לשדרג פרוייקטים קיימים ולשפר את ניתוחם.
  • שיתוף: אם יש לכם שאלות או ברצונכם לשתף את החוויה שלכם, אל תהססו ליצור קשר!

אני מקווה שמדריך זה עזר לכם להבין כיצד להשתמש ביעילות בדפוסי עיצוב ב־Java. המשיכו לנסות, ושעות קוד שמחות!