→ Пошук по сайту       Увійти / Зареєструватися
Знання Патерни Породжуючі патерни – Creational patterns

5. Одинак — Singleton

5.	Одинак — Singleton

Уявімо, що нам потрібна глобальна логувальна система в програмі, також нам треба логувати наші повідомлення в якийсь один-єдиний файл, причому нумеруючи повідомлення послідовно, незалежно від того, з якого закутка програми вилетіла помилка. Як можна це зробити?

Синглтон забезпечує існування єдиного екземпляру класу та єдиного доступу до нього.

Клас, зображений нижче, демонструє просту реалізацію дизайн патерну Одинак (ця назва, чесно кажучи, звучить дещо «дико». Думаю, що може буде краще його називати Синглтон. Як вважаєте?)

Уривок коду 5.1. Проста реалізація Синглтону

 class LoggerSingleton
{
    private LoggerSingleton() { }
    private int _logCount = 0;
    private static LoggerSingleton _loggerSingletonInstance = null;
    public static LoggerSingleton GetInstance()
    {
        if (_loggerSingletonInstance == null)
        {
           _loggerSingletonInstance = new LoggerSingleton();
        }
        return _loggerSingletonInstance;
    }
    public void Log(String message)
    {
        Console.WriteLine(_logCount + ": " + message);
        _logCount++;
    }
}

Чіткі ознаки Одинака — приватний конструктор та доступ до єдиного, внутрішньо створеного екземпляру, здійснюваний через статичний метод.
Отже, ми збираємося почати роботу програми із методу DoHardWork і хочемо залогувати факт того, що вона почалася, всякі інші події та факт того, що робота закінчилася.

Уривок коду 5.2. Наша «важка робота» може виглядати так

 public static void DoHardWork()
    {
        LoggerSingleton logger = LoggerSingleton.GetInstance();
        HardProcessor processor = new HardProcessor(1);
        logger.Log("Hard work started...");
        processor.ProcessTo(5);
        logger.Log("Hard work finished...");
    }

Як бачимо метод використовує деякий клас HardProcessor. Що саме він наспраді робить, нас не дуже переймає, проте ми б хотіли, щоб було залоговано в місцях, коли екземпляр класу створився та коли виконувалися якісь підрахунки.

Уривок коду 5.3. HardProcessor

 class HardProcessor
{
    private int _start;
    public HardProcessor(int start)
    {
        _start = start;
        LoggerSingleton.GetInstance().Log("Processor just created.");
    }
    public int ProcessTo(int end)
    {
        int sum = 0;
        for (int i = _start; i <= end; ++i)
        {
            sum += i;
        }
        LoggerSingleton.GetInstance().Log(
                             "Processor just calculated some value: " + sum);
        return sum;
    }
}

А ось вивід програми:

 0: Processor just created.
1: Hard work started...
2: Processor just calculated some value: 15
3: Hard work finished...

Я написав цей приклад, коли був у поїзді, і показав його свому другу, який також є програмістом. Він мені сказав, що я написав Моностейт. – «Я? Де?» Ми поглянули на код і виявилося, що тільки одна змінна в Синглтоні, яку я використовував, _logCount, була статичною в початковому варіанті, який я написав. (Зараз вона змінена на змінну інстансу, тому вгорі дійсно Синглтон).

То ж в чому різниця між Синглтоном і Моностейтом?
Синглтон можна розглядати як спосіб забезпечення одного інстансу класу для нашої аплікації. Моностейт (Monostate), взагалі кажучи, робить те ж саме, що і GoF Синглтон. В Моностейті всі змінні є статичними, отже, теоретично, ви можете мати багато інстансів Моностейту, але ж статичні дані одні і ті ж для одного і того ж типу. Таким чином це допомагає також вирішити проблеми багатопоточності.

Давайте глянемо на приклад ще раз. Швидше за все, що логгер не є таким тривіальним, можливо ви захочете додати функціональні зберігання останнього номера логу а при наступному зчитуванні починати нумерацію саме із того номера. Також ваша програма не така вже й проста і логування викликається не тільки із багатьох місць у коді, але і з багатьох потоків.

Оскільки конструктор вашого класу працює із IO, то витрачається час на створення екземпляру класу. Таким чином GetInstance() через багатопоточність може привезти до ситуації, коли ви створите декілька екземплярів класу, а потім, що гірше, будете звертатися до різних логгерів! Напевно, не така вже і гарна ідея для вашої аплікації.

Найпростіше вирішення проблеми це здійснити ініціалізацію екземпляру прямо у лінійці визначення змінної Синглтону, як зображено нижче.

Уривок коду 5.4. Спрощене вирішення проблеми потокобезпечності

 private static LoggerSingleton _loggerSingletonInstance = new LoggerSingleton();

Більш правильним, та елегантним буде таке рішення:

Уривок коду 5.5. Потокобезпечний Синглтон

 public class ThreadSafeLoggerSingleton
{
    private ThreadSafeLoggerSingleton()
    {
        // Читаємо дані із якогось файлу і дістаємо номер останнього запису
        // _logCount = вичитане значення
    }
    private int _logCount = 0;
    private static ThreadSafeLoggerSingleton _loggerInstance;
    private static readonly object locker = new object();

    public static ThreadSafeLoggerSingleton GetInstance()
    {
        lock (locker)
        {
            if (_loggerInstance == null)
            {
                _loggerInstance = new ThreadSafeLoggerSingleton();
            }
        }
        return _loggerInstance;
    }

    public void Log(String message)
    {
        Console.WriteLine(_logCount + ": " + message);
        _logCount++;
    }
}

Також часто використовується техніка Double-Checked Locking (DCL), яка не завжди працює. Прочитайте ось цю дуже хорошу статтю, вказану в зносках для повної картини потокобезпечності Синглтона на C#.

По матеріалам книги Андрія Будая "Дизайн патерни – просто, як двері". Матеріал розміщується за домовленістю з автором.
Робота представлена за умовами ліцензії Creative Commons Attribution-NonCommercial 3.0 Unported License.

загрузка...
Сторінки, близькі за змістом