Статьи

Шаблоны проектирования: синглтоны

Шаблоны проектирования часто считаются более сложной темой и поэтому игнорируются или игнорируются людьми, плохо знакомыми с разработкой для iOS или OS X. Тем не менее, существует целый ряд шаблонов проектирования, с которыми столкнется каждый начинающий разработчик iOS или OS X с первого дня. Синглтон-паттерн является одним из таких паттернов.

Прежде чем мы начнем изучать методы реализации одноэлементного паттерна в Swift и Objective-C, я бы хотел немного разобраться в одноэлементном паттерне, включая его преимущества и недостатки.

Шаблон синглтона неразрывно связан с объектно-ориентированным программированием. Определение одноэлементного шаблона простое, только один экземпляр одноэлементного класса может быть создан или является живым в любой момент времени.

Брент Симмонс , однако, нюансует это определение, проводя различие между истинными синглетонами и функциональными синглетонами. Brent определяет настоящие синглтоны как классы, которые всегда возвращают один и тот же экземпляр самого себя. Невозможно создать более одного экземпляра класса. Функциональные синглтоны создаются один раз и используются в нескольких местах.

Я уверен, что многие разработчики не классифицируют функциональные синглтоны как синглтоны, но мне нравится различие, которое делает Брент. Функциональным синглетонам часто не хватает недостатков, типичных для настоящих синглетонов. Мы рассмотрим эти недостатки позже в этой статье.

Если вы знакомы с разработкой для iOS или OS X, то заметите, что в средах Apple используется шаблон синглтона. Например, приложение iOS может иметь только один экземпляр класса UIApplication , доступ к которому можно получить с помощью sharedApplication класса sharedApplication .

1
UIApplication *sharedApplication = [UIApplication sharedApplication];
1
let sharedApplication = UIApplication.sharedApplication()

Хотя класс UIApplication предоставляет вам доступ к одноэлементному UIApplication , ничто не мешает вам явно создать экземпляр экземпляра UIApplication .

1
UIApplication *newApplication = [[UIApplication alloc] init];
1
let newApplication = UIApplication()

Результат, однако, является исключением времени выполнения. Исключение ясно гласит, что только один экземпляр класса UIApplication может быть живым в любой момент времени. Другими словами, класс UIApplication был разработан с учетом шаблона синглтона.

1
*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘There can only be one UIApplication instance.’

Наиболее очевидным преимуществом шаблона синглтона является доступность. Рассмотрим класс UIApplication в качестве примера. Импортируя структуру UIApplication синглтон UIApplication доступен из любой точки вашего проекта. Взгляните на следующий пример подкласса UIViewController .

1
2
3
4
5
#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController
 
@end
01
02
03
04
05
06
07
08
09
10
11
12
13
14
#import «ViewController.h»
 
@implementation ViewController
 
#pragma mark —
#pragma mark View Life Cycle
— (void)viewDidLoad {
    [super viewDidLoad];
     
    // Access Application Singleton
    UIApplication *application = [UIApplication sharedApplication];
}
 
@end

Иногда полезно или важно, чтобы только один экземпляр класса был жив в любой момент времени. Класс UIApplication является примером этого требования. Другие примеры могут включать классы для регистрации, кэширования или операций ввода-вывода.

Тем не менее, важно понимать, что синглтоны являются средством достижения контроля и поведения. Методы защитного кодирования могут дать вам тот же результат без ментальных издержек шаблона синглтона.

Создавая синглтон, вы по сути создаете глобальное состояние. Важно, чтобы вы знали об этом. Несколько лет назад Мишко Хевери написал замечательную статью о синглтон-паттерне, в которой он объясняет, почему паттерна следует избегать. Мишко подчеркивает, что глобальное государство является основной причиной вреда синглетонов.

Хотя есть исключения. Например, неизменяемые синглтоны могут навредить. Вы все еще создаете глобальное состояние, но это состояние неизменно. Мишко также упоминает регистраторы как «несколько приемлемое» применение одноэлементного шаблона, поскольку состояние течет в одном направлении, из вашего приложения в регистратор.

Большинство объектно-ориентированных языков программирования считают жесткое соединение модулей плохой практикой. Иными словами, рекомендуется максимально разделить модули. Это ставит шаблон синглтона в плохое место, и поэтому многие уважаемые программисты считают синглтоны плохой практикой. Некоторые разработчики заходят так далеко, что считают это анти-паттерном , которого следует избегать.

Чтобы лучше понять проблему, рассмотрите следующий сценарий. Вы создали приложение iOS с сетевым компонентом, в котором вы UIApplication доступ к одноэлементному приложению UIApplication . Ваше приложение для iOS имеет такой успех, что вы рассматриваете возможность создания приложения для OS X. Класс UIApplication , однако, недоступен в OS X из-за отсутствия инфраструктуры UIKit. Это означает, что вам придется реорганизовать или изменить сетевой компонент приложения iOS.

Существует несколько решений для решения этой проблемы, но я хочу подчеркнуть, что вы тесно связали сетевой модуль с каркасом UIApplication в частности с классом UIApplication .

Плотное сцепление часто тесно связано с возможностью повторного использования. Сильно связанный объектный граф очень часто трудно повторно использовать без рефакторинга. Плохое повторное использование иногда неизбежно, но это, безусловно, необходимо учитывать при разработке программного обеспечения.

Создать синглтон в Objective-C очень легко. В следующем фрагменте кода мы создаем и реализуем класс ведения журнала Logger . Мы sharedLogger доступ к объекту singleton через sharedLogger класса sharedLogger .

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
 
@interface Logger : NSObject
 
#pragma mark —
#pragma mark Class Methods
+ (Logger *)sharedLogger;
 
@end

Реализация проста. Мы объявляем статическую переменную _sharedInstance которая будет содержать ссылку на одноэлементный объект. Мы вызываем dispatch_once , функцию Grand Central Dispatch , и передаем блок, в котором мы инициализируем экземпляр класса Logger . Мы храним ссылку на экземпляр в _sharedInstance .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#import «Logger.h»
 
@implementation Logger
 
#pragma mark —
#pragma mark Class Methods
+ (Logger *)sharedLogger {
    static Logger *_sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedInstance = [[self alloc] init];
    });
     
    return _sharedInstance;
}
 
@end

Функция dispatch_once гарантирует, что переданный нами блок будет выполнен один раз за время существования приложения. Это очень важно, поскольку мы хотим инициализировать только один экземпляр класса Logger .

Переменная oncePredicate которая передается в качестве первого аргумента функции dispatch_once используется функцией dispatch_once чтобы гарантировать, что блок выполняется только один раз.

sharedLogger класса sharedLogger возвращает экземпляр Logger , возвращая ссылку, хранящуюся в _sharedInstance . На sharedLogger взгляд реализация sharedLogger может показаться сложной, но я уверен, что вы согласны с тем, что идея очень проста.

Чтобы реализовать шаблон синглтона в Swift, мы используем константы классов, которые были введены в Swift 1.2. Если у вас возникли проблемы с приведенным ниже фрагментом кода, убедитесь, что вы используете Xcode 6.3+.

1
2
3
class Logger {
    static let sharedInstance = Logger()
}

Позвольте мне познакомить вас с краткой реализацией класса Logger . Мы начнем с объявления класса с именем Logger . Чтобы реализовать шаблон синглтона, мы объявляем свойство типа sharedInstance типа Logger .

Используя ключевое слово static , константа sharedInstance связана с классом Logger а не с экземплярами класса. Константа sharedInstance называется свойством типа, поскольку она связана с типом, то есть классом Logger . Мы получаем доступ к одноэлементному объекту через класс Logger .

1
Logger.sharedInstance

Вы можете быть удивлены, почему мы не используем функцию dispatch_once как мы использовали в реализации Objective-C. Под капотом Swift уже использует dispatch_once при инициализации статических констант. Это то, что вы получаете бесплатно в Swift.

Другой распространенный подход для реализации одноэлементного шаблона — использование вложенных типов. В следующем примере мы объявляем вычисляемое свойство типа sharedInstance типа Logger . При закрытии свойства вычисляемого типа мы объявляем структуру с именем Singleton . Структура определяет свойство типа константы, instance типа Logger .

1
2
3
4
5
6
7
8
9
class Logger {
    class var sharedInstance: Logger {
        struct Singleton {
            static let instance: Logger = Logger()
        }
         
        return Singleton.instance
    }
}

Доступ к одноэлементному объекту одинаков для обеих стратегий. Если вы используете Swift 1.2, то я рекомендую использовать первый подход для его краткости и простоты.

Я полагаю, это заманчиво, если единственный инструмент, который у вас есть, это молоток, обращаться со всем, как с гвоздем. Авраам Маслоу

Когда я впервые услышал о паттерне синглтона, я был взволнован тем, насколько полезным он будет в моем следующем проекте. Очень заманчиво использовать инструмент, который, кажется, решает многие проблемы. На первый взгляд шаблон синглтона может выглядеть как серебряная пуля или золотой молоток , но это не тот шаблон синглтона.

Например, если вы используете только одноэлементный файл, чтобы сделать его легкодоступным, вы можете использовать шаблон одноэлементного режима по неправильным причинам. Не потому, что Apple использует шаблон синглтона в своих собственных средах, это правильное решение для любой проблемы.

Одиночки могут быть очень полезны, но многие опытные программисты считают их анти-паттернами. У большинства из них был плохой опыт, чтобы прийти к такому выводу. Учитесь на их ошибках и используйте их экономно.

Я не считаю синглтон-паттерн анти-паттерном, но его следует использовать с осторожностью. Если вы испытываете желание превратить объект в одиночку, задайте себе вопрос, есть ли другой способ решить эту проблему. В большинстве случаев шаблон синглтона — не единственное доступное решение. Альтернативное решение может потребовать больше работы или немного накладных расходов, но, вероятно, лучше в долгосрочной перспективе.

Функциональные синглтоны, на которые ссылается Брент Симмонс , часто являются вполне допустимой альтернативой настоящим синглетонам. Нет ничего плохого в создании экземпляра класса и передаче его нуждающимся объектам. Рассмотрите этот подход в следующий раз, когда вы собираетесь создать еще один синглтон.

Шаблон синглтона является простым и мощным, но его следует использовать с осторожностью. Некоторые из ваших коллег могут посоветовать вам против этого, но я думаю, что важно рассмотреть его использование и оценить для себя, считаете ли вы, что это полезное дополнение к вашему набору инструментов.