yusufyilmazfr / tasarim-desenleri-turkce-kaynak

Türkçe kaynağa destek olması amacıyla oluşturulmuş bir kaynaktır. Konu anlatımının yanı sıra C#, Java, Go, Python, Kotlin ve TypeScript gibi birçok dilde tasarım desenlerinin uygulamasını içermektedir.
https://yusufyilmazfr.gitbook.io/tasarim-desenleri-turkce-kaynak/
3.28k stars 454 forks source link

Singleton deseni ve lock #24

Closed ozerkon closed 1 year ago

ozerkon commented 2 years ago

Selam üstat, öncelikle teşekkürle harika bir kaynak olmuş. Kafama takılan soru ise neden singleton deseninde lock kontrolü yaparken iç içe iki kez if (database == null) yazıldığı.

public class Database
{
    private static Database database;
    private static Object _lockObject = new object();

    private Database()
    {

    }

    public static Database GetInstance()
    {
        if (database == null)
        {
            lock (_lockObject)
            {
                if (database == null)
                {
                    database = new Database();
                }
            }
        }

        return database;
    }
}
yusufyilmazfr commented 2 years ago

Selam efendim, teveccühünüz ve güzel dilekleriniz için de teşekkür ediyorum. :)

Orada 2 defa if ile kontrol edilmesinde thread-safe durumunu göz önünde bulundurmak istedik. GetInstance() metotunu aynı anda birden fazla thread çalıştırabilir, bu gerçekleşebilir bir senaryo. İkisi de aynı anda if(database == null) kısmına da gelebilir, bu durumda thread sayısı kadar bir instance/sınıf-örneği meydana gelebilir. lock (_lockObject) birim zamanda kendi scope içerisinde bir adet thread bulunmasına izin veriyor, içerideki thread işini bitirdikten sonra bekleyen varsa sıradaki o olarak devam edecektir. Bu durumda lock (_lockObject) içerisinde tekrardan if kontrolü gerçekleştirildi. Olur da birden fazla thread lock üzerinde bekler ve kendilerine sıra gelirse önce bir kontrol etsinler, kendisinden önce nesne oluşturan varsa bir daha oluşturmasın diye. Bu sayede bir defa oluştuğundan emin oluruz ve kaynak tasarrufu sağlarız.

Umarım açıklamalarım anlaşılır olmuştur, oturmayan bir şeyler varsa görseller ve kaynaklar ile de destekleyebilirim bunu.

ozerkon commented 2 years ago

Büyük ölçüde anladım ama; bir kaç başka kaynağa da baktım sizinkine benzer bir örnek göremedim. Müsait bir zamanda görselleri ve bahsettiğiniz kaynakları ekleyebilirseniz memnun olurum.

yusufyilmazfr commented 2 years ago

"Double checked locking singleton" diye arattığımız zaman birçok kaynağı görebiliyoruz. Neden böyle bir şeyin olduğu, nasıl uygulandığı vs. anlatılıyor.

Buradaki tartışmada, bahsettiğim gibi senaryolar üzerinden anlatılmış. Faydalı olabilir: https://stackoverflow.com/questions/18093735/double-checked-locking-in-singleton

Ek olarak da ilk denk gelen şu kaynakları da bırakabilirim:

ozerkon commented 2 years ago

Tamamdır üstat, şimdi oturdu işte :) Bir de factory desenini anlatırken

class NotifyFactory
{
    public INotify CreateNotify(string notifyType)
    {
        if (notifyType == "SMS")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton olarak da ayarlanabilirdi.
            return new SmsNotify();
        }
        else if (notifyType == "MAIL")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton olarak da ayarlanabilirdi.
            return new MailNotify();
        }
        return null;
    }
}

"Örnek olarak burası Singleton olarak da ayarlanabilirdi" demişsiniz. Şu şekilde yaptım, doğru bir yaklaşım mıdır acaba?

    public class NotifyFactory
    {
        INotify notify;
        private static Object _lockObject = new object();
        public INotify CreateNotify(string notifyType)
        {
            if (notify == null)
            {
                lock (_lockObject)
                {
                    if (notify == null)
                    {
                        if (notifyType == "mail")
                        {
                            notify = new MailNotify() ;
                        }
                        else if (notifyType == "sms")
                        {
                            notify = new SmsNotify();
                        }
                        else
                        {
                            return null;
                        }

                    }
                }
            }
            return notify;
        }

    }
yusufyilmazfr commented 2 years ago

Tamamdır üstat, şimdi oturdu işte :) Bir de factory desenini anlatırken

class NotifyFactory
{
    public INotify CreateNotify(string notifyType)
    {
        if (notifyType == "SMS")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton olarak da ayarlanabilirdi.
            return new SmsNotify();
        }
        else if (notifyType == "MAIL")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton olarak da ayarlanabilirdi.
            return new MailNotify();
        }
        return null;
    }
}

"Örnek olarak burası Singleton olarak da ayarlanabilirdi" demişsiniz. Şu şekilde yaptım, doğru bir yaklaşım mıdır acaba?

    public class NotifyFactory
    {
        INotify notify;
        private static Object _lockObject = new object();
        public INotify CreateNotify(string notifyType)
        {
            if (notify == null)
            {
                lock (_lockObject)
                {
                    if (notify == null)
                    {
                        if (notifyType == "mail")
                        {
                            notify = new MailNotify() ;
                        }
                        else if (notifyType == "sms")
                        {
                            notify = new SmsNotify();
                        }
                        else
                        {
                            return null;
                        }

                    }
                }
            }
            return notify;
        }

    }

Maalesef, ilk bakışta çok da sağlıklı görünmüyor. Siz direkt sınıfların davranışlarını değil de metotun vereceği datayı Singleton olarak ayarladınız.

Bu koddan dolayı;

public class NotifyFactory
{
    ...
    public INotify CreateNotify(string notifyType)
    {
    ...
        if (notify == null)
        {
        ...
        }
    }
}

Örnek senaryo;

NotifyFactory notifyFactory = new NotifyFactory();
INotify notify = notifyFactory.CreateNotify("mail");
// Burada MailNotify sınıfı nesnesi verecektir. 
// Buraya kadar her şey yolunda.
// Fakat NotifyFactory.CreateNotify(obj) sınıfına bakıldığında
// İçerisindeki dönülecek data direkt olarak Singleton olarak ayarlanmış.
// Aynı durumdan devam edelim ve mail işleminden sonra bir de sms isteyelim.

INotify notify = notifyFactory.CreateNotify("sms");
// Buraya dikkat etmek gerekiyor!
// Burada da MailNotify sınıfı nesnesi verecektir. 
// Nedeni ise NotifyFactory.CreateNotify(obj) içerisinde
// Geriye döndürülmesi beklenen data Singleton olarak ayarlanmış.
// Bu yüzden ilk parametre ne ise diğerleri de sürekli o olacaktır.
// Çünkü yukarıda yazdığınız kodda geriye gönderilecek datanın null kontrolü gerçekleştiriliyor.

Yapmak istediğiniz işlemi şu şekilde de toplayabiliriz:

using System;

class Program 
{
    static void Main(string[] args) 
    {
        NotifyFactory notifyFactory = new NotifyFactory();
        INotify notify = notifyFactory.CreateNotify("SMS");
        notify.SendNotification(new User());

        notify = notifyFactory.CreateNotify("MAIL");
        notify.SendNotification(new User());
    }
}

public class User
{
    // Burada bildirimin gideceği kullanıcıya
    // ait bilgiler bulunacaktır.
}

interface INotify
{
    void SendNotification(User user);
}

class MailNotify : INotify
{
    public void SendNotification(User user)
    {
        Console.WriteLine("MAIL");
    }
}

public class SmsNotify : INotify
{
    private static SmsNotify smsNotify;
    private static Object _lockObject = new object();

    private SmsNotify()
    {

    }

    public static SmsNotify GetInstance()
    {
        if (smsNotify == null)
        {
            lock (_lockObject)
            {
                if (smsNotify == null)
                {
                    smsNotify = new SmsNotify();
                }
            }
        }

        return smsNotify;
    }

    public void SendNotification(User user)
    {
        Console.WriteLine("SMS");
    }
}

// UML diyagramındaki ProductFactory sınıfına denk gelmektedir.
class NotifyFactory
{
    public INotify CreateNotify(string notifyType)
    {
        if (notifyType == "SMS")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton ayarlandı.
            return SmsNotify.GetInstance();
        }
        else if (notifyType == "MAIL")
        {
            // Buradaki nesne oluşturma süreçleri bize kalmıştır.
            // Kullanıcıdan soyutlanmıştır.
            // Örnek olarak burası Singleton olarak da ayarlanabilirdi.
            return new MailNotify();
        }
        return null;
    }
}
ozerkon commented 2 years ago
NotifyFactory notifyFactory = new NotifyFactory();
INotify notify = notifyFactory.CreateNotify("mail");
// Burada MailNotify sınıfı nesnesi verecektir. 
// Buraya kadar her şey yolunda.
// Fakat NotifyFactory.CreateNotify(obj) sınıfına bakıldığında
// İçerisindeki dönülecek data direkt olarak Singleton olarak ayarlanmış.
// Aynı durumdan devam edelim ve mail işleminden sonra bir de sms isteyelim.

INotify notify = notifyFactory.CreateNotify("sms");
// Buraya dikkat etmek gerekiyor!
// Burada da MailNotify sınıfı nesnesi verecektir. 
// Nedeni ise NotifyFactory.CreateNotify(obj) içerisinde
// Geriye döndürülmesi beklenen data Singleton olarak ayarlanmış.
// Bu yüzden ilk parametre ne ise diğerleri de sürekli o olacaktır.
// Çünkü yukarıda yazdığınız kodda geriye gönderilecek datanın null kontrolü gerçekleştiriliyor.

Üstat çok teşekkürler, açıklama satırlarını okuyunca anladım hatamı. İlk nesne üretildikten sonra null olmayacağı için hep ilk üretilen döndürülüyor benim metotta.