Kotlin入门系列:Kotlin与设计模式

设计模式是软件工程中解决特定问题的一种指南,我们通常所说的经典的设计模式,是指软件设计领域中所阐述的23种设计模式。这里将通过kotlin的语言特性重新思考常见的设计模式以及如何在代码中实际应用它们。

设计模式的论述形式依旧采用GoF针对常见设计模式的分类方式,即创建型模式、行为型模式、结构性模式。

1 创建型模式

kotlin中几种最主流的创建型设计模式:工厂方法模式、抽象工厂模式以及构建者模式。

1.1 伴生对象增强工厂模式

工厂模式是我们最熟悉的设计模式之一,在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。

简单工厂的核心作用就是通过一个工厂类隐藏对象实例的创建逻辑,而不需要暴露给客户端。

假设现在有一个电脑加工厂,同时生产个人电脑和服务器主机。用熟悉的工厂模式设计描述其业务逻辑:

interface Computer {
	val cpu: String
}

class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computer

enum class ComputerType {
	PC, Server
}

class ComputerFactory {
	fun produce(type: ComputerType): Computer {
		return when(type) {
			ComputerType.PC -> PC()
			ComputerType.Server -> Server()
		}
	}
}

val comp = ComputerFactory().produce(ComputerType.PC)
println(comp.cpu)

输出:
Core

上面是使用kotlin模仿java的标准的工厂模式设计,它改善了程序的可维护性,但创建对象的表达式上却显得不够简洁:当我们在不同地方创建 Computer 的子类对象是,我们都需要先创建一个 ComputerFactory 类对象。

1.1.1 单例代替工厂

kotlin在语言层级支持单例。将上面的代码修改为单例来代替工厂模式:

object ComputerFactory {
	fun produce(type: ComputerType): Computer {
		return when(type) {
			ComputerType.PC -> PC()
			ComputerType.Server -> Server()
		}
	}
}

ComputerFactory.produce(ComputerType.PC)

kotlin支持运算符重载,因此我们可以通过 operator 操作符重载 invoke 代替 produce(),进一步简化表达式:

object ComputerFactory {
	operator fun invoke(type: ComputerType): Computer {
		return when(type) {
			ComputerType.PC -> PC()
			ComputerType.Server -> Server()
		}
	}
}

ComputerFactory(ComputerType.PC)

1.1.2 伴生对象创建静态工厂方法

当前的工厂模式实现也许还觉得不够完美:是否可以直接通过 Computer() 而不是 ComputerFactory() 来创建一个实例?

在kotlin可以使用伴生对象,它代替了java中的 static,同时在功能和表达上拥有更强的能力。

interface Computer {
	companion object {
		operator fun invoke(type: ComputerTyp): Computer {
			return when(type) {
				ComputerType.PC -> PC()
				ComputerType.Server -> Server()
			}
		}
	}
}interface Computer {
	val cpu: String

	companion object Factory {
		operator fun invoke(type: ComputerType): Computer {
			return when(type) {
				ComputerType.PC -> PC()
				ComputerType.Server -> Server()
			}
		}
	}
}

// 伴生对象没有名称
Computer(ComputerType.PC)// 伴生对象名为Factory
Computer.Factory(ComputerType.PC)

1.1.3 扩展伴生对象方法

上面的代码还可以进一步的进行改造,我们通过扩展函数来扩展。

Computer 增加一种功能,通过CPU型号来判断电脑类型:

// 伴生对象没有名称
fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when(cpu) {
	"Core" -> ComputerType.PC
	"Xeon" -> ComputerType.Server
	else -> null
}// 伴生对象名为Factory
fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when(cpu) {
	"Core" -> ComputerType.PC
	"Xeon" -> ComputerType.Server
	else -> null
}

1.2 内联函数简化抽象工厂

kotlin中的内联函数有一个很大的作用,就是可以具体化参数类型。利用这一特性,我们还可以改进一种更复杂的工厂模式,称为抽象工厂。

抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。

现在我们需要引入多个产品等级结构,比如现在引入了品牌商的概念,我们有好几个电脑品牌,那么就有必要再增加一个工厂类。然而,我们并不希望对每个模型都建立一个工厂,这会让代码变得难以维护,所以这时候就需要引入抽象工厂模式。

interface Computer
class Dell: Computer
class Asus: Computer
class Acer: Computer

abstract class AbstractFactory {
	abstract fun produce(): Computer

	companion object {
		operator fun invoke(factory: AbstractFactory): AbstractFactory {
			return factory
		}
	}
}

class DellFactory: AbstractFactory() {
	override fun produce() = Dell()
}
class AsusFactory: AbstractFactory() {
	override fun produce() = Asus()
}
class AcerFactory: AbstractFactory() {
	override fun produce() = Acer()
}

val dellFactory = AbstractFactory(DellFactory())
val dell = dellFactory.produce()

上面是抽象工厂的实现,但是每次创建具体的工厂类时都需要传入一个具体的工厂类对象作为参数构造,在语法上不是很优雅,我们用内联函数来改善:

abstract class AbstractFactory {
	abstract fun produce(): Computer

	companion object {
		// 添加关键字inline定义为内联函数,reified关键字可以使用具体化参数类型
		inline operator fun <reified T: Computer> invoke(): AbstractFactory = when(T::class) {
			Dell::class -> DellFactory()
			Asus::class -> AsusFactory()
			Acer::class -> AcerFactory()
			else -> throw IllegalArgumentException()
		}
	}
}

val dellFactory = AbstractFactory<Dell>()
val dell = dellFactory.produce()

1.3 用具名可选参数而不是构建者模式

在java开发中,如果设计不当就会出现构造方法或者重载方法出现过多参数的问题:

Robot robot = new Robot(1, true, true, false, false, false);

面对这样的多参数的业务场景,我们会使用构建者模式来解决。

构建者模式:构建者模式主要做的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

一种糟糕的做法是采用重叠的构造器模式,即先提供一个只有必要参数的构造函数,然后在提供其他更多的构造函数。这种做法当参数变得越来越多,代码会变得难以维护。

使用构建者模式可以避免以上问题:

class Robot private contructor(
	val code: String,
	val battery: String?,
	val height: Int?,
	val weight: Int?) {
	
	class Builder(val code: String) {
		private var bettery: String? = null
		private var height: Int? = null
		private var weight: Int?= null

		fun setBattery(battery: String?): Builder {
			this.battery = battery
			return this
		}

		fun setHeight(height: Int?): Builder {
			this.height = height
			return this
		}
	
		fun setWeight(weight: Int?): Builder {
			this.weight = weight
			return this
		}

		fun build(): Robot {
			return Robot(code, battery, height, weight)
		}
	}
}

val robot = Robot.Builder("007")
	.setBattery("R6")
	.setHeight(100)
	.setWeight(80)
	.build()

构建者模式也存在一些不足:

  • 如果业务需求的参数很多,代码依然会显得比较冗长

  • 你可能会在使用Builder的时候忘记在最后调用 build()

  • 由于在创建对象的时候,必须先创建它的构造器,因此额外增加了多余的开销,在某些十分注重性能的情况下,可能就存在一定问题

kotin在语言层级可以使用具名可选参数的方式来实现构建者模式的效果:

class Robot(
	val code: String,
	val battery: String? = null,
	val height: Int? = null,
	val weight: Int? = null) 

val robot1 = Robot(code = "007")
val robot2 = Robot(code = "007", battery = "R6")
val robot3 = Robot(code = "007", height = 100, weight = 80)

相比构建者模式,具名可选参数具有很多优点:

  • 代码变得十分简单,声明对象时每个参数名都可以是显式的,并且无需按照顺序书写,非常方便灵活

  • Robot 类每个对象属性都是 val 声明,相较构建者模式中用 var 更加安全,在多线程并发的业务场景会有优势

构建者模式还有另外一个作用,就是可以在 build() 中对参数添加约束条件:

fun build(): Robot {
	if (weight != null && battery == null) {
		throw IllegalArgumentException("Battery should be determined when setting weight.")
	} else {
		return Robot(code, battery, height, weight)
	}
}

在kotlin中,我们在类或函数中还可以使用 require 关键字进行函数参数限制,本质上它是一个内联函数,类似于java的 assert

class Robot(
	val code: String,
	val battery: String? = null,
	val height: Int? = null,
	val weight: Int? = null) {
	init {
		require(weight == null || battery != null) {
			"Battery should be determined when setting weight."
		}
	}
}

总的来说,在kotlin中我们应该尽量避免使用构建者模式,因为kotlin支持具名可选参数,让我们的代码更加简洁并利于维护。

2 行为型模式

当我们用创建型模式创建出类对象之后,就需要在不同对象之间划分职责、产生交互。哪些用来识别对象之间的常用交流模式就是行为型模式。行为型模式包括:观察者模式、策略模式、模板方法模式、迭代器模式、责任链模式及状态模式。

2.1 观察者模式

观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。

观察者模式无非做两件事情:

  • 订阅者(也称为观察者 observer)添加或删除对发布者(也称为被观察者 publisher)的状态监听

  • 发布者状态改变时,将时间通知给监听它的所有观察者,然后观察者执行响应逻辑

java提供了 java.util.Observablejava.util.Observer 接口:

class StockUpdate: Observable() {
	val observers = mutableSetOf<Observer>()

	fun setStockChanged(price: Int) {
		this.observer.forEach {
			it.update(this, price)
		}
	}
}

class StockDisplay: Observer {
	override fun update(o: Observable, price: Any) {
		if (o is StockUpdate) {
			println("The latest stock price is $price")
		}
	}
}

val su = StockUpdate()
val sd = StockDisplay()
su.observers.add(sd)
su.setStockChanged(100)

2.1.1 属性委托观察Deletages.observable()

先使用kotlin的委托改造观察者模式:

interface StockUpdateListener {
	fun onRise(price: Int)
	fun onFall(price: Int)
}

class StockDisplay: StockUpdateListener {
	override fun onRise(price: Int) {
		println("The latest stock price has risen to $price")
	}
	override fun onFall(price: Int) {
		println("The latest stock price has fell to $price")
	}
}

class StockUpdate {
	val listeners = mutableSetOf<StockUpdateListener>()

	var price: Int by Delegates.observable(0) { _, old, new ->
		if (new > old) it.onRise(price) else it.onFall(price)
	}
}

val su = StockUpdate()
val sd = StockDisplay()
su.listeners.add(sd)
su.price = 100
su.price = 98

使用 java.util.Observer 接口类只能覆写 update() 来编写响应逻辑,也就是说如果存在多种不同的逻辑响应,我们也必须通过在该方法中进行区分实现,显然这会让订阅者的代码显得臃肿。

使用 Delegates.observable() 更加灵活,它提供了3个参数,依次代表委托属性的元数据 KProperty、旧值以及新值。 通过额外定义一个 StockUpdateListener 接口将不同响应逻辑封装成接口方法,从而在 StockDisplay 中实现该接口的 onRise()onFall() 实现解耦。

2.1.2 Vetoable

Vetoable 提供了一种新功能,在被赋新值生效之前提前进行截获,然后判断是否接收它。

var value: Int by Delegates.vetoable(0) { prop, old, new ->
	// new > 0时才接收结果
	new > 0
}
value = 1
println(value)
value = -1
println(value) // 只接收被正整数赋值,试图把value改成-1打印结果仍旧为旧值1

输出:
1
1

2.2 策略模式

策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。本质上,策略模式做的事情就是将不同的行为策略进行独立封装,与类在逻辑上解耦。然后根据不同的上下文切换不同的策略,然后用类对象进行调用。

interface SwimStrategy {
	fun swim()
}
class Breaststroke: SwimStrategy {
	override fun swim() {
		println("I am breaststroking...")
	}
}
class Backstroke: SwimStrategy {
	override fun swim() {
		println("I am backstroke...")
	}
}
class Freestyle: swimStrategy {
	override fun swim() {
		println("I am freestyling...")
	}
}
class Swimmer(val strategy: SwimStrategy) {
	fun swim() {
		strategy.swim()
	}
}

val weekendShaw = Swimmer(FreeStyle())
weekendShaw.swim()
val weekdaysShaw = Swimmer(Breaststroke())
weekdaysShaw.swim()

上面是策略模式的代码,接下来我们使用kotlin的高阶函数抽象,将策略封装成一个函数然后作为参数传递给 Swimmer 类会更加简洁:

fun breaststroke() {
	println("I am breaststroking...")
}
fun backstroke() {
	println("I am backstroking...")
}
fun freestyle() {
	println("I am freestyling...")
}
class Swimmer(val swimming: () -> Unit) {
	fun swim() {
		swimming()
	}
}

val weekendShaw = Swimmer(::freestyle)
weekendShaw.swim()
val weekdaysShaw = Swimmer(::breaststroke)
weekdaysShaw.swim()

将策略算法都封装成一个个函数,通过传递函数引用实现。

2.3 模板方法模式

另一个可用高阶函数改良的设计模式就是模板方法模式。某种程度上,模板方法模式和策略模式要解决的问题是相似的,它们都可以分离通用的算法和具体上下文。

策略模式采用的思路是将算法进行委托,那么传统的模板方法模式更多是基于继承方式实现的。

模板方式定义一个算法中的操作框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。

abstract class CivicCenterTask {
	fun execute() {
		this.lineUp()
		this.askForHelp()
		this.evaluate()
	}

	private fun lineUp() {
		println("line up to take a number")
	}

	private fun evaluate() {
		println("evaluaten service attitude")
	}

	abstrat fun askForHelp()
}

class PullSocialSecurity : CivicCenterTask {
	override fun askForHelp() {
		println("ask for pulling the social security")
	}
}

class ApplyForCitizenCard : CivicCenterTask {
	override fun askForHelp() {
		println("apply for a citizen card")
	}
}

模板方式模式的代码复用性已经非常高了,但是我们还是得根据不同的业务场景定义一个具体的子类。

我们使用kotlin改造模板方法模式:

class CivicCenterTask {
	fun execute(askForHelp: () -> Unit) {
		this.lineUp()
		askForHelp()
		this.evaluate()
	}
	private fun lineUp() {
		println("line up to take a number")
	}
	private fun evaluate() {
		println("evaluaten service attitude")
	}
}

fun pullSocialSecurity() {
	println("ask for pulling the social security")
}

fun applyForCitizenCard() {
	println("apply for a citizen card")
}

val task = CivicCenterTask()
task.execute(::pullSocialSecurity)
task.execute(::applyForCitizenCard)

2.4 迭代器模式

迭代器模式的核心作用是将遍历和实现分离开,在遍历的同时不需要暴露对象的内部表示。

2.4.1 实现Iterator接口

data class Book(val name: String)
class Bookcase(val books: List<Book>): Iterator<Book> {
	private val iterator: Iterator<Book>
	init {
		this.iterator = books.iterator()
	}
	override fun hasNext() = this.iterator.hasNext()
	override fun next() = this.iterator.next()
}

val bookcase = Bookcase(listOf(Book("Dive into kotlin"), Book("Thinking in java")))
while(bookcase.hasNext()) {
	println("The book name is ${bookcase.next().name}"}
}for (book in bookcase) {
	println("The book name is ${book.name}")
}

2.4.2 重载iterator方法

kotlin可以利用 operator 关键字重载运算符,这里我们重载 Bookcase 类的 iterator() 实现更加精简版本:

data class Book(val name: String)
class Bookcase(val books: List<Book>) {
	operator fun iterator(): Iterator<Book> = this.books.iterator()
}

2.4.3 通过扩展函数

如果 Book 是一个引入的类你并不能修改它的源码,那么可以用扩展函数来扩展功能:

data class Book(val name: String)
class Bookcase(val books: List<Book>) {}

operator fun Bookcase.iterator(): Iterator<Book> = books.iterator()
// 对迭代器逻辑更多控制权
operator fun Bookcase.iterator(): Iterator<Book> = object : Iterator<Book> {
	val iterator = books.iterator()
	override fun hasNext() = iterator.hasNext()
	override fun next() = iterator.next()
}

2.5 责任链模式

假设我们遇到这样的业务场景:希望使得多个对象都有机会处理某种类型的请求。那么可能就需要考虑是否可以采用责任链模式。

责任链模式的目的就是避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

data class ApplyEvent(val money: Int, val title: String)

interface ApplyHandler {
	val successor: ApplyHandler?
	fun handleEvent(event: ApplyEvent)
}

class GroupLeader(override val successor: ApplyHandler?): ApplyHandler {
	override fun handleEvent(event: ApplyEvent) {
		when {
			event.money <= 100 -> println("Group Leader handled application:${event.title}")
			else -> when(successor) {
				is ApplyHandler -> successor.handleEvent(event)
				else -> println("Group Leader:This application cannot be handdle")
			}
		}
	}
}

class President(override val successor: ApplyHandler?): ApplyHandler {
	override fun handleEvent(event: ApplyEvent) {
		when {
			event.money <= 500 -> println("President handled application:${event.title}")
			else -> when(successor) {
				is ApplyHandler -> successor.handleEvent(event)
				else -> println("President:This application cannot be handdle")
			}
		}
	}
}

class College(override val successor: ApplyHandler?): ApplyHandler {
	override fun handleEvent(event: ApplyEvent) {
		when {
			event.money > 1000 -> println("College:This application is refused")
			else -> println("College handled application:${event.title}")
		}
	}
}

val college = College(null)
val president = President(college)
val groupLeader = GroupLeader(president)

groupLeader.handleEvent(ApplyEvent(10, "buy a pen"))
groupLeader.handleEvent(ApplyEvent(200, "team building"))
groupLeader.handleEvent(ApplyEvent(600, "hold a debate match"))
groupLeader.handleEvent(ApplyEvent(1200, "annual meeting of the college"))

责任链的机理是整个链条的每个处理环节都有对其输入参数的校验标准,当输入参数处于某个责任链环节的有效接收范围之内,该环节才能对其做出正常的处理操作。在编程语言中使用“偏函数”来描述这种情况。

偏函数是数学中的概念,指的是定义域X中可能存在某些值在值域Y中没有对应的值。

在一个普通函数中,我们可以给指定类型的参数传入任意该类型的值,比如 (Int) -> Unit,可以接收任何 Int 值;而在偏函数中,指定类型的参数并不接收任意该类型的值:

// 普通函数
fun mustGreaterThan5(x: Int): Boolean {
	if (x > 5) {
		return true
	} else throw Exception("x must be greator than 5")
}

// 偏函数
// 声明类对象需接收两个构造参数:definetAt为校验函数,f为处理函数
// 执行invoke()时,definetAt会对输出参数p1进行有效性校验
// 如果校验通过,则执行f函数,同时将p1作为参数传递给它,反之抛出异常
class PartialFunction<in P1, out R>(private val definetAt: (P1) -> Boolean, private val f: (P1) -> R): (P1) -> R {
	override fun invoke(p1: P1): R {
		if (definetAt(p1)) {
			return f(p1)
		} else {
			throw IllegalArgumentException("value:$p1 isn't supported by this function")
		}
	}
	fun isDefinedAt(p1: P1) = definetAt(p1)
}

偏函数 PartialFunction 可以解决责任链模式中各个环节对输入的校验及处理逻辑问题;将请求在整个链条中进行传递可以添加一个扩展函数 orElse() 处理:

// infix关键字让orElse函数成为一个中缀函数
// orElse函数传入另一个PartialFunction类对象that,它是责任链的后继者
// isDefinedAt()为false时就调用that对象继续处理请求
infix fun <P1, R> PartialFunction<P1, R>.orElse(that: PartialFunction<P1, R>): PartialFunction<P1, R> {
	return PartialFunction({this.isDefinedAt(it) || that.isDefinedAt(it)) {
		when {
			this.isDefinedAt(it) -> this(it)
			else -> that(it)
		}
	}
}

我们使用上面的偏函数和 orElse() 改造责任链模式:

data class ApplyEvent(val money: Int, val title: String)

val groupLeader = {
	val definetAt: (ApplyEvent) -> Boolean = { it.money <= 200 }
	val handler: (ApplyEvent) -> Unit = {
		println("Group Leader handled application:${it.title}")
	}
	PartialFunction(definetAt, handler)
}()

val president = {
	val definetAt: (ApplyEvent) -> Boolean = { it.money <= 500 }
	val handler: (ApplyEvent) -> Unit = {
		println("President handled application:${it.title}")
	}
	PartialFunction(definetAt, handler)
}()

val college = {
	val definetAt: (ApplyEvent) -> Boolean = { true }
	val handler: (ApplyEvent) -> Unit = {
		when {
			it.money > 1000 -> println("College: This application is refused")
			else -> println("College handled application:${it.title}")
		}
	}
	PartialFunction(definetAt, handler)
}()

// 构建一个责任链
val applyChain = groupLeader orElse president orElse college
applyChain(ApplyEvent(600, "hold a debate match"))

2.6 状态模式

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

状态模式具体表现在:

  • 状态决定行为,对象的行为由它内部的状态决定

  • 对象的状态在运行期被改变时,它的行为也会因此而改变。从表面上看,同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样

状态模式与策略模式的不同在于:

  • 策略模式通过在客户端切换不同的策略实现来改变算法

  • 状态模式的对象通过修改内部的状态来切换不同的行为方法

class WaterMachine {
	// 随时切换为下面的三种状态
	var state: WaterMachineState
	
	val off = Off(this)
	val heating = Heating(this)
	val cooling = Cooling(this)

	init {
		this.state = off
	}
	fun turnHeating() {
		this.state.turnHeating()
	}
	fun turnCooling() {
		this.state.turnCooling()
	}
	fun turnOff() {
		this.state.turnOff()
	}
}

sealed class WaterMachineState(open val machine: WaterMachine) {
	fun turnHeating() {
		if (this !is Heating) {
			println("turn heating")
			machine.state = machine.heating
		} else {
			prinltn("The state is already heating mode")
		}
	}
	fun turnCooling() {
		if (this !is Cooling) {
			println("turn cooling")
			machine.state = machine.cooling
		} else {
			println("The state is already cooling mode")
		}
	}
	fun turnOff() {
		if (this !is Off) {
			println("turn off")
			machine.state = machine.off
		} else {
			println("The state is already off")
		}
	}
}

class Off(override val machine: WaterMachine): WaterMachineState(machine)
class Heating(override val machine: WaterMachine): WaterMachineState(machine)
class Cooling(override val machine: WaterMachine): WaterMachineState(machine)

enum class Moment {
	EARLY_MORNING, // 早上上班
	DRINKING_WATER, // 日常饮水
	INSTANCE_NOODLES, // 吃泡面
	AFTER_WORK // 下班
}

fun waterMachineOps(machine: WaterMachine, moment: Moment) {
	when(moment) {
		Moment.EARLY_MORNING, 
		Moment.DRINKING_WATER -> when(machine.state) {
			!is Cooling -> machine.turnCooling()	
		}
		Moment.INSTANCE_NOODLES -> when(machine.state) {
			!is Heating -> machine.turnHeating()
		} 
		Moment.AFTER_WORK -> when(machine.state) {
			!is Off -> machine.turnOff()
		}
		else -> Unit
	}
}

val machine = WaterMachine()
waterMachineOps(machine, Moment.DRINKING_WATER)
waterMachineOps(machine, Moment.INSTANCE_NOODLES)
waterMachineOps(machine, Moment.DRINKING_WATER)
waterMachineOps(machine, Moment.AFTER_WROK)

3 结构型模式

3.1 装饰者模式

在java中,当我们要给一个类扩展行为的时候,通常有两种选择:

  • 设计一个继承它的子类

  • 使用装饰者模式对该类进行装饰,然后对功能进行扩展

装饰者模式在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。该模式通过创建一个包装对象,来包裹真实的对象。

总的来说,装饰者模式做的以下几件事情:

  • 创建一个装饰类,包含一个需要被装饰类的实例

  • 装饰类重写所有被装饰类的方法

  • 在装饰类中对需要增强的功能进行扩展

装饰者模式很大优势在于符合“组合优于继承”的设计原则,规避了某些场景下继承所带来的问题,但是因为要重写所有的装饰者对象方法,所以可能存在大量的样板代码。

3.1.1 类委托减少装饰者模式样板代码

在kotlin中使用委托的特性利用 by 关键字,将装饰类所有方法委托给一个被装饰的类对象,只需要覆写需要装饰的方法即可:

interface MacBook {
	fun getCost(): Int
	fun getDesc(): Int
	fun getProdDate(): String
}
class MacBookPro: MacBook {
	override fun getCost() = 10000
	override fun getDesc() = "MacBook Pro"
	override fun getProdDate() = "Late 2011"
}
// 装饰类,委托给传参macbook
class ProcessorUpgradeMacBookPro(val macbook: MacBook): MacBook by macbook {
	// 覆写方法
	override fun getCost() = macboook.getCost() + 219
	override fun getDesc() = macbook.getDesc() + ", +1G Memory"
}

val macBookPro = MacBookPro()
val processorUpgradeMacBookPro = ProcessorUpgradeMacBookPro(macBookPro)
println(processorUpgradeMacBookPro.getCose())
println(processorUpgradeMacBookPro.getDesc())

3.1.2 通过扩展代替装饰者

除了使用 by 委托的方式,也可以通过扩展函数来实现:

class Printer {
	fun drawDottedLine() {
		println("-----------")
	}
	fun drawStars() {
		println("***********")
	}
}

fun Printer.startDraw(decorated: Printer.() -> Unit) {
	println("+++ start drawing +++")
	this.decorated()
	println("+++ end drawing +++")
}

Printer().run {
	startDraw {
		drawDottedLine()
	}
	startDraw {
		drawStars()
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值