Паттерн «Одиночка»
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 42 из 57.
Шаблон проектирования — это проверенное решение задачи проектирования, которая возникает снова и снова. Шаблон синглтон гарантирует, что у класса будет ровно один экземпляр, доступный из любой части программы. В Kotlin объявление object является синглтоном: он создаётся один раз, при первом использовании, и безопасно даже в случае, когда несколько потоков запускаются одновременно:
object Settings {
var theme = "light"
var fontSize = 14
}
fun openEditor() = "editor in ${Settings.theme} theme"Внутри main:
Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)Результат:
editor in dark theme
14Синглтон хранит состояние, которым совместно пользуются разные части программы. Держите это состояние закрытым и изменяйте его только через методы, чтобы каждое изменение проходило через одни и те же правила:
object Metrics {
private val hits = mutableMapOf<String, Int>()
fun hit(page: String) { hits[page] = (hits[page] ?: 0) + 1 }
fun report() = hits.entries.joinToString { "${it.key}=${it.value}" }
}
fun home() = Metrics.hit("home")
fun about() = Metrics.hit("about")Внутри main:
home(); about(); home()
println(Metrics.report())Вывод:
home=2, about=1Когда единственному экземпляру нужны данные для настройки, класс с private constructor может создать его в своём сопутствующем объекте при первом запросе и затем возвращать тот же экземпляр:
class Database private constructor(val url: String) {
companion object {
private var instance: Database? = null
fun connect(url: String): Database =
instance ?: Database(url).also { instance = it }
}
}Внутри main:
val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)Вывод:
true
db://mainИспользуй синглтоны умеренно. Синглтон — это глобальное состояние: любой код может изменить его, из-за чего становится непонятно, кто от него зависит, а тесты не могут начинаться с нового экземпляра. Они подходят для того, что действительно существует в единственном экземпляре на программу, например для конфигурации или логгера. Когда объект нужен только части программы, передавай его как параметр, типизируя его интерфейсом, чтобы тест мог передать вместо него поддельный объект:
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
class Session(private val clock: Clock) { // зависимость видна
val started = clock.now()
}
val session = Session(SystemClock) // тест передаёт фейковый ClockЗадание
ЛегкоЗавершите реализацию синглтона Logger. У него есть изменяемый minLevel (debug, info, warn или error, именно в таком порядке). log(level, message) сохраняет [WARN] disk low, если уровень не ниже minLevel, а в противном случае учитывает сообщение в dropped, которое код вне этого класса может читать, но не изменять. entries — это доступный только для чтения список сохранённых строк, а count(level) возвращает количество сохранённых строк с этим уровнем.
Вспомогательные функции в Functions.kt предоставлены: они обращаются к тому же Logger из другого файла. Предоставленный код считывает минимальный уровень, затем строки level,message и выводит каждую запись, а также errors: 1, dropped: 2.
Ваш код должен находиться в Logger.kt и Functions.kt. В Main.kt содержится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
Logger.minLevel = input[0]
for (line in input.drop(1)) {
val (level, message) = line.split(",", limit = 2)
when (level) {
"debug" -> debug(message)
"info" -> info(message)
"warn" -> warn(message)
else -> error(message)
}
}
for (e in Logger.entries) println(e)
println("errors: ${Logger.count("error")}, dropped: ${Logger.dropped}")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin