Шаблоны проектирования часто считаются более сложной темой и поэтому игнорируются или игнорируются людьми, плохо знакомыми с разработкой для iOS или OS X. Тем не менее, существует целый ряд шаблонов проектирования, с которыми столкнется каждый начинающий разработчик iOS или OS X с первого дня. Синглтон-паттерн является одним из таких паттернов.
Прежде чем мы начнем изучать методы реализации одноэлементного паттерна в Swift и Objective-C, я бы хотел немного разобраться в одноэлементном паттерне, включая его преимущества и недостатки.
1. Что такое синглтон?
Истинные и функциональные синглтоны
Шаблон синглтона неразрывно связан с объектно-ориентированным программированием. Определение одноэлементного шаблона простое, только один экземпляр одноэлементного класса может быть создан или является живым в любой момент времени.
Брент Симмонс , однако, нюансует это определение, проводя различие между истинными синглетонами и функциональными синглетонами. 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
.
Повторное использование
Плотное сцепление часто тесно связано с возможностью повторного использования. Сильно связанный объектный граф очень часто трудно повторно использовать без рефакторинга. Плохое повторное использование иногда неизбежно, но это, безусловно, необходимо учитывать при разработке программного обеспечения.
2. Создание синглтона
Objective-C
Создать синглтон в 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, то я рекомендую использовать первый подход для его краткости и простоты.
3. Когда использовать синглтоны?
Я полагаю, это заманчиво, если единственный инструмент, который у вас есть, это молоток, обращаться со всем, как с гвоздем. — Авраам Маслоу
Когда я впервые услышал о паттерне синглтона, я был взволнован тем, насколько полезным он будет в моем следующем проекте. Очень заманчиво использовать инструмент, который, кажется, решает многие проблемы. На первый взгляд шаблон синглтона может выглядеть как серебряная пуля или золотой молоток , но это не тот шаблон синглтона.
Например, если вы используете только одноэлементный файл, чтобы сделать его легкодоступным, вы можете использовать шаблон одноэлементного режима по неправильным причинам. Не потому, что Apple использует шаблон синглтона в своих собственных средах, это правильное решение для любой проблемы.
Одиночки могут быть очень полезны, но многие опытные программисты считают их анти-паттернами. У большинства из них был плохой опыт, чтобы прийти к такому выводу. Учитесь на их ошибках и используйте их экономно.
Я не считаю синглтон-паттерн анти-паттерном, но его следует использовать с осторожностью. Если вы испытываете желание превратить объект в одиночку, задайте себе вопрос, есть ли другой способ решить эту проблему. В большинстве случаев шаблон синглтона — не единственное доступное решение. Альтернативное решение может потребовать больше работы или немного накладных расходов, но, вероятно, лучше в долгосрочной перспективе.
Функциональные синглтоны, на которые ссылается Брент Симмонс , часто являются вполне допустимой альтернативой настоящим синглетонам. Нет ничего плохого в создании экземпляра класса и передаче его нуждающимся объектам. Рассмотрите этот подход в следующий раз, когда вы собираетесь создать еще один синглтон.
Вывод
Шаблон синглтона является простым и мощным, но его следует использовать с осторожностью. Некоторые из ваших коллег могут посоветовать вам против этого, но я думаю, что важно рассмотреть его использование и оценить для себя, считаете ли вы, что это полезное дополнение к вашему набору инструментов.