Паттерны проектирования - проверенные временем решения общих задач в программировании. Они разделяются на три категории:
Порождающие (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() // загрузка не произойдет
}
В следующей части статьи рассмотрим поведенческие паттерны.
e_Hector
В
Kotlin
нет места билдерам.Идиоматично, использовать в таком случае комбинацию дефолтных и именованных параметров