Как стать автором
Обновить

Паттерны проектирования на языке Kotlin (часть 2)

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров2.8K

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

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

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<Command>()

  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<Notification>()

  fun addNotification(notification: Notification) = notifications.add(notification)
  fun iterator(): Iterator<Notification> = 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<Editor.Memento>()

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

Теги:
Хабы:
Всего голосов 7: ↑2 и ↓5-3
Комментарии2

Публикации

Ближайшие события