Паттерны проектирования на языке Kotlin

0840a0f211d3135d8db511f9edb28ef0

Это вторая часть статьи. Первую часть читайте здесь.

Поведенческие паттерны

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. Каждый паттерн решает определенную проблему и может быть использован в различных ситуациях для улучшения архитектуры приложения.

© Habrahabr.ru