Фабричный паттерн
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 43 из 57.
Паттерн фабрика помещает решение о том, какой класс создать, в одно место. Вызывающая сторона описывает, что ей нужно, и получает тип интерфейса, не указывая конкретный класс:
interface Notifier {
fun send(to: String): String
}
class EmailNotifier : Notifier {
override fun send(to: String) = "email to $to"
}
class SmsNotifier : Notifier {
override fun send(to: String) = "sms to $to"
}
object NotifierFactory {
fun create(channel: String): Notifier? = when (channel) {
"email" -> EmailNotifier()
"sms" -> SmsNotifier()
else -> null
}
}Внутри main:
println(NotifierFactory.create("sms")?.send("ada"))
println(NotifierFactory.create("fax")?.send("bo"))Вывод:
sms to ada
nullВ Kotlin фабрикой часто является функция в сопутствующем объекте интерфейса или базового класса, поэтому вызывающий код записывает Shape.from("circle 2"). Она может разобрать и проверить входные данные до создания чего-либо:
interface Shape {
fun area(): Double
companion object {
fun from(text: String): Shape? {
val p = text.split(" ")
val n = p.getOrNull(1)?.toDoubleOrNull() ?: return null
return when (p[0]) {
"circle" -> Circle(n)
"square" -> Square(n)
else -> null
}
}
}
}
class Circle(val r: Double) : Shape {
override fun area() = 3.14 * r * r
}
class Square(val s: Double) : Shape {
override fun area() = s * s
}Внутри main:
println(Shape.from("square 3")?.area())
println(Shape.from("circle x")?.area())Вывод:
9.0
nullВызывающие видят только интерфейс, поэтому фабрика может изменить возвращаемый класс, не затрагивая их. Здесь небольшие данные остаются в памяти, а большие записываются на диск:
interface Storage {
fun describe(): String
}
class MemoryStorage : Storage {
override fun describe() = "memory"
}
class DiskStorage(val path: String) : Storage {
override fun describe() = "disk at $path"
}
fun storageFor(sizeMb: Int): Storage = if (sizeMb < 100) MemoryStorage() else DiskStorage("/tmp/cache")Внутри main:
println(storageFor(5).describe())
println(storageFor(500).describe())Вывод:
memory
disk at /tmp/cacheФабрика также может быть значением: функция типа (String) -> Animal создаёт объекты, не раскрывая вызывающему коду их класс. Отображение имён на такие функции — это реестр, а ссылка на конструктор, например ::Dog, — готовая фабричная функция:
open class Animal(val name: String) {
open fun sound() = "..."
}
class Dog(name: String) : Animal(name) {
override fun sound() = "Woof"
}
class Cat(name: String) : Animal(name) {
override fun sound() = "Meow"
}Внутри main:
val makers: Map<String, (String) -> Animal> = mapOf("dog" to ::Dog, "cat" to ::Cat)
val pet = makers.getValue("cat")("Tom")
println("${pet.name} says ${pet.sound()}")Вывод:
Tom says MeowЗадание
ЛегкоНесколько экспортёров реализуют предоставленный интерфейс Exporter (extension и export(title, lines)). Напишите MarkdownExporter (расширение md: строка # Title и по одной строке - item для каждой строки), CsvExporter (расширение csv: title,Title, а затем по одной строке line,item для каждой строки) и PlainExporter (расширение txt: заголовок прописными буквами, затем строки без изменений). Затем напишите объект-фабрику ExporterFactory: create(format) возвращает экспортёр для markdown, csv или plain, без учёта регистра, либо null, а supported() возвращает названия форматов.
Предоставленный код считывает формат, заголовок и строки и выводит report.md и экспортированный текст либо unsupported format pdf; try markdown, csv, plain.
Ваш код должен находиться в файлах MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt и Exporter.kt. В Main.kt находится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
val exporter = ExporterFactory.create(input[0])
if (exporter == null) {
println("unsupported format ${input[0]}; try ${ExporterFactory.supported().joinToString(", ")}")
} else {
println("report.${exporter.extension}")
println(exporter.export(input[1], input.drop(2)))
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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