Projeto mantido por Leonardo Alves
Inspirado por Design-Patterns-In-Kotlin by Dariusz Baciński
Design patterns (Padrões de projeto) são soluções de problemas comuns em projetos de desenvolvimento de software, essas soluções já foram amplamente estudadas, testadas e consolidadas.
Padrões de Criação | Padrões Estruturais | Padrões Comportamentais | |
---|---|---|---|
Abtract Factory | Adapter | Chain of Reponsibility | Observer |
Builder | Facade | Command | Stade |
Factory Method | Bridge | Strategy | Interprete |
Prototype | Decorator | Iterator | Template Method |
Singleton | Flyweight | Mediator | Visitor |
Composite | Memento | ||
Proxy |
Os padrões de criação abstraem o processo de instaciação. Eles ajudam a tornar um sistema independente de como seus objetos são criados, composto e representados. Há dois temas recorrentes nesse padrões. Primeiro, todos encapsulam conhecimento sobre quais classes concretas são usadas pelo sistema. Segundo, ocultam o modo como as instâncias destas classes concretas são criadas e compostas.
Fornece uma interface para a criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
//Produto Abstrato
interface Simpson
//Produtos Concretos
class HomerSimpson : Simpson
class MargeSimpson : Simpson
//Fábrica Abstrata
abstract class SimpsonFactory {
abstract fun makeSimpson(): Simpson
companion object {
inline fun <reified T : Simpson> createFactory(): SimpsonFactory =
when (T::class) {
HomerSimpson::class -> HomerFactory()
MargeSimpson::class -> MargeFactory()
else -> throw IllegalArgumentException()
}
}
}
//Fábricas Concretas
class HomerFactory : SimpsonFactory() {
override fun makeSimpson(): Simpson = HomerSimpson()
}
class MargeFactory : SimpsonFactory() {
override fun makeSimpson(): Simpson = MargeSimpson()
}
val simpsonFactory = SimpsonFactory.createFactory<MargeSimpson>()
val simpson = simpsonFactory.makeSimpson()
println("Simpson instanciado: $simpson")
Simpson instanciado: MargeSimpson@67205a84
Separa a construção de um objeto complexo de sua representação, de modo que o mesmo processo do remetente de construção possa criar diferentes representações.
// Vamos supor que a classe Dialog seja fornecida por uma biblioteca externa.
// Temos apenas acesso à interface pública do Dialog que não pode ser alterada.
// Product
class Dialog {
fun setTitle(text: String) = println("definindo texto do título: '$text'")
fun setTitleColor(color: String) = println("definindo a cor do título: $color")
fun setMessage(text: String) = println("definindo a mensagem: '$text'")
fun setMessageColor(color: String) = println("definindo a cor da mensagem: $color")
fun setImage(bitmapBytes: ByteArray) = println("mostrando imagem com tamanho: '${bitmapBytes.size}'")
fun show() = println("mostrando dialog: $this")
}
// Builder
class DialogBuilder() {
constructor(init: DialogBuilder.() -> Unit) : this() {
init()
}
private var titleHolder: TextView? = null
private var messageHolder: TextView? = null
private var imageHolder: File? = null
fun title(attributes: TextView.() -> Unit) {
titleHolder = TextView().apply { attributes() }
}
fun message(attributes: TextView.() -> Unit) {
messageHolder = TextView().apply { attributes() }
}
fun image(attributes: () -> File) {
imageHolder = attributes()
}
fun build(): Dialog {
println("build")
val dialog = Dialog()
titleHolder?.apply {
dialog.setTitle(text)
dialog.setTitleColor(color)
}
messageHolder?.apply {
dialog.setMessage(text)
dialog.setMessageColor(color)
}
imageHolder?.apply {
dialog.setImage(readBytes())
}
return dialog
}
class TextView {
var text: String = ""
var color: String = "#00000"
}
}
//Função que cria o builder dos dialogs e cria o Dialog que é a classe produto.
fun dialog(init: DialogBuilder.() -> Unit): Dialog = DialogBuilder(init).build()
val dialog: Dialog = dialog {
title {
text = "Dialog título"
}
message {
text = "Dialog mensagem"
color = "#333333"
}
image {
File.createTempFile("image", "jpg")
}
}
dialog.show()
definindo texto do título: 'Dialog título'
definindo a cor do título: #00000
definindo a mensagem: 'Dialog mensagem'
definindo a cor da mensagem: #333333
mostrando imagem com tamanho: '0'
Mostrar dialog
mostrando dialog: Dialog@731a74c
Define uma interface para criar um objeto, mas deixa as subclasses decidirem qual classe postergar (defer) a instanciação às subclasses.
open abstract class Pais {
object EUA : Pais()
}
class Espanha : Pais()
class Grecia(val algumaPropriedade: String) : Pais()
class Canada(val algumaPropriedade: String) : Pais()
//Produto concreto.
class Moeda(val code: String)
//Creator.
object MoedaFactory {
fun moedaPorPais(pais: Pais): Moeda = when (pais) {
is Grecia -> Moeda("EUR")
is Espanha -> Moeda("EUR")
is Pais.EUA -> Moeda("USD")
else -> Moeda("CAD")
}
}
val moedaGrecia = MoedaFactory.moedaPorPais(Grecia("")).code
println("Moeda da Grécia: $moedaGrecia")
val moedaEUA = MoedaFactory.moedaPorPais(Pais.EUA).code
println("Moeda dos EUA: $moedaEUA")
assertThat(moedaGrecia).isEqualTo("EUR")
assertThat(moedaEUA).isEqualTo("USD")
Moeda da Grécia: EUR
Moeda dos EUA: USD
Garante que uma classe tenha somente uma instância e fornece um ponto global de acesso para ela.
object Impressora {
init {
println("Inicializando o objeto: $this")
}
fun imprimir(): Impressora = apply { println("Imprimindo objeto: $this") }
}
println("Start")
val impressora1 = Impressora.imprimir()
val impressora2 = Impressora.imprimir()
Start
Inicializando o objeto: Impressora@192b07fd
Imprimindo objeto: Impressora@192b07fd
Imprimindo objeto: Impressora@192b07fd
Na engenharia de software, os padrões de design estrutural são padrões de design que facilitam o design, identificando uma maneira simples de realizar relacionamentos entre entidades.
Converte a interface de uma classe em outra interface esperada pelos clientes. O Adapter permite que certas classes trabalhem em conjunto, pois, de outra forma seria impossível por causa de suas interfaces incompatíveis.
interface Temperature {
var temperature: Double
}
class CelsiusTemperature(override var temperature: Double) : Temperature
class FahrenheitTemperature(var celsiusTemperature: CelsiusTemperature) : Temperature {
override var temperature: Double
get() = convertCelsiusToFahrenheit(celsiusTemperature.temperature)
set(temperatureInF) {
celsiusTemperature.temperature = convertFahrenheitToCelsius(temperatureInF)
}
private fun convertFahrenheitToCelsius(f: Double): Double = (f - 32) * 5 / 9
private fun convertCelsiusToFahrenheit(c: Double): Double = (c * 9 / 5) + 32
}
val celsiusTemperature = CelsiusTemperature(0.0)
val fahrenheitTemperature = FahrenheitTemperature(celsiusTemperature)
celsiusTemperature.temperature = 36.6
println("${celsiusTemperature.temperature} C -> ${fahrenheitTemperature.temperature} F")
fahrenheitTemperature.temperature = 100.0
println("${fahrenheitTemperature.temperature} F -> ${celsiusTemperature.temperature} C")
36.6 C -> 97.88000000000001 F
100.0 F -> 37.77777777777778 C
Atribuir indefensabilidades adicionais a um objeto dinamicamente. Os decorators fornecem uma alternativa flexível as subclasses para extensão da funcionalidade.
interface CoffeeMachine {
fun makeSmallCoffee()
fun makeLargeCoffee()
}
class NormalCoffeeMachine : CoffeeMachine {
override fun makeSmallCoffee() = println("Normal: Making small coffee")
override fun makeLargeCoffee() = println("Normal: Making large coffee")
}
//Decorator:
class EnhancedCoffeeMachine(val coffeeMachine: CoffeeMachine) : CoffeeMachine by coffeeMachine {
// overriding behaviour
override fun makeLargeCoffee() {
println("Enhanced: Making large coffee")
coffeeMachine.makeLargeCoffee()
}
// extended behaviour
fun makeCoffeeWithMilk() {
println("Enhanced: Making coffee with milk")
coffeeMachine.makeSmallCoffee()
println("Enhanced: Adding milk")
}
}
val normalMachine = NormalCoffeeMachine()
val enhancedMachine = EnhancedCoffeeMachine(normalMachine)
// non-overridden behaviour
enhancedMachine.makeSmallCoffee()
// overriding behaviour
enhancedMachine.makeLargeCoffee()
// extended behaviour
enhancedMachine.makeCoffeeWithMilk()
Normal: Making small coffee
Enhanced: Making large coffee
Normal: Making large coffee
Enhanced: Making coffee with milk
Normal: Making small coffee
Enhanced: Adding milk
Fornece uma interface unificada para um conjunto de interfaces em um subsistema. O Façade define uma interface de nível mais alto que torna o subsistema fácil de usar.
class ComplexSystemStore(val filePath: String) {
init {
println("Reading data from file: $filePath")
}
val store = HashMap<String, String>()
fun store(key: String, payload: String) {
store.put(key, payload)
}
fun read(key: String): String = store[key] ?: ""
fun commit() = println("Storing cached data: $store to file: $filePath")
}
data class User(val login: String)
//Facade:
class UserRepository {
val systemPreferences = ComplexSystemStore("/data/default.prefs")
fun save(user: User) {
systemPreferences.store("USER_KEY", user.login)
systemPreferences.commit()
}
fun findFirst(): User = User(systemPreferences.read("USER_KEY"))
}
val userRepository = UserRepository()
val user = User("dbacinski")
userRepository.save(user)
val resultUser = userRepository.findFirst()
println("Found stored user: $resultUser")
Reading data from file: /data/default.prefs
Storing cached data: {USER_KEY=dbacinski} to file: /data/default.prefs
Found stored user: User(login=dbacinski)
Fornece um objeto representante ou um marcador de outro objeto para controlar o acesso ao mesmo.
interface File {
fun read(name: String)
}
class NormalFile : File {
override fun read(name: String) = println("Reading file: $name")
}
//Proxy:
class SecuredFile : File {
val normalFile = NormalFile()
var password: String = ""
override fun read(name: String) {
if (password == "secret") {
println("Password is correct: $password")
normalFile.read(name)
} else {
println("Incorrect password. Access denied!")
}
}
}
val securedFile = SecuredFile()
securedFile.read("readme.md")
securedFile.password = "secret"
securedFile.read("readme.md")
Incorrect password. Access denied!
Password is correct: secret
Reading file: readme.md
Compõe objetos em estrutura de árvore para representar hierarquia do tipo partes-todos. O Composite permite que os clientes tratem objetos individuais e composições de objetos de maneira uniforme.
open class Equipment(private var price: Int, private var name: String) {
open fun getPrice(): Int = price
}
/*
[composite]
*/
open class Composite(name: String) : Equipment(0, name) {
val equipments = ArrayList<Equipment>()
fun add(equipment: Equipment) {
this.equipments.add(equipment);
}
override fun getPrice(): Int {
return equipments.map { it -> it.getPrice() }.sum()
}
}
/*
leafs
*/
class Cabbinet : Composite("cabbinet")
class FloppyDisk : Equipment(70, "Floppy Disk")
class HardDrive : Equipment(250, "Hard Drive")
class Memory : Equipment(280, "Memory")
var cabbinet = Cabbinet()
cabbinet.add(FloppyDisk())
cabbinet.add(HardDrive())
cabbinet.add(Memory())
println(cabbinet.getPrice())
600
Na engenharia de software, os padrões de design comportamental são padrões de design que identificam padrões de comunicação comuns entre objetos e realizam esses padrões. Ao fazer isso, esses padrões aumentam a flexibilidade na realização dessa comunicação.
Define uma dependência uma-para-muitos entre objetos, de modo que, quando um objeto muda de estado, todos os seus dependentes são automaticamente notificados e atualizados.
interface TextChangedListener {
fun onTextChanged(oldText: String, newText: String)
}
class PrintingTextChangedListener : TextChangedListener {
var text = ""
override fun onTextChanged(oldText: String, newText: String) {
text = "Text is changed: $oldText -> $newText"
}
}
class TextView {
val listeners = mutableListOf<TextChangedListener>()
var text: String by Delegates.observable("<empty>") { _, old, new ->
listeners.forEach { it.onTextChanged(old, new) }
}
}
val textView = TextView().apply {
listener = PrintingTextChangedListener()
}
with(textView) {
text = "Lorem ipsum"
text = "dolor sit amet"
}
Text is changed <empty> -> Lorem ipsum
Text is changed Lorem ipsum -> dolor sit amet
Define uma família de algoritmos encapsula cada um deles e os torna intercambiáveis. O Strategy permite que o algoritmo varie.
class Printer(private val stringFormatterStrategy: (String) -> String) {
fun printString(string: String) {
println(stringFormatterStrategy(string))
}
}
val lowerCaseFormatter: (String) -> String = { it.toLowerCase() }
val upperCaseFormatter = { it: String -> it.toUpperCase() }
val inputString = "LOREM ipsum DOLOR sit amet"
val lowerCasePrinter = Printer(lowerCaseFormatter)
lowerCasePrinter.printString(inputString)
val upperCasePrinter = Printer(upperCaseFormatter)
upperCasePrinter.printString(inputString)
val prefixPrinter = Printer { "Prefix: $it" }
prefixPrinter.printString(inputString)
lorem ipsum dolor sit amet
LOREM IPSUM DOLOR SIT AMET
Prefix: LOREM ipsum DOLOR sit amet
Encapsula uma solicitação como um objeto, desta forma permitindo que você parametrize clientes com diferentes solicitações, enfileire ou registre (log) solicitações e suporte operações que podem se desfeitas.
interface OrderCommand {
fun execute()
}
class OrderAddCommand(val id: Long) : OrderCommand {
override fun execute() = println("Adding order with id: $id")
}
class OrderPayCommand(val id: Long) : OrderCommand {
override fun execute() = println("Paying for order with id: $id")
}
class CommandProcessor {
private val queue = ArrayList<OrderCommand>()
fun addToQueue(orderCommand: OrderCommand): CommandProcessor =
apply {
queue.add(orderCommand)
}
fun processCommands(): CommandProcessor =
apply {
queue.forEach { it.execute() }
queue.clear()
}
}
CommandProcessor()
.addToQueue(OrderAddCommand(1L))
.addToQueue(OrderAddCommand(2L))
.addToQueue(OrderPayCommand(2L))
.addToQueue(OrderPayCommand(1L))
.processCommands()
Adding order with id: 1
Adding order with id: 2
Paying for order with id: 2
Paying for order with id: 1
Permite que um objeto altere seu comportamento quando seu estado interno muda. O objeto parecerá ter mudado de classe.
sealed class AuthorizationState
object Unauthorized : AuthorizationState()
class Authorized(val userName: String) : AuthorizationState()
class AuthorizationPresenter {
private var state: AuthorizationState = Unauthorized
val isAuthorized: Boolean
get() = when (state) {
is Authorized -> true
is Unauthorized -> false
}
val userName: String
get() {
val state = this.state //val enables smart casting of state
return when (state) {
is Authorized -> state.userName
is Unauthorized -> "Unknown"
}
}
fun loginUser(userName: String) {
state = Authorized(userName)
}
fun logoutUser() {
state = Unauthorized
}
override fun toString() = "User '$userName' is logged in: $isAuthorized"
}
val authorizationPresenter = AuthorizationPresenter()
authorizationPresenter.loginUser("admin")
println(authorizationPresenter)
authorizationPresenter.logoutUser()
println(authorizationPresenter)
User 'admin' is logged in: true
User 'Unknown' is logged in: false
Evita o acoplamento do remetente de uma solicitação ao seu destinatário, dando a mais de um objeto a chance de tratar a solicitação. Encadeia os objetos receptores e passa a solicitação ao longo da cadeia até que um objeto a trate.
interface HeadersChain {
fun addHeader(inputHeader: String): String
}
class AuthenticationHeader(val token: String?, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String {
token ?: throw IllegalStateException("Token should be not null")
return inputHeader + "Authorization: Bearer $token\n"
.let { next?.addHeader(it) ?: it }
}
}
class ContentTypeHeader(val contentType: String, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String =
inputHeader + "ContentType: $contentType\n"
.let { next?.addHeader(it) ?: it }
}
class BodyPayload(val body: String, var next: HeadersChain? = null) : HeadersChain {
override fun addHeader(inputHeader: String): String =
inputHeader + "$body"
.let { next?.addHeader(it) ?: it }
}
//create chain elements
val authenticationHeader = AuthenticationHeader("123456")
val contentTypeHeader = ContentTypeHeader("json")
val messageBody = BodyPayload("Body:\n{\n\"username\"=\"dbacinski\"\n}")
//construct chain
authenticationHeader.next = contentTypeHeader
contentTypeHeader.next = messageBody
//execute chain
val messageWithAuthentication =
authenticationHeader.addHeader("Headers with Authentication:\n")
println(messageWithAuthentication)
val messageWithoutAuth =
contentTypeHeader.addHeader("Headers:\n")
println(messageWithoutAuth)
Headers with Authentication:
Authorization: Bearer 123456
ContentType: json
Body:
{
"username"="dbacinski"
}
Headers:
ContentType: json
Body:
{
"username"="dbacinski"
}
Representa uma operação a ser executada sobre os elementos da estrutura de um objeto. O visitor permite que você defina uma nova operação sem mudar as classes dos elementos sobre os quais opera.
interface ReportVisitable {
fun <R> accept(visitor: ReportVisitor<R>): R
}
class FixedPriceContract(val costPerYear: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
class TimeAndMaterialsContract(val costPerHour: Long, val hours: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
class SupportContract(val costPerMonth: Long) : ReportVisitable {
override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
}
interface ReportVisitor<out R> {
fun visit(contract: FixedPriceContract): R
fun visit(contract: TimeAndMaterialsContract): R
fun visit(contract: SupportContract): R
}
class MonthlyCostReportVisitor : ReportVisitor<Long> {
override fun visit(contract: FixedPriceContract): Long =
contract.costPerYear / 12
override fun visit(contract: TimeAndMaterialsContract): Long =
contract.costPerHour * contract.hours
override fun visit(contract: SupportContract): Long =
contract.costPerMonth
}
class YearlyReportVisitor : ReportVisitor<Long> {
override fun visit(contract: FixedPriceContract): Long =
contract.costPerYear
override fun visit(contract: TimeAndMaterialsContract): Long =
contract.costPerHour * contract.hours
override fun visit(contract: SupportContract): Long =
contract.costPerMonth * 12
}
val projectAlpha = FixedPriceContract(costPerYear = 10000)
val projectGamma = TimeAndMaterialsContract(hours = 150, costPerHour = 10)
val projectBeta = SupportContract(costPerMonth = 500)
val projectKappa = TimeAndMaterialsContract(hours = 50, costPerHour = 50)
val projects = arrayOf(projectAlpha, projectBeta, projectGamma, projectKappa)
val monthlyCostReportVisitor = MonthlyCostReportVisitor()
val monthlyCost = projects.map { it.accept(monthlyCostReportVisitor) }.sum()
println("Monthly cost: $monthlyCost")
assertThat(monthlyCost).isEqualTo(5333)
val yearlyReportVisitor = YearlyReportVisitor()
val yearlyCost = projects.map { it.accept(yearlyReportVisitor) }.sum()
println("Yearly cost: $yearlyCost")
assertThat(yearlyCost).isEqualTo(20000)
Monthly cost: 5333
Yearly cost: 20000
O padrão de design do mediador é usado para fornecer um meio de comunicação centralizado entre diferentes objetos em um sistema. Esse padrão é muito útil em um aplicativo corporativo em que vários objetos estão interagindo entre si.
class ChatUser(private val mediator: ChatMediator, val name: String) {
fun send(msg: String) {
println("$name: Sending Message= $msg")
mediator.sendMessage(msg, this)
}
fun receive(msg: String) {
println("$name: Message received: $msg")
}
}
class ChatMediator {
private val users: MutableList<ChatUser> = ArrayList()
fun sendMessage(msg: String, user: ChatUser) {
users
.filter { it != user }
.forEach {
it.receive(msg)
}
}
fun addUser(user: ChatUser): ChatMediator =
apply { users.add(user) }
}
val mediator = ChatMediator()
val john = ChatUser(mediator, "John")
mediator
.addUser(ChatUser(mediator, "Alice"))
.addUser(ChatUser(mediator, "Bob"))
.addUser(john)
john.send("Hi everyone!")
John: Sending Message= Hi everyone!
Alice: Message received: Hi everyone!
Bob: Message received: Hi everyone!
Sem violar o encapsulamento, captura e externaliza um estado interno de um objeto, de modo que o mesmo possa posteriormente ser restaurado para este estado.
data class Memento(val state: String)
class Originator(var state: String) {
fun createMemento(): Memento {
return Memento(state)
}
fun restore(memento: Memento) {
state = memento.state
}
}
class CareTaker {
private val mementoList = ArrayList<Memento>()
fun saveState(state: Memento) {
mementoList.add(state)
}
fun restore(index: Int): Memento {
return mementoList[index]
}
}
val originator = Originator("initial state")
val careTaker = CareTaker()
careTaker.saveState(originator.createMemento())
originator.state = "State #1"
originator.state = "State #2"
careTaker.saveState(originator.createMemento())
originator.state = "State #3"
println("Current State: " + originator.state)
assertThat(originator.state).isEqualTo("State #3")
originator.restore(careTaker.restore(1))
println("Second saved state: " + originator.state)
assertThat(originator.state).isEqualTo("State #2")
originator.restore(careTaker.restore(0))
println("First saved state: " + originator.state)
Current State: State #3
Second saved state: State #2
First saved state: initial state