Kotlin 語言下設計模式的不同實現
偶然在 Github 上看到 dbacinski 寫的 Kotlin 語言下設計模式的不同實現(這里的不同是相對于 Java 語言的),有些實現非常好,但是有些實現的例子不是很贊同。所以自己寫了 Kotlin 語言版本的 23 種設計模式的實現,充分利用 Kotlin 的語法糖,例如單例模式、策略模式等可以很巧妙地實現,其他實現方式與 Java 不變的也有代碼示例,就當是回顧設計模式。
創建型模式
工廠方法模式
工廠方法把創建對象的過程抽象為接口,由工廠的子類決定對象的創建,Kotlin 下的實現與 Java 一樣。
interface Product{
val name: String
}
class ProductA(override val name: String = "ProductA") : Product
class ProductB(override val name: String = "ProductB") : Product
interface Factory{
fun makeProduct(): Product
}
class FactoryA:Factory {
override fun makeProduct() = ProductA()
}
class FactoryB:Factory {
override fun makeProduct() = ProductB()
}
抽象工廠模式
工廠方法針對一種產品,而抽象工廠是針對一系列產品,為每種產品定義一個工廠方法,工廠子類負責創建該系列的多種產品,Kotlin 下的實現與 Java 一樣。
class SeriesATextView(context: Context?) : TextView(context)
class SeriesBTextView(context: Context?) : TextView(context)
class SeriesAButton(context: Context?) : Button(context)
class SeriesBButton(context: Context?) : Button(context)
interface AbstractFactory{
val context: Context?
fun makeTextView(): TextView
fun makeButton(): Button
}
class SeriesAFactory(override val context: Context?) : AbstractFactory {
override fun makeTextView() = SeriesATextView(context)
override fun makeButton() = SeriesAButton(context)
}
class SeriesBFactory(override val context: Context?) : AbstractFactory {
override fun makeTextView() = SeriesBTextView(context)
override fun makeButton() = SeriesBButton(context)
}
建造者模式
建造者模式是為了構建復雜對象的,一般情況下,Kotlin 中使用標準的 apply 函數就可以了,例如下面創建 Dialog 的例子:
val dialog = Dialog(context).apply {
setTitle("DialogA")
setCancelable(true)
setCanceledOnTouchOutside(true)
setContentView(contentView)
}
不過上面的代碼中在 apply 里的 lambda 表達式里可以調用 Dialog.show() 等其他與構建對象無關的方法,或者不想公開構造函數,只想通過 Builder 來構建對象,這時可以使用 Type-Safe Builders:
class Car(
val model: String?,
val year: Int
) {
private constructor(builder: Builder) : this(builder.model, builder.year)
class Builder{
var model: String? = null
var year: Int = -1
fun build() = Car(this)
}
companion object {
inline fun build(block:Builder.() -> Unit) = Builder().apply(block).build()
}
}
// usage
val car = Car.build {
model = "John One"
year = 2017
}
原型模式
原型模式是以一個對象為原型,創建出一個新的對象,在 Kotlin 下很容易實現,因為使用 data class 時,會自動獲得 equals 、 hashCode 、 toString 和 copy 方法,而 copy 方法可以克隆整個對象并且允許修改新對象某些屬性。
data class EMail(var recipient: String, var subject: String?, var message: String?)
val mail = EMail("abc@example.com", "Hello", "Don't know what to write.")
val copy = mail.copy(recipient = "other@example.com")
單例模式
單例模式在 Kotlin 下直接使用 object 就行了。想要實現懶漢式單例或更詳細的內容,請看之前的文章 Kotlin 設計模式之單例模式 。
結構型模式
適配器模式
適配器模式是把一個不兼容的接口轉化為另一個類可以使用的接口,Kotlin 下的實現與 Java 一樣。
interface Target{
fun request()
}
interface Adaptee{
fun ask()
}
class Adapter(val wrapper: Adaptee) : Target {
override fun request() {
wrapper.ask()
}
}
橋接模式
橋接模式可以分離某個類存在兩個獨立變化的緯度,把多層繼承結構改為兩個獨立的繼承結構,在兩個抽象層中有一個抽象關聯,Kotlin 下的實現與 Java 一樣。
interface Color{
fun coloring();
}
class RedColor:Color { ... }
class BlueColor:Color { ... }
interface Pen{
val colorImpl: Color // this is bridge
fun write()
}
class BigPen(override val colorImpl: Color) : Pen { ... }
class SmallPen(override val colorImpl: Color) : Pen { ... }
組合模式
組合模式是對樹形結構的處理,讓調用者忽視單個對象和組合結構的差異,通常會新增包含葉子節點和容器節點接口的抽象構件 Component,Kotlin 下的實現與 Java 一樣。
interface AbstractFile{ // Component
var childCount: Int
fun getChild(i:Int): AbstractFile
fun size(): Long
}
class File(val size: Long, override var childCount: Int = 0) : AbstractFile {
override fun getChild(i:Int): AbstractFile {
throw RuntimeException("You shouldn't call the method in File")
}
override fun size() = size
}
class Folder(override var childCount: Int) : AbstractFile {
override fun getChild(i:Int): AbstractFile {
...
}
override fun size(): Long {
return (0..childCount)
.map { getChild(it).size() }
.sum()
}
}
裝飾模式
裝飾模式可以給一個對象添加額外的行為,在 Kotlin 中可以通過擴展函數簡單的實現。
class Text(val text: String) {
fun draw() = print(text)
}
fun Text.underline(decorated:Text.() -> Unit) {
print("_")
this.decorated()
print("_")
}
// usage
Text("Hello").run {
underline {
draw()
}
}
外觀模式
外觀模式是為一個復雜的子系統提供一個簡化的統一接口,Kotlin 下的實現與 Java 一樣,下面我直接使用 dbacinski 的例子。
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:$storeto 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"))
}
// usage
val userRepository = UserRepository()
val user = User("dbacinski")
userRepository.save(user)
val resultUser = userRepository.findFirst()
享元模式
享元模式以共享的方式高效地支持大量細粒度對象的重用,享元對象能做到共享的關鍵是區分了可共享內部狀態和不可共享外部狀態。Kotlin 下的實現與 Java 一樣。
enum class Color{
black, white
}
open class Chess(val color: Color) {
fun display(pos:Pair<Int,Int>) {
println("The$colorchess at position$pos")
}
}
class BlackChess:Chess(color = Color.black)
class WhiteChess:Chess(color = Color.white)
object ChessFactory {
private val table = Hashtable<Color, Chess>()
init {
table.put(Color.black, BlackChess())
table.put(Color.white, WhiteChess())
}
fun getChess(color:Color) = table[color]!!
}
// usage
val blackChess = ChessFactory.getChess(Color.black)
val whiteChess = ChessFactory.getChess(Color.white)
blackChess.display(Pair(9, 5))
whiteChess.display(Pair(5, 9))
whiteChess.display(Pair(2, 3))
代理模式
代理模式是使用一個代理對象來訪問目標對象的行為,Kotlin 下的實現與 Java 一樣,下面我也直接使用 dbacinski 的例子。
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) // call target object behavior
} else {
println("Incorrect password. Access denied!")
}
}
}
行為型模式
職責鏈模式
職責鏈模式通過建立一條鏈來組織請求的處理者,請求將沿著鏈進行傳遞,請求發送者無須知道請求在何時、何處以及如何被處理,實現了請求發送者與處理者的解耦。Kotlin 下的實現與 Java 一樣,看下面這個簡易的 Android 事件傳遞的例子,event 不知道是否被 ViewGroup 攔截并處理。
interface EventHandler{
var next: EventHandler?
fun handle(event:MotionEvent): Boolean
}
open class View:EventHandler {
override var next: EventHandler? = null
override fun handle(event:MotionEvent): Boolean {
return onTouchEvent()
}
open fun onTouchEvent() : Boolean {
...
return false
}
}
open class ViewGroup:View() {
override fun handle(event:MotionEvent): Boolean {
if (onInterceptTouchEvent(event)) return onTouchEvent()
else return next?.handle(event)!!
}
open fun onInterceptTouchEvent(event:MotionEvent): Boolean { // 是否攔截事件
...
return false
}
}
命令模式
命令模式是將請求封裝為命令對象,解耦請求發送者與接收者,對請求排隊或者記錄請求日志,以及支持可撤銷的操作。Kotlin 下的實現與 Java 一樣。
interface Command{
var value: Int
val param: Int
fun execute()
fun undo()
}
class AddCommand(override var value: Int, override val param: Int) : Command {
override fun execute() {
value += param
println("execute add command and value:$value")
}
override fun undo() {
value -= param
println("undo add command and value:$value")
}
}
class MultiplyCommand(override var value: Int, override val param: Int) : Command {
override fun execute() {
value *= param
println("execute multiply command and value:$value")
}
override fun undo() {
value /= param
println("undo multiply command and value:$value")
}
}
class Calculator{
val queue = mutableListOf<Command>()
fun compute(command:Command) {
command.execute()
queue.add(command)
}
fun undo() {
queue.lastOrNull()?.undo()
queue.removeAt(queue.lastIndex)
}
}
解釋器模式
解釋器模式是定義一個語言的文法,并且建立一個解釋器來解釋該語言中的句子,這里的“語言”是指使用規定格式和語法的代碼。因為使用頻率較低,而且 Kotlin 中也沒有特殊的實現,所以就不舉例說明了。
迭代器模式
迭代器模式提供一種遍歷聚合對象中的元素的一種方式,在不暴露底層實現的情況下。在 Kotlin 下,定義 operator fun iterator() 迭代器函數,或者是作為擴展函數時,可以在 for 循環中遍歷。
class Sentence(val words: List<String>)
operator fun Sentence.iterator(): Iterator<String> = words.iterator()
// or
operator fun Sentence.iterator(): Iterator<String> = object : Iterator<String> {
val iterator = words.iterator()
override fun hasNext() = iterator.hasNext()
override fun next() = iterator.next()
}
中介者模式
中介者模式用一個中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
interface ChatMediator{
fun sendMsg(msg:String, user:User)
fun addUser(user:User)
}
abstract class User(val name: String, val mediator: ChatMediator) {
abstract fun send(msg:String)
abstract fun receive(msg:String)
}
class ChatMediatorImpl:ChatMediator {
private val users = mutableListOf<User>()
override fun sendMsg(msg:String, user:User) {
users.filter { it != user }
.forEach { it.receive(msg) }
}
override fun addUser(user:User) {
users.add(user)
}
}
class UserImpl(name: String, mediator: ChatMediator) : User(name, mediator) {
override fun send(msg:String) {
println("$name: sending messages =$msg")
mediator.sendMsg(msg, this)
}
override fun receive(msg:String) {
println("$name: received messages =$msg")
}
}
備忘錄模式
備忘錄模式是在不破壞封裝的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,這樣可以在以后將對象恢復到原先保存的狀態。
data class Memento(val fileName: String, val content: StringBuilder)
class FileWriter(var fileName: String) {
private var content = StringBuilder()
fun write(str:String) {
content.append(str)
}
fun save() = Memento(fileName, StringBuilder(content))
fun restore(m:Memento) {
fileName = m.fileName
content = m.content
}
}
觀察者模式
觀察者模式是一個對象狀態發生變化后,可以立即通知已訂閱的另一個對象。在 Kotlin 下可以使用 observable properties,簡化實現。
interface TextChangedListener{
fun onTextChanged(newText:String)
}
class TextView{
var listener: TextChangedListener? = null
var text: String by Delegates.observable("") { prop, old, new ->
listener?.onTextChanged(new)
}
}
狀態模式
狀態模式將一個對象在不同狀態下的不同行為封裝在一個個狀態類中,通過設置不同的狀態可以讓對象擁有不同的行為。
sealed class UserState(val name:String, val isAuthorized: Boolean) {
abstract fun click()
class Unauthorized:UserState(name = "Unauthorized", isAuthorized = false) {
override fun click() {
print("User is unauthorized.")
}
}
class Authorized(name: String) : UserState(name, isAuthorized = true) {
override fun click() {
print("User is authorized as$name")
}
}
}
class User{
private var state: UserState = UserState.Unauthorized()
val name: String
get() = state.name
val isAuthorized: Boolean
get() = state.isAuthorized
fun click() = state.click()
fun login(name:String) {
state = UserState.Authorized(name)
}
fun logout() {
state = UserState.Unauthorized()
}
}
策略模式
策略模式用于算法的自由切換和擴展,分離算法的定義與實現,在 Kotlin 中可以使用高階函數作為算法的抽象。
class Customer(val name: String, val fee: Double, val discount: (Double) -> Double) {
fun pricePerMonth() = discount(fee)
}
// usage
val studentDiscount = { fee: Double -> fee/2 }
val noDiscount = { fee: Double -> fee }
val student = Customer("Ned", 10.0, studentDiscount)
val regular = Customer("John", 10.0, noDiscount)
模版方法模式
模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實現可以在其子類中完成,Kotlin 中使用高階函數可以避免繼承的方式。
class Task{
fun run(step2: () -> Unit, step3: () -> Unit) {
step1()
step2()
step3()
}
fun step1() { ... }
}
訪問者模式
訪問者模式提供一個作用于某對象結構中的各元素的操作表示,它使我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作。
interface Employee{
fun accept(visitor:Visitor)
}
class GeneralEmployee(val wage: Int) : Employee {
override fun accept(visitor:Visitor) = visitor.visit(this)
}
class ManagerEmployee(val wage: Int, val bonus: Int) : Employee {
override fun accept(visitor:Visitor) = visitor.visit(this)
}
interface Visitor{
fun visit(ge:GeneralEmployee)
fun visit(me:ManagerEmployee)
}
class FADVisitor:Visitor {
override fun visit(ge:GeneralEmployee) {
println("GeneralEmployee wage:${ge.wage}")
}
override fun visit(me:ManagerEmployee) {
println("ManagerEmployee wage:${me.wage + me.bonus}")
}
}
// other visitor ...
參考資料
Gang of Four Patterns in Kotlin
來自:http://johnnyshieh.me/posts/kotlin-design-pattern-impl/