Сек (шаблон)

Сек (Singleton) е създаващ шаблон за дизайн, който се използва в обектно-ориентираното програмиране. Този шаблон се използва обикновено в моделирането на обекти, които трябва да бъдат глобално достъпни за обектите на приложението (например обекта, съдържащ структурите с настройките на програмата) или обекти, които се нуждаят от максимално късна инициализация за пестенето на ресурси от паметта.

В софтуерното инженерство Сек шаблонът е дизайн шаблон, който представя ограничението на клас до един обект. Това е полезно, когато точно един обект се нуждае да координира действия през системата. Концепцията понякога се отнася за системи, които работят по-ефективно, когато съществува само един обект или когато е ограничено представянето на определен брой обекти. Терминът идва от математическата концепция за Сек. Има критики откъм използването на Сек, като някои го смятат за анти-модел. Съди се, че е преизползван и въвежда ненужни ограничения в ситуации, където например класа не е наистина необходим и въвежда глобално условни в приложението.

В C++ се използва да изолира непредвидимостта на реда на динамичната инициализация, като връща контрола на програмиста.

Моделите The Abstract Factory, Builder, и Prototype могат да използват Сек при тяхното изпълнение.

Фасадни обекти (Facade objects) са чести модели, защото само Facade objects са задължителни.

Условни обекти (State objects) са чести Сек.

Сек шаблоните често са предпочитани за глобални променливи, защото:

  • те не замърсяват global namespace с ненужни променливи;
  • те позволяват мързеливи разпределения и инициализации, докато променливите в много езици винаги ще консумират ресурси.

Изпълнението на Сек шаблона, трябва да удовлетворява всеки отделен случай и принципите за общодостъпност. Изисква механизъм, който да има достъп до члена на Сек класа, без да се създава клас обект и механизма да запази стойността на този член между клас-обектите. Сек шаблонът се изпълнява като се прави клас с метод, който прави нова инстанция на класа, ако не съществува. Ако инстанцията съществува, просто връща референцията на този обект. За да е сигурно, че обектът не може да бъде инстанциран по друг начин, конструкторът се прави частен. Забелязва се, че разликата между простата статична инстанция на класа и шаблона, въпреки че шаблонът може да представи статичната инстанция, също може да бъде и мързеливо конструиран, не се изисква памет или ресурси, докато не са нужни.

Сек шаблонът трябва да бъде внимателно конструиран в multi-threaded приложения. Ако два реда изпълнят направения метод по едно и също време, когато шаблона вече не съществува, и двата трябва да се проверят за инстанция на шаблона и само тогава да се създаде нов. Ако езикът за програмиране има едновременни възможности за обработка на метода, трябва да бъде конструиран да изпълнява взаимоизключващи операции. Класическото решение на този проблем е взаимоизключващият клас, който показва на обекта, че е представен.

// Пример за Сек
using System;

namespace SingletonDesignPattern
{
  /// <summary>
  /// Главен клас за стартиране на примерното приложение
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Главен метод за стартиране
    /// </summary>
    static void Main()
    {

      // Конструкторът е защитен -- операторът new не може да бъде извикан
      Singleton s1 = Singleton.Instance();
      Singleton s2 = Singleton.Instance();

      // Проверка за идентичност на инстанцията
      if (s1 == s2)
      {
        Console.WriteLine("Objects are the same instance"); //и двата обекта са една и съща инстанция на този клас
      }
      // Изчакай натискане по конзолата от потребителя
      Console.ReadKey();
    }
  }

  /// <summary>
  /// The 'Singleton' class – класът Сек
  /// </summary>
  class Singleton
  {
    private static Singleton _instance;  //променлива за единствената инстанция на този клас
    // Конструторът е защитен и не може да бъде извикан
    protected Singleton()
    {

    }

    // Единственият начин за инстанциране е от тук
    public static Singleton Instance()
    {
      // Използва късна инициализация (lazy initialization)
      // N.B.: Да не се използва в многонишкови приложения
      if (_instance == null)
      {
        _instance = new Singleton();
      }
      return _instance;
    }
  }

}

Всички предоставени оттук решения за езика Java са многонишково сигурни, но се различават в поддържаните версии на езика и в късната инициализация. От Java 5.0 нататък най-лесният начин за създаване на Сек шаблон е чрез enum, както е показано в края на този раздел.

Късна инициализация

[редактиране | редактиране на кода]

Този метод използва двойно-проверено заключване, което не трябва да се използва преди Java 5.0, понеже е уязвимо за коварни, сложни бъгове. Проблемът се поражда от динамично изпълнение (out-of-order) ще позволи връщането на инстанцията преди сек шаблонът да бъде изпълнен.

public class SingletonDemo {
    private static volatile SingletonDemo instance;
    private SingletonDemo() { }

    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }

        return instance;
    }
}

Може да се използва алтернативната и по-изчистена версия, която изразходва по-малко ресусрси в многонишковата среда:

public class SingletonDemo {
    private static SingletonDemo instance = null;
    private SingletonDemo() { }

    public static synchronized SingletonDemo getInstance() {
        if (instance == null) {
            instance = new SingletonDemo();
        }

        return instance;
    }
}

Нетърпеливо инициализиране (Eager initialization)

[редактиране | редактиране на кода]

В случаите, когато програмата винаги ще са нуждае от поне една инстанция или създаването на инстанции няма да натовари програмата от гледна точка на време и ресурси, може да се използва нетърпеливото инициализиране, което винаги създава нова инстанция:

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

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Този метод има редица предимства:

  • Статичният инициализатор се изпълнява, когато се инициализира класа, след зареждането на класа, но и преди класа да бъде използван от нишката.
  • Не е необходимо да се синхронизира методът на getInstance(), което означава, че инстанцията ще бъде видима за всички нишки и няма да е необходимо (скъпо) заключване (locking).
  • Думата final означава, че инстанцията не може да се редефинира, което гарантира, че няма да се дублират инстанциите.

Статично блоково инициализиране (Static Block initialization)

[редактиране | редактиране на кода]

Някои автори се позовават на подобно решение, което позволява някои предварителни обработки (напр. за проверка на грешки). В този смисъл традиционният подход би могъл да се разглежда като частен случай, тъй като зареждачът на класове (class loader) ще направи точно същата преработка.

public class Singleton {
    private static final Singleton instance;

    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("Darn, an error occurred!", e);
        }
    }

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {
        // ...
    }
}

Initialization-on-demand holder idiom

[редактиране | редактиране на кода]

В софтуерното инженерство, Initialization-on-demand holder idiom е мързеливо-заредения Сек шаблон. Във всички версии на Java, идиомът позволява безопасно, силно конкурентно мързелива инициализация с добър перформанс.

public class Something {
    private Something() {}

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Изпълнението на идиома разчита на фаза инициализация на изпълнение в рамките на Java Virtual Machine (JVM), както е определено от езиковата спецификация на езика. Когато класът Something се зарежда от JVM, класът минава през инициализация. Тъй като класът не включва инициализирането на никакви, инициализацията завършва тривиално. Статичната дефиниция на клас LazyHolder в него не се инициализира, докато JVM определи, че LazyHolder трябва да бъде изпълнена. Класът LazyHolder се изпълнява, само когато статичен getInstance се позова на класа Something и за първи път това се случи, когато JVM се зареди, инициализира класа LazyHolder. Инициализацията на резултатите от LazyHolder в статичната променлива INSTANCE се инициализират при изпълнение на (частния) конструктор за външния клас Something.

Това дава по-висока ефективност на thread-safe Сек шаблона, без допълнителна синхронизация. Сравнителен анализ показва, че е много по-бързо, отколкото дори uncontended синхронизация. Въпреки това идиом е Сингълтън-специфично и не разширяема до множества от обекти (например карта на базата на кеш).

Прототипно-базиран Сек (шаблон)

[редактиране | редактиране на кода]

В прототипно-базираният програмен език, обектите, а не класовете са използвани. Сек „Шаблонът“ просто сочи към обект без копия или такива, които не са използвани за прототип, за които и да са други обекти. Пример в Io:

Foo := Object clone
Foo clone := Foo

Пример за приложение на абстрактен фабричен сек. шаблон

[редактиране | редактиране на кода]

Сек шаблонът е често използван за връзка с абстрактно фабричния Сек шаблон, за да създаде ресурс за цялата система, чийто специфичен тип не е познат на кода, който го използва. Пример за приложение на тези два Сек шаблона заедно е Java Abstract Window Toolkit AWT.

Java.awt.Toolkit е абстрактен клас, който свързва различни AWT компоненти към отделни toolkit имплементации. Toolkit класът има Toolkit.getDefaultToolkit() фабричен метод, който връща platform-specific субклас от Toolkit. Toolkit обектът е Сек шаблон, защото AWT се нуждае само от един обект, за да осъществи свързването и обектът е относително скъп за да се създаде. Toolkit методите трябва да бъдат имплементирани в обект, а не като статичен метод на клас, защото специфични имплементации не са познати на платформно-независимите компоненти. Името на използвания специфичен Toolkit субклас е обозначен от „awt.toolkitenvironment property, който е достъпен през System.getProperties().

Свързването, осъществено от toolkit позволява например backing имплементирането на java.awt.Window да се свърже към платформно-специфичната java.awt.peer. WindowPeer имплементация. Window класът и application-ът, изплозвайки window трябва да са на ясно с кой платформно-специфичен суб-клас се използва.