לא מזמן היה לי דיון עם אחד מהעמיתים שלי במהלך code review. דיברנו על גביש נסתר ב-Spring (וגם ב-frameworks אחרים כמו Micronaut) שיכול לפשט את הקוד שלנו.

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

נתחיל עם המימוש הפשוט על ידי הגדרת ה-greeters:

class HebrewGreeter {
    fun sayGoodMorning() {
        println("בוקר טוב")
    }
}

class EnglishGreeter {
    fun sayGoodMorning() {
        println("Good morning")
    }
}

עכשיו ניצור קלאס שמחזיק את ה-greeters ומפעיל אותם:

@Service
class GreeterService(
    private val hebrewGreeter: HebrewGreeter = HebrewGreeter(),
    private val englishGreeter: EnglishGreeter = EnglishGreeter()
) {
    fun greetInAllLanguages() {
        hebrewGreeter.sayGoodMorning()
        englishGreeter.sayGoodMorning()
    }
}

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

כדי לטפל בבעיה, נגדיר interface שעוטף את הפונקציונליות המשותפת של כל ה-greeters:

interface Greeter {
    fun sayGoodMorning()
}

@Component
class HebrewGreeter : Greeter {
    override fun sayGoodMorning() {
        println("בוקר טוב")
    }
}

@Component
class EnglishGreeter : Greeter {
    override fun sayGoodMorning() {
        println("Good morning")
    }
}

@Component
class GermanGreeter : Greeter {
    override fun sayGoodMorning() {
        println("Guten Morgen")
    }
}

עכשיו מגיע החלק המרגש. מאחר שמימשנו interface, ה-GreeterService יכול לבקש מ-Spring להזריק את כל המימושים הזמינים לתוך הקונסטרקטור שלו:

@Service
class GreeterService(private val greeters: List<Greeter>) {
    fun greetInAllLanguages() {
        greeters.forEach { it.sayGoodMorning() }
    }
}

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

אפשרות נוספת במקום List<Greeter> היא להשתמש ב-Map<String, Greeter>. במקרה כזה, Spring יזריק map שבו המפתחות הם שמות הקלאסים המלאים והערכים הם ה-beans המתאימים. לדוגמה, com.example.greeter.EnglishGreeter.