Wzorzec fabryki
Część sekcji Programowanie obiektowe ścieżki Kotlin w Coddy. Lekcja 43 z 57.
Wzorzec factory umieszcza decyzję o tym, którą klasę utworzyć, w jednym miejscu. Wywołujący opisuje, czego potrzebuje, i otrzymuje typ interfejsu, bez podawania konkretnej klasy:
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
}
}Wewnątrz main:
println(NotifierFactory.create("sms")?.send("ada"))
println(NotifierFactory.create("fax")?.send("bo"))Wynik:
sms to ada
nullW Kotlin fabryka często jest funkcją w obiekcie towarzyszącym interfejsu lub klasy bazowej, więc wywołujący zapisują Shape.from("circle 2"). Może analizować i sprawdzać dane wejściowe, zanim cokolwiek utworzy:
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
}Wewnątrz main:
println(Shape.from("square 3")?.area())
println(Shape.from("circle x")?.area())Wynik:
9.0
nullWywołujący widzą tylko interfejs, więc fabryka może zmienić zwracaną klasę bez ingerowania w kod wywołujący. Tutaj małe dane pozostają w pamięci, a duże trafiają na dysk:
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")Wewnątrz main:
println(storageFor(5).describe())
println(storageFor(500).describe())Wynik:
memory
disk at /tmp/cacheFabryka może być również wartością: funkcja typu (String) -> Animal tworzy obiekty, nie wymagając od wywołującego znajomości ich klasy. Mapa nazw i takich funkcji to rejestr, a odwołanie do konstruktora, takie jak ::Dog, to gotowa funkcja fabrykująca:
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"
}Wewnątrz 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()}")Wynik:
Tom says MeowWyzwanie
ŁatwyKilka eksporterów implementuje dostarczony interfejs Exporter (extension i export(title, lines)). Napisz MarkdownExporter (rozszerzenie md: wiersz # Title i po jednym wierszu - item dla każdego wiersza), CsvExporter (rozszerzenie csv: title,Title, a następnie line,item dla każdego wiersza) oraz PlainExporter (rozszerzenie txt: tytuł zapisany wielkimi literami, a następnie niezmienione wiersze). Następnie napisz obiekt fabryki ExporterFactory: create(format) zwraca eksporter dla markdown, csv lub plain, bez rozróżniania wielkości liter, albo null, a supported() zwraca nazwy formatów.
Dostarczony kod odczytuje format, tytuł i wiersze oraz wypisuje report.md i wyeksportowany tekst albo unsupported format pdf; try markdown, csv, plain.
Twój kod umieść w plikach MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt i Exporter.kt. Main.kt zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
fun main() {
// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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)))
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasKonstruktory główneBloki init i walidacjaKonstruktory dodatkowePodsumowanie — karta biblioteczna4Dziedziczenie
Klasy otwarte i podklasyNadpisywanie składowychsuper i konstruktoryKlasy abstrakcyjnePodsumowanie – katalog pracowników7Klasy specjalne
Klasy danychKlasy wyliczenioweHierarchie zapieczętowaneequals, hashCode, toStringPodsumowanie — śledzenie zamówień10Wzorce projektowe, część 2
Delegowanie za pomocą byWzorzec dekoratoraWzorzec budowniczegoWzorzec stanu2Właściwości i hermetyzacja
Własne akcesoryModyfikatory widocznościPrywatne setterylazy i lateinitPowtórka – konto bankowe5Interfejsy
Deklarowanie interfejsówMetody domyślneWiele interfejsówInterfejsy jako typyPowtórzenie – metody płatności8Typy generyczne
Funkcje generyczneKlasy generyczneOgraniczenia typówKowariancja i kontrawariancja: in i outPodsumowanie — kolejka priorytetowa11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Kotlin