Java singleton – the next generation

סינגלטון הוא אחד התבניות הנפוצות בתוכנה.
בגדול, סינגלטון הוא אובייקט יחיד מסוג מסויים במערכת, שכולל פונקציה סטטית שמאפשרת גישה נוחה לאובייקט מכל מקום במערכת.
למשל, אם יש אובייקט שמטפל בשליחת אימיילים, סביר שהוא יהיה סינגלטון כי אין ברוב התוכנות משמעות לכמה אובייקטים שיטפלו בשליחת אימיילים.
בדרך כלל כותבים סינגלטון ככה:
[code lang="java"]
public class EmailSender
{
private static EmailSender s_instance;

private EmailSender(){} // private constructor

public static synchronized EmailSender getInstance()
{
if (s_instance == null)
s_instance = new EmailSender();
return s_instance;
}

public void sendEmail(String recepient, String subject, String text)
{
// …
}
}
[/code]
כדי להשתמש בסינגלטון ככה:
[code lang="java"]
EmailSender.getInstance().sendEmail(…);
[/code]
כמה דברים לשים אליהם לב:
* יש בנאי פרטי, כדי למנוע מכל מני גורמים "לא מוסמכים" לאתחל עותק משל עצמם של הסינגלטון.
* הפונקציה getInstance צריכה להיות מסונכרנת כדי למנוע מצב שבו שני ת'רדים שונים ניגשים במקביל לפונקציה וגורמים לאיתחול של שני אובייקטים במקום אחד.

עד כאן, טרויאלי לכל מי שכתב תוכנה בג'אווה.
הפוסט הזה הוא על דרך אלטרנטיבית לכתיבת סינגלטון, והרי היא:
[code lang="java"]
public enum EmailSender
{
instance;

public void sendEmail(String recepient, String subject, String text)
{
// …
}
}
[/code]
אפשר להשתמש בenum עם קבוע בודד, ולמרבה ההפתעה אפשר להכניס פונקציות ומשתנים בהגדרה של הenum.
מותר גם (אבל לא חייבים) להגדיר בנאי פרטי.
השפה מונעת את בעיית הגישה מכמה ת'רדים, וכן אין בכלל קונספט של איתחול יזום של enum.
כדי להשתמש בסינגלטון ככה:
[code lang="java"]
EmailSender.instance.sendEmail(…);
[/code]
אלגנטי, לא?

Facebook Comments
קטגוריותJava

10 תגובות בנושא “Java singleton – the next generation”

  1. לדעתי, לא כל כך אלגנטי.
    כן, זה האק נחמד.

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

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

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

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

  4. הכי אלגנטי לדעתי, והדרך שבה אני משתמש כל הזמן, היא הדרך בה המופע מאותחל כבר בנקודת ההגדרה:
    [code lang="java"]
    public class EmailSender {
    private static EmailSender instance = new EmailSender();

    private EmailSender() {
    super();
    }

    public static EmailSender getInstance() {
    return instance;

    }
    }
    [/code]
    ככה ה-JVM עצמו דואג לסינכרוניזציה.

  5. @עמרי – נניח שאתה בונה פריימוורק. ובתוכו אתה בונה בסיס אבסטקרטי לשליחת דוארץ אתה ממש מופע שלו עם שימוש במיילר של ג'אווה, אבל עכשיו משתמש של הפרימוורק רוצה לדרוס אותו, ולהשתמש בספק חיצוני (נגיד, סיילת'רו). דוגמא סבירה?
    דוגמא נוספת תהיה בסיס אבסטרקטי ששומר מידע, ומאפשר למשתמש לדרוס אותו ולעשות שימוש בבסיסי נתונים שונים, מערכת קבצים, וכו'.
    או לוגגר גלובאלי, שאותו צריך להרחיב כדי לקבוע לאן לכתוב, ואיך.

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

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

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

    אייל, נקודה טובה – אבל פתירה, למשל ככה:
    [code lang="java"]
    interface ISender
    {
    public void send();
    }

    public enum Sender implements ISender
    {
    instance;

    private ISender delegate;

    private Sender()
    {
    String scz = System.getProperty("sender.class");
    if (scz == null) scz = "DefaultSender";
    try
    {
    delegate = (ISender) Class.forName(scz).newInstance();
    } catch (Exception e)
    {
    e.printStackTrace();
    }
    }

    @Override
    public void send()
    {
    delegate.send();
    }
    }
    [/code]

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

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

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

  8. Gersht, רק הערה שהפתרון של אתחול המופע בעת הגדרת חבר מחלקה סטטי הוא כן "עצלן" במידה מסויימת. ברוב מימושי ה-JVM הCLASS לא יטען עד לגישה הראשונה אליו, ולכן זה אכן "עצלן" בפועל. נסה עם דיבאגר ותיווכח.

  9. הממ… במקרה קראתי את המאמר, העתיק, הבא: http://java.sun.com/developer/Books/shiftintojava/page1.html#replaceenums
    די ברור שנכתב טרם JAVA5, מכיוון שהוא מפרט design pattern ליצירת enum באמצ' מחלקה "רגילה".
    מייד נזכרתי בפוסט שלך. אני מניח שהמימוש של ENUM מאוד דומה ל- design pattern של "יהושע בלוך" (מוכן לקחת את העברית עד הסוף…).
    על בסיס המאמר של יהושוע הפרוע; מאחורי הקלעים (כנראה ברמת הבייטקוד) מתבצע הדבר הבא (שגורם לסינגלטון שלך לעבוד):

    public static final EmailSender instanse = new EmailSender()

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

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

סגור לתגובות.