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

от автора

Паттерны проектирования — проверенные временем решения общих задач в программировании. Они разделяются на три категории:

  • Порождающие (Creational)

  • Структурные (Structural)

  • Поведенческие (Behavioral)

В этой части статьи рассмотрим порождающие и структурные паттерны.

Порождающие паттерны

1. Singleton (Одиночка)

Описание: Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.

Когда использовать: Когда нужен единственный экземпляр класса для контроля доступа к общему ресурсу.

Пример кода:

object DatabaseConnection {   fun connect() {     println("Подключение к базе данных")   } }  fun main() {   DatabaseConnection.connect() }

Объяснение: в Kotlin есть ключевое слово object, которое автоматически создает синглтон.

2. Factory Method (Фабричный метод)

Описание: Определяет интерфейс для создания объектов, но позволяет подклассам решать, какой класс инстанцировать.

Когда использовать: Когда заранее неизвестны типы и зависимости объектов, с которыми должен работать ваш код.

Пример кода:

interface Transport {   fun deliver() }  class Truck : Transport {   override fun deliver() {     println("Доставка грузовиком по суше")   } }  class Ship : Transport {   override fun deliver() {     println("Доставка кораблем по морю")   } }  abstract class Logistics {   abstract fun createTransport(): Transport    fun planDelivery() {     val transport = createTransport()     transport.deliver()   } }  class RoadLogistics : Logistics() {   override fun createTransport(): Transport = Truck() }  class SeaLogistics: Logistics() {   override fun createTransport = Ship() }  fun main() {   val logistics: Logistics = RoadLogistics()   logistics.planDelivery() }

3. Abstract Factory (Абстрактная фабрика)

Описание: Предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.

Когда использовать: Когда система должна быть независимой от процесса создания, композиции и представления продуктов.

Пример кода:

interface Button {   fun render() }  interface Checkbox {   fun render() }  class WindowsButton : Button {   override fun render() {     println("Рендеринг кнопки в стиле Windows")   } }  class MacOSButton : Button {   override fun render() {     println("Рендеринг кнопки в стиле MacOS")   } }  class WindowsCheckbox : Checkbox {   override fun render() {     println("Рендеринг чекбокса в стиле Windows")   } }  class MacOSCheckbox : Checkbox {   override fun render() {     println("Рендеринг чекбокса в стиле MacOS")   } }  interface GUIFactory {   fun createButton(): Button   fun createCheckbox(): Checkbox }  class WindowsFactory : GUIFactory {   override fun createButton(): Button = WindowsButton()   override fun createCheckbox(): Checkbox = WindowsCheckbox() }  class MacOSFactory : GUIFactory {   override fun createButton(): Button = MacOSButton()   override fun createCheckbox(): Checkbox = MacOSCheckbox() }  fun main() {   val factory: GUIFactory = WindowsFactory()   val button = factory.createButton()   val checkbox = factory.createCheckbox()   button.render()   checkbox.render() }

4. Builder (Строитель)

Описание: Разделяет создание сложного объекта от его представления, так что один и тот же процесс строительства может создавать разные представления.

Когда использовать: Когда процесс создания объекта должен быть независим от его составляющих и как они собираются.

Пример кода:

class House private constructor(   val walls: Int,   val doors: Int,    val windows: Int,   val hasGarage: Boolean,   val hasSwimmingPool: Boolean ) {   data class Builder(     var walls: Int = 0,     var doors: Int = 0,     var windows: Int = 0,     var hasGarage: Boolean = false,     var hasSwimmingPool: Boolean = false   ) {     fun walls(count: Int) = apply { this.walls = count }     fun doors(count: Int) = apply { this.doors = count }     fun windows(count: Int) = apply { this.windows = count }     fun hasGarage(value: Boolean) = apply { this.hasGarage = value }     fun hasSwimmingPool(value: Boolean) = apply { this.hasSwimmingPool = value }     fun build() = House(walls, doors, windows, hasGarage, hasSwimmingPool)   } }  fun main() {   val house = House.Builder()       .walls(4)       .doors(2)       .windows(6)       .hasGarage(true)       .build()    println("Дом с ${house.walls} стенами, ${house.doors} дверями, " +            "${house.windows} окнами, гараж: ${house.hasGarage}")     }

5. Prototype (Прототип)

Описание: Позволяет копировать объекты не вдаваясь в подробности их реализации.

Когда использовать: Когда создание объекта дорогостоящее или сложно, а копирование может быть более эффективным.

Пример кода:

abstract class Shape : Cloneable {   var x: Int = 0   var y: Int = 0    public override fun clone(): Shape {     return super.clone() as Shape   }    abstract fun draw() }  class Rectangle(var width: Int, var height: Int) : Shape() {   override fun clone(): Shape {     val clone = super.clone() as Rectangle     clone.width = this.width     clone.height = this.height     return clone   }    override fun draw() {     println("Рисуем прямоугольник шириной $width и высотой $height")   } }  fun main() {   val original = Rectangle(10, 20)   val copy = original.clone() as Rectangle   copy.width = 30    original.draw()   copy.draw() }

Структурные паттерны

6. Adapter (Адаптер)

Описание: Позволяет объектам с несовместимыми интерфейсами работать вместе.

Когда использовать: Когда нужно использовать существующий класс, но его интерфейс не соответствует потребностям.

Пример кода:

interface RoundPeg {   val radius: Double }  class RoundHole(val radius: Double) {   fun fits(peg: RoundPeg): Boolean = this.radius >= peg.radius }  class SquarePeg(val width: Double)  class SquarePegAdapter(private val peg: SquarePeg) : RoundPeg {   override val radius: Double       get() = peg.width * Math.sqrt(2.0) / 2 }  fun main() {   val hole = RoundHole(5.0)   val smallSquarePeg = SquarePeg(5.0)   val largeSquarePeg = SquarePeg(10.0)    val smallPegAdapter = SquarePegAdapter(smallSquarePeg)   val largePegAdapter = SquarePegAdapter(largeSquarePeg)    println("Малый квадратный колышек подходит? ${hole.fits(smallPegAdapter)}")   println("Большой квадратный колышек подходит? ${hole.fits(largePegAdapter)}") }

7. Bridge (Мост)

Описание: Разделяет абстракцию и реализацию так, чтобы они могли изменяться независимо.

Когда использовать: Когда нужно разделить монолитный класс на несколько отдельных иерархий.

Пример кода:

interface Device {   var volume: Int   var isEnabled: Boolean    fun enable()   fun disable() }  class Radio : Device {   override var volume: Int = 30   override var isEnabled: Boolean = false    override fun enable() {     isEnabled = true     println("Радио включено")   }    override fun disable() {     isEnabled = false     println("Радио выключено")   } }  class TV : Device {   override var volume: Int = 50   override var isEnabled: Boolean = false    override fun enable() {     isEnabled = true     println("Телевизор включен")   }    override fun disable() {     isEnabled = false     println("Телевизор выключен")   } }  abstract class Remote(val device: Device) {   fun togglePower() {     if (device.isEnabled) {       device.disable()     } else {       device.enable()     }   }    fun volumeUp() {     device.volume += 10     println("Громкость увеличена до ${device.volume}")   }    fun volumeDown() {     device.volume -= 10     println("Громкость уменьшена до ${device.volume}")   } }  class AdvancedRemote(device: Device) : Remote(device) {   fun mute() {     device.volume = 0     println("Звук выключен")   } }  fun main() {   val tv = TV()   val remote = AdvancedRemote(tv)    remote.togglePower()   remote.volumeUp()   remote.mute() }

8. Composite (Компоновщик)

Описание: Позволяет создавать древовидные структуры объектов и работать с ними как с единичными объектами.

Когда использовать: Когда нужно представить иерархию объектов и работать с ними единообразно.

Пример кода:

interface Graphic {   fun draw() }  class Dot(val x: Int, val y: Int) : Graphic {   override fun draw() {     println("Рисуем точку на координатах ($x, $y)")   } }  class Circle(x: Int, y: Int, val radius: Int) : Dot(x, y) {   override fun draw() {     println("Рисуем круг с центром ($x, $y) и радиусом $radius")   } }  class CompoundGraphic : Graphic {   private val children = mutableListOf<Graphic>()    fun add(child: Graphic) = children.add(child)   fun remove(child: Graphic) = children.remove(child)    override fun draw() {     println("Рисуем составной график")     children.forEach { it.draw() }   } }  fun main() {   val dot = Dot(1, 2)   val circle = Circle(5, 3, 10)    val compoundGraphic = CompoundGraphic()   compoundGraphic.add(dot)   compoundGraphic.add(circle)    compoundGraphic.draw() }

9. Decorator (Декоратор)

Описание: Динамически добавляет объектам новые обязанности.

Когда использовать: Когда нужно добавить обязанности объекту, не затрагивая другие объекты.

Пример кода:

interface DataSource {   fun writeData(data: String)   fun readData(): String }  class FileDataSource(private val filename: String) : DataSource {   private var data: String = ""    override fun writeData(data: String) {     this.data = data     println("Данные записаны в файл $filename")   }    override fun readData(): String {     println("Чтение данных из файла $filename")     return data   } }  open class DataSourceDecorator(private val wrappee: DataSource) : DataSource {   override fun writeData(data: String) {     wrappee.writeData(data)   }   override fun readData(): String = wrappee.readData() }  class EncryptionDecorator(wrappee: DataSource) : DataSourceDecorator(wrappee) {   override fun writeData(data: String) {     val encryptedData = "encrypted($data)"     super.writeData(encryptedData)   }    override fun readData(): String {     val data = super.readData()     return "decrypted($data)"   } }  fun main() {   val source = FileDataSource("somefile.dat")   val encryptedSource = EncryptionDecorator(source)    encryptedSource.writeData("важные данные")   println(encryptedSource.readData()) }

10. Facade (Фасад)

Описание: Предоставляет унифицированный интерфейс к набору интерфейсов в системе.

Когда использовать: Когда нужно упростить взаимодействие с комплексной системой.

Пример кода:

class CPU {   fun jump(position: Long) = println("CPU переходит к $position")   fun execute() = println("CPU выполняет инструкции") }  class Memory {   fun load(position: Long, data: String) =        println("Память загружает данные '$data' на позицию $position") }  class HardDrive {   fun read(lba: Long, size: Int): String {     println("Жесткий диск читает $size байт с позиции $lba")     return "данные"   } }  class ComputerFacade {   private val cpu = CPU()   private val memory = Memory()   private val hardDrive = HardDrive()    fun start() {     val bootData = hardDrive.read(0, 1024)     memory.load(0, bootData)     cpu.jump(0)     cpu.execute()   } }  fun main() {   val computer = ComputerFacade()   computer.start() }

11. Flyweight (Приспособленец)

Описание: Позволяет вместить большее количество объектов, используя разделение общего состояния между ними.

Когда использовать: Когда приложение должно эффективно подддерживать множество мелких объектов.

Пример кода:

data class TreeType(val name: String, val color: string, val texture: String)  class Tree(val x: Int, val y: Int, val type: TreeType) {   fun draw() = println("Рисуем дерево '${type.name}' на позиции ($x, $y)") }  object TreeFactory {   private val treeTypes = mutableMapOf<String, TreeType>()    fun getTreeType(name: String, color: String, texture: String): TreeType {     val key = "$name-$color-$texture"     return treeTypes.getOrPut(key) { TreeType(name, color, texture) }   } }  class Forest {   private val trees = mutableListOf<Tree>()    fun plantTree(x: Int, y: Int, name: String, color: String, texture: String) {     val type = TreeFactory.getTreeType(name, color, texture)     val tree = Tree(x, y, type)     trees.add(tree)   }    fun draw() = trees.forEach { it.draw() } }  fun main() {   val forest = Forest()   forest.plantTree(1, 2, "Дуб", "Зеленый", "Грубая")   forest.plantTree(3, 4, "Дуб", "Зеленый", "Грубая")   forest.plantTree(5, 6, "Береза", "Белый", "Гладкая")    forest.draw() }

12. Proxy (Заместитель)

Описание: Предоставляет суррогатный объект, контролирующий доступ к другому объекту.

Когда использовать: Когда нужен более функциональный или изолированный доступ к объекту.

Пример кода:

interface Image {   fun display() }  class RealImage(private val filename: String) : Image {   init {     loadFromDisk()   }    private fun loadFromDisk() = println("Загрузка $filename с диска")    override fun display() = println("Отображение $filename") }  class ProxyImage(private val filename: String) : Image {   private var realImage: RealImage? = null    override fun display() {     if (realImage == null) {       realImage = RealImage(filename)     }     realImage?.display()   } }  fun main() {   val image = ProxyImage("test_image.jpg")   image.display() // загрузка произойдет здесь   image.display() // загрузка не произойдет }

В следующей части статьи рассмотрим поведенческие паттерны.


ссылка на оригинал статьи https://habr.com/ru/articles/859520/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *