Паттерны проектирования на языке Kotlin
Это вторая часть статьи. Первую часть читайте здесь.
Поведенческие паттерны
13. Chain of Responsibility (Цепочка обязанностей)
Описание: Позволяет передавать запросы последовательно по цепочке обработчиков.
Когда использовать: Когда есть более одного объекта, который может обработать запрос.
Пример кода:
abstract class Handler(private val next: Handler?) {
open fun handle(request: String) {
next?.handle(request)
}
}
class AuthenticationHandler(next: Handler?) : Handler(next) {
override fun handle(request: String) {
if (request.contains("auth")) {
println("Аутентификация прошла")
super.handle(request)
} else {
println("Аутентификация не удалась")
}
}
}
class LoggingHandler(next: Handler?) : Handler(next) {
override fun handle(request: String) {
println("Логирование запроса: $request")
super.handle(request)
}
}
fun main() {
val handler = AuthenticationHandler(LoggingHandler(null))
handler.handle("auth: запрос к ресурсу")
}
14. Command (Команда)
Описание: Инкапсулирует запрос как объект, позволяя параметризовать клиентов с разными запросами.
Когда использовать: Когда нужно параметризовать объекты выполняемым действием.
Пример кода:
interface Command {
fun execute()
}
class Light {
fun turnOn() = println("Свет включен")
fun turnOff() = println("Свет выключен")
}
class TurnOnCommand(private val light: Light) : Command {
override fun execute() = light.turnOn()
}
class TurnOffCommand(private val light: Light) : Command {
override fun execute() = light.turnOff()
}
class RemoteControl {
private val commands = mutableListOf()
fun addCommand(command: Command) = commands.add(command)
fun executeCommands() = commands.forEach { it.execute() }
}
fun main() {
val light = Light()
val turnOn = TurnOnCommand(light)
val turnOff = TurnOffCommand(light)
val remote = RemoteControl()
remote.addCommand(turnOn)
remote.addCommand(turnOff)
remote.executeCommands()
}
15. Iterator (Итератор)
Описание: Предоставляет способ последовательного доступа к элементам агрегатного объекта без раскрытия его внутреннего представления.
Когда использовать: Когда нужно предоставить единый интерфейс для обхода различных коллекций.
Пример кода:
class Notification(val message: String)
class NotificationCollection {
private val notifications = mutableListOf()
fun addNotification(notification: Notification) = notifications.add(notification)
fun iterator(): Iterator = notifications.iterator()
}
fun main() {
val collection = NotificationCollection()
collection.addNotification(Notification("Уведомление 1"))
collection.addNotification(Notification("Уведомление 2"))
collection.addNotification(Notification("Уведомление 3"))
val iterator = collection.iterator()
while (iterator.hasNext()) {
val notification = iterator.next()
println(notification.message)
}
}
16. Mediator (Посредник)
Описание: Определяет объект, который инкапсулирует способ взаимодействия множества объектов.
Когда использовать: Когда нужно упростить коммуникацию между множеством взаимодействующих объектов.
Пример кода:
interface Mediator {
fun notify(sender: Component, event: String)
}
abstract class Component(protected val mediator: Mediator)
class Button(mediator: Mediator) : Component(mediator) {
fun click() {
println("Кнопка нажата")
mediator.notify(this, "click")
}
}
class TextBox(mediator: Mediator) : Component(mediator) {
fun setText(text: String) = println("Текстовое поле установлено в '$text'")
}
class AuthenticationDialog : Mediator {
private val button = Button(this)
private val textBox = TextBox(this)
fun simulateUserAction() = button.click()
override fun notify(sender: Component, event: String) {
if (sender is Button && event = "click") {
textBox.setText("Авторизация пользователя")
}
}
}
fun main() {
val dialog = AuthenticationDialog()
dialog.simulateUserAction()
}
17. Memento (Хранитель)
Описание: Сохраняет внутреннее состояние объекта без нарушения инкапсуляции для возможности восстановления.
Когда использовать: Когда нужно сохранять и восстанавливать прошлые состояния объекта.
Пример кода:
class Editor {
var content: String = ""
fun save(): Memento = Memento(content)
fun restore(memento: Memento) {
content = memento.content
}
data class Memento(val content: String)
}
class History {
private val states = mutableListOf()
fun push(memento: Editor.Memento) = states.add(memento)
fun pop(): Editor.Memento = states.removeAt(states.lastIndex)
}
fun main() {
val editor = Editor()
val history = History()
editor.content = "Состояние 1"
history.push(editor.save())
editor.content = "Состояние 2"
history.push(editor.save())
editor.content = "Состояние 3"
editor.restore(history.pop())
println("Текущее содержание: ${editor.content}")
editor.restore(history.pop())
println("Текущее содержание: ${editor.content}")
}
18. Observer (Наблюдатель)
Описание: Определяет зависимость «один ко многим» между объектами так, что при изменении состояния одного объекта все зависящие от него оповещаются.
Когда использовать: Когда изменение состояния одного объекта требует изменения других объектов.
Пример кода:
interface Observer {
fun update(state: Int)
}
class Subject {
private val observers = mutableListOf()
var state: Int = 0
set(value) {
field = value
notifyAllObservers()
}
fun attach(observer: Observer) = observers.add(observer)
private fun notifyAllObservers() = observers.forEach { it.update(state) }
}
class BinaryObserver : Observer {
override fun update(state: Int) =
println("Двоичное представление: ${Integer.toBinaryString(state)}")
}
class HexObserver : Observer {
override fun update(state: Int) =
println("Шестнадцатеричное представление: ${Integer.toHexString(state)}")
}
fun main() {
val subject = Subject()
subject.attach(BinaryObserver())
subject.attach(HexObserver())
subject.state = 15
subject.state = 10
}
19. State (Состояние)
Описание: Позволяет объекту менять свое поведение при изменении внутреннего состояния.
Когда использовать: Когда поведение объекта зависит от его состояния.
Пример кода:
interface State {
fun handle(context: Context)
}
class Context {
var state: State = ConcreteStateA()
fun request() = state.handle(this)
}
class ConcreteStateA : State {
override fun handle(context: Context) {
println("Состояние А, переходим к В")
context.state = ConcreteStateB()
}
}
class ConcreteStateB : State {
override fun handle(context: Context) {
println("Состояние В, переходим к А")
context.state = ConcreteStateA()
}
}
fun main() {
val context = Context()
context.request()
context.request()
context.request()
}
20. Strategy (Стратегия)
Описание: Определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость.
Когда использовать: Когда есть несколько похожих алгоритмов, и нужно переключаться между ними во время выполнения.
Пример кода:
interface Strategy {
fun execute(a: Int, b: Int): Int
}
class AdditionStrategy : Strategy {
override fun execute(a: Int, b: Int): Int = a + b
}
class SubtractionStrategy : Strategy {
override fun execute(a: Int, b: Int): Int = a - b
}
class Context(private var strategy: Strategy) {
fun setStrategy(strategy: Strategy) {
this.strategy = strategy
}
fun executeStrategy(a: Int, b: Int): Int = strategy.execute(a, b)
}
fun main() {
val context = Context(AdditionStrategy())
println("10 + 5 = ${context.executeStrategy(10, 5)}")
context.setStrategy(SubtractionStrategy())
println("10 - 5 = ${context.executeStrategy(10, 5)}")
}
21. Template Method (Шаблонный метод)
Описание: Определяет скелет алгоритма в методе, оставляя реализацию шагов подклассам.
Когда использовать: Когда нужно определить основной алгоритм и делегировать реализацию отдельных шагов подклассам.
Пример кода:
abstract class Game {
fun play() {
initialize()
startPlay()
endPlay()
}
abstract fun initialize()
abstract fun startPlay()
abstract fun endPlay()
}
class Football : Game() {
override fun initialize() = println("Футбол: Игра инициализирована")
override fun startPlay() = println("Футбол: Игра начата")
override fun endPlay() = println("Футбол: Игра завершена")
}
class Basketball : Game() {
override fun initialize() = println("Баскетбол: Игра инициализирована")
override fun startPlay() = println("Баскетбол: Игра начата")
override fun endPlay() = println("Баскетбол: Игра завершена")
}
fun main() {
val game1: Game = Football()
game1.play()
val game2: Game = Basketball()
game2.play()
}
22. Visitor (Посетитель)
Описание: Разделяет алгоритмы от структур данных, по которым они работают.
Когда использовать: Когда у вас есть сложная структура объектов, и вы хотите выполнять над ними разнообразные операции, не изменяя классы этих объектов.
Пример кода:
// Элемент, который принимает посетителя
interface Shape {
fun accept(visitor: Visitor)
}
// Конкретные элементы
class Circle(val radius: Double) : Shape {
override fun accept(visitor: Visitor) {
visitor.visitCircle(this)
}
}
class Rectangle(val width: Double, val height: Double) : Shape {
override fun accept(visitor: Visitor) {
visitor.visitRectangle(this)
}
}
// Интерфейс посетителя
interface Visitor {
fun visitCircle(circle: Circle)
fun visitRectangle(rectangle: Rectangle)
}
// Посетитель для рисования фигур
class DrawVisitor : Visitor {
override fun visitCircle(circle: Circle) {
println("Рисуем круг с радиусом ${circle.radius}")
}
override fun visitRectangle(rectangle: Rectangle) {
println("Рисуем прямоугольник шириной ${rectangle.width} и высотой ${rectangle.height}")
}
}
// Посетитель для вычисления площади
class AreaVisitor : Visitor {
override fun visitCircle(circle: Circle) {
val area = Math.PI * circle.radius * circle.radius
println("Площадь круга: $area")
}
override fun visitRectangle(rectangle: Rectangle) {
val area = rectangle.width * rectangle.height
println("Площадь прямоугольника: $area")
}
}
fun main() {
val shapes = listOf(
Circle(5.0),
Rectangle(3.0, 4.0)
)
val drawVisitor = DrawVisitor()
val areaVisitor = AreaVisitor()
// Рисуем фигуры
println("=== Рисование фигур ===")
shapes.forEach { it.accept(drawVisitor) }
// Вычисляем площади фигур
println("\n=== Вычисление площади фигур ===")
shapes.forEach { it.accept(areaVisitor) }
}
Это основные паттерны проектирования с примерами на языке Kotlin. Каждый паттерн решает определенную проблему и может быть использован в различных ситуациях для улучшения архитектуры приложения.