Saturday, April 5, 2014

Design Patterns : Singleton

"Gang Of Four" (GOF)-н 1994 онд гаргасан "Design Patterns: Elements of Reusable Object-Oriented Software" номны
23 pattern-н бараг хамгийн энгийн ойлгомжтой нь гэж хэлж болохоор нь энэ Singleton Pattern юм.

Singleton Pattern (SP)-г ямарваа нэг class-с тухайн системд (ажиллаж буй програм дотор) зөвхөн ганц л instance 
байлгах шаардлагатай үед ашигладаг. Өөрөөр хэлбэл, програмыг ажиллаж эхлээд дуусах хүртэл, тухайн class-с нэгээс
олон instance үүсэхгүй гэсэн үг. Системийн онцлогоос шалтгаалж, үнэхээр ганц л байх зайлшгүй шаардлагатай 
тохиолдолуудад, үүнийг ашиглана. Хамгийн өргөн хэрэглэгддэг жишээ бол Logger class юм. Програмаас гарч буй бүхий
л лог зөвхөн нэг л газраар дамжиж нэг л байдлаар хадгалагдана. Мөн өөр нэг хэрэглээ нь, тухайн програмын ажиллаж
буй орчины талаарх мэдээлэл байж болно. Java-н java.lang.Runtime class нь жишээ нь Singleton юм. Мөн ADF-н 
ADFFacesContext, ADFContext нь мөн л ялгаагүй Singleton.

Энэ нь private constructor, static method/variable зэргийг ойлгоход (хэрэглээ талаас нь) их хэрэг болох pattern
байгаа юм.


Бүтэц нь гэвэл их энгийн. Class-н constructor нь заавал private байна. Ингэснээрээ, тухайн class-н instance-г 
гаднаас үүсгэх боломжгүй болох юм. Мөн instance үүсгэх болон үүссэн instance-г нь авах зорилго, public 
хандалттай (хүсвэл өөр байж болно.) static method байна. Энэ method-н үүрэг  нь, тухайн class-с ямар нэг 
instance үүссэн эсэхийг шалгаад үүсээгүй бол үүсгээд түүнийгээ буцаах юм. Яагаад static байх шаардлагатай вэ 
гэхээр, static байж байж, ямар нэг instance-р дамжихгүйгээр(угаасаа байхгүй шд) шууд энэ method-г дуудах 
боломжтой болох юм. Энэ method-н нэрийг ихэвчлэн getInstance() гэж нэрлэдэг. Нэгэнт үүсгэсэн instance-аа нэг 
газар хадгалаад, дараагийн дуудалтан дээрээ буцаах хэрэгтэй учраас, private static variable ашиглана.


Implementations:

1.  Singleton class-н хамгийн энгийн implementation доорх хэлбэртэй байж болно.

    public class Singleton {
        private static Singleton final theInstance = new Singleton();

        public static Singleton getInstance() {
            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Тухайн class load хийгдэх мөчид, шинэ instance үүсээд theInstance variable-д утгаа өгчихнө. Дараа нь 
    шаардлага гараад getInstance method дуудагдах үед зүгээр л тэр variable-нхаа утгыг буцаачихна. 

2.  Lazy-loading. Class-н шинэ instance үүсгэх процесс нь их хүнд (үнэтэй) бол, мөн class програм ажиллах
    бүрд ашиглагдахгүй байх магадлалтай тохиолдолд, дээрх арга тийм зөв сонголт болохгүй байх. Харин зөвхөн
    шаардлагатай үед л шинэ instance үүсгэдэг доорх арга илүү зөв болох талтай. Хамгийн эхний getInstance
    дуудагдах үед, шинээр instance үүснэ. Тэрний дараагаас зүгээр л variable-нхаа утгыг буцаана. 
    
    public class Singleton {
        private static Singleton final theInstance = null;

        public static Singleton getInstance() {
            if (theInstance == null) {
                theInstance = new Singleton();
            }

            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Thread-Safe. Гэвч дээрх аргыг олон thread зэрэг хандах орчинд ашиглавал, нэгээс олон instance үүсчих
    боломжтой. Тэгэхээр getInstance() method-г synchronized болгосноор асуудлыг шийдэж болно.
    
        public static synchonized Singleton getInstance() {

    Гэвч, хамгийн эхний удаад л instance үүсгээд, бусад бүх дуудалтанд зүгээр бэлэн утга буцаадаг энэ method-г 
    synchronized болгосноороо, performance талаасаа их асуудалтай болж ирнэ. Зүгээр нэг утга буцаахын төлөө 
    lock/unlock/wait хийгдэнэ гэсэн үг. 

    Double-Checked-Locking. Дээрх асуудлыг шийдэхийн тулд, бид эхлээд variable-нхаа утгыг шалгаад, хэрэв
    instance үүссэн бол шууд буцаадаг, үүсээгүй бол synchronized block руу ордогоор хийе.

    public class Singleton {
        private static Singleton final theInstance = null;

        public static Singleton getInstance() {
            if (theInstance == null) {
                synchronized(Singleton.class) {
                    if (theInstance == null) {
                        theInstance = new Singleton();
                    }
                }
            }

            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }
    
    Ингэснээрээ, эхнийхээс бусад бүх дуудалт ямар нэг саадгүйгээр хурдан хэрэгжинэ. Харин эхний удаад, эхлээд
    null эсэхийг нь шалгаад, дараа нь synchronized block руу ороод, дахиад null эсэхийг шалгаад, тэгээд
    instance-аа үүсгэнэ. Яагаад яг адилхан шалгалтыг 2 удаа хийгээд байгаад гайхаж магадгүй. Яагаад гэвэл, жишээ
    нь t1 thread эхний шалгуурыг даваад, synchronized block руу орохын өмнөхөн нь t2 thread бас тэр шалгуурыг
    даваад орох ирэх боломжтой. Энэ үед хэрэв дотор талын шалгуур байхгүй байсан бол, 2 thread ээлжээр
    synchronized block руу орж instance-аа үүсгэх байсан юм. Ингээд бидний Singleton худлаа болох байлаа. Давхар
    шалгуур тавьж өгснөөрөө 2дахь thread synchronized block руу орсон ч, өмнө нь орсон thread нь үүсгэсэн байна
    уу, гэдгийг шалгаж байгаа юм. 

    Java-н хувьд Double-Checked-Locking арга нь "эвдэрхий" гэж үзэгддэг байна. Яагаад гэвэл, 
        1.  Thread A нь instance үүсээгүй байна гэж бодоод lock хийгээд instance-аа үүсгэж эхэллээ гэж бодъё
        2.  Java-д ялангуяа, тухайн constructor ажиллаж эхлэх үед л, санах ойд авсан (allocate) хаягаа шууд 
            хувьсагчдаа (theInstance) оноочихдог байна. Thread A-г instance-аа бүрэн үүсгэж дуусаагүй байх үед
            шүү дээ. Өөрөөр хэлбэл, манай static хувьсагч дутуу үүссэн обьектыг зааж байна гэсэн үг.
        3.  Яг энэ үед Thread B getInstance() method руу орж ирээд хувьсагчийнхаа утгыг хоосон байна уу гэж 
            шалгана. Мэдээж тэнд дутуу ч гэсэн нэг хаяг оноочихсон байгаа учир, түүнийгээ аваад буцна. Тэгээд 
            "дутуу үүссэн" instance-г ашиглах гэж оролдоод алдаа заана.

    Үүнээс үүдэн Java 5.0-н memory model-д өөрчлөлт орж, volatile түлхүүр үгийн ажиллах зарчим ч шинэчлэгдэж. 
    Volatile-г нэмж ашиглан дээрх аргыг алдаагүй болгосон байна.

3.  On-Demand-Holder. Нэгэнт double-checked-locking нь эвдэрхий гэж үзэгдсэн учир, мөн synchronized,
    volatile гэх мэт их "үнэтэй" аргуудыг ашиглаж буй учир, илүү хөнгөн аргыг бодож олцгоожээ. Энэ нь бидний
    эхний аргыг inner class ашиглан хийх юм. 
    
    public class Singleton {
        private static class SingletonHolder {
            private static final Singleton theInstance = new Singleton();
        }

        public static Singleton getInstance() {
            return SingletonHolder.theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Singleton class нь load хийгдэж байх үед, SingletonHolder class load хийгдэхгүй. Хэзээ load хийгдэх вэ
    гэхээр, getInstance() method дуудагдах үед л хийгдэнэ. Тэр бол ямар ч Singleton-н instance үүсэхгүй гэсэн
    үг. Энэ нь lazy-loading гэдгийг нь батална. Харин getInstance() method дуудагдахад, JVM SingletonHolder-г
    load хийх хэрэгтэйгээ мэдээд (мэдээж өмнө нь хийгээгүй бол шүү дээ) load хийж эхэлнэ. Java-н ямар нэг 
    class-г load хийх үйлдэл нь өөрөө serial буюу давхар дуудагдах асуудалгүй учир бид SingletonHolder-н load
    хийгдэж, доторх Singleton-н reference-дээ шинэ intance үүсгэжн оноож өгөх процесс нь аюулгүй гэдэгт бүрэн
    итгэж болно. Энэ нь thread-safe гэдгийг нь харуулна. Нэгэнт class load хийгдэж, бүрэн хамгаалалтын доор шинэ
    instance-аа үүсгэсэн болохоор, getInstance() method-н хийх ажил нь ердөө л тэр reference-н утгыг буцаах юм.

4.  Enum. "Effective Java" номонд Joshua Bloch-н танилцуулсан арга. Нэгэнт Java-н enum нь lazy байдлаар,
    classload хийгдэх үед ажилладаг болохоор, мөн тухайн enum доторх тооны л instance-ууд үүсдэг учир, ганцхан
    элементтэй enum үүсгэх нь Singleton-г хэрэгжүүлэх сонгодог арга болж байгаа юм.

    public enum Singleton {
    INSTANCE;
        public void execute (String arg) {
                // perform operation here 
        }
    }

    Энд ямар ч getInstance гэх мэт static method, variable хэрэггүй, дуудах, ашиглахад ч амар.


Singleton pattern-ы талаар ерөнхийд нь гэвэл нэг иймэрхүү, мэдээж үлдээсэн, дутуу мэдээлэл зөндөө байгаа, мөн
ухаад л байвал ухаад л байхаар юм билээ.

Бусад pattern-ы адилаар энэ pattern-г ашиглах газар гэж бий, ашиглахгүй газар гэж бий. Буруу газраа ашиглах нь 
огт ашиглаагүйгээсээ илүүтэй хортой байж болох. Доорх линкүүдэд жишээ нь өгүүлсэн байгаа. 

References:
http://serkank.wordpress.com/2009/04/16/singleton-pattern-uzerine/
http://tech.puredanger.com/2007/07/03/pattern-hate-singleton/
http://tech.puredanger.com/2007/06/15/double-checked-locking/
http://en.wikipedia.org/wiki/Singleton_pattern
http://en.wikipedia.org/wiki/Double-checked_locking

1 comment:

  1. Их ойлгомжтой сайхан тайлбарлажээ. Бусад pattern-уудыг бас ингээд оруулчихвал ч nice даа :D

    ReplyDelete