网站制作论文优帮云/汕头百度网站推广
创建型模式
主流的创建型模式有:工厂方法模式、抽象工厂模式、构建者模式
伴生对象增强工厂模式
在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。
这里主要介绍简单工厂的模式,它的核心作用就是通过一个工厂类隐藏对象实例的创建逻辑,而不需要暴露给客户端。典型的使用场景就是当拥有一个父类与多个子类的时候,我们可以通过这种模式来创建子类对象。
假设现在有一个电脑加工厂,同时生产个人电脑和服务器主机。我们用熟悉的工厂模式设计描述其业务逻辑:
interface Computer {val cpu: String
}class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computerenum class ComputerType { PC, Server }class ComputerFactory {fun produce(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}
}
fun main() {val compter = ComputerFactory().produce(ComputerType.PC)println(compter.cpu)
}
以上代码通过调用ComputerFactory
类的produce
方法来创建不同的Computer
子类对象,这样我们就把创建实例的逻辑与客户端之间实现解耦,当对象创建的逻辑发生变化时(如构造参数的数量发生变化),该模式只需要修改produce方法内部的代码即可,相比直接创建对象的方式更加利于维护。
用单例代替工厂类
我们已经知道的是,Kotlin 支持用 object
来实现 Java 中的单例模式。所以,我们可以实现一个 ComputerFactory
单例,而不是一个工厂类。
object ComputerFactory { // 用 object 代替 classfun produce(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}
}
fun main() {val compter = ComputerFactory.produce(ComputerType.PC)println(compter.cpu)
}
我们可以通过operator
操作符重载invoke
方法来代替produce
,从而进一步简化表达:
object ComputerFactory { operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}
}
fun main() {val compter = ComputerFactory(ComputerType.PC)println(compter.cpu)
}
伴生对象创建静态工厂方法
我们是否可以直接通过 Computer()
而不是 ComputerFactory()
来创建一个实例呢?
考虑用静态工厂方法代替构造器。相信你已经想到了 Kotlin 中的伴生对象,它代替了 Java 中的static
,同时在功能和表达上拥有更强的能力。通过在 Computer
接口中定义一个伴生对象,我们就能够实现以上的需求,代码如下:
interface Computer {val cpu: Stringcompanion object {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}}
}class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computerenum class ComputerType { PC, Server }fun main() {val compter = Computer(ComputerType.PC)println(compter.cpu)
}
在不指定伴生对象名字的情况下,我们可以直接通过 Computer
来调用其伴生对象中的方法。当然,如果你喜欢伴生对象有名字,我们还是可以命名 Computer
的伴生对象,如下用 Factory
来命名:
interface Computer {val cpu: Stringcompanion object Factory {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}}
}fun main() {val compter = Computer.Factory(ComputerType.PC)println(compter.cpu)
}
注意:即便伴生对象是有名字的情况下,在调用时依然可以省略显示指定的名字。
扩展伴生对象方法
假设实际业务中我们是Computer
接口的使用者,比如它是工程引入的第三方类库,所有的类的实现细节都得到了很好地隐藏。那么,如果我们希望进一步改造其中的逻辑,Kotlin 中伴生对象的方式同样可以依靠其扩展函数的特性,很好地实现这一需求。
比如我们希望给Computer
增加一种功能,通过CPU型号来判断电脑类型,那么就可以如下实现:
fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when(cpu) {"Core" -> ComputerType.PC"Xeon" -> ComputerType.Serverelse -> null
}
如果指定了伴生对象的名字为Factory
,那么就可以如下实现:
fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when(cpu) {"Core" -> ComputerType.PC"Xeon" -> ComputerType.Serverelse -> null
}
调用:
fun main() {val type = Computer.fromCPU("Core")println(type)
}
内联函数简化抽象工厂
Kotlin中 的内联函数有一个很大的作用,就是可以具体化参数类型。利用这一特性,我们还可以改进一种更复杂的工厂模式,称为抽象工厂。
工厂模式已经能够很好地处理一个产品等级结构的问题,在上一节中,我们已经用它很好地解决了电脑厂商生产服务器、PC机的问题。进一步思考,当问题上升到多个产品等级结构的时候,比如现在引入了品牌商的概念,我们有好几个不同的电脑品牌,比如 Dell、Asus、Acer,那么就有必要再增加一个工厂类。然而,我们并不希望对每个模型都建立一个工厂,这会让代码变得难以维护,所以这时候我们就需要引入抽象工厂模式。
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
在抽象工厂的定义中,我们也可以把“ 一组相关或相互依赖的对象” 称作 “产品族”,在上述的例子中,我们就提到了3个代表不同电脑品牌的产品族。
下面我们就利用抽象工厂,来实现具体的需求:
class Dell: Computer { }
class Asus: Computer { }
class Acer: Computer { }class DellFactory: AbstractFactory() {override fun produce() = Dell()
}
class AsusFactory: AbstractFactory() {override fun produce() = Asus()
}
class AcerFactory: AbstractFactory() {override fun produce() = Acer()
}abstract class AbstractFactory {abstract fun produce(): Computercompanion object {operator fun invoke(factory: AbstractFactory): AbstractFactory {return factory}}
} fun main() { val dellFactory = AbstractFactory(DellFactory())val dell = dellFactory.produce()println(dell)
}
以上代码当你每次创建具体的工厂类时,都需要传入一个具体的工厂对象作为参数进行构造,这个在语法上显然不是很优雅。
下面我们可以用 Kotlin 中的内联函数来改善这一情况。我们所需要做的,就是去重新实现 AbstractFactory
类中的 invoke
方法。
abstract class AbstractFactory {abstract fun produce(): Computercompanion object {inline operator fun <reified T : Computer> invoke(): AbstractFactory = when(T::class) {Dell::class -> DellFactory()Asus::class -> AsusFactory()Acer::class -> AcerFactory()else -> throw IllegalArgumentException()}}
}fun main() { val dellFactory = AbstractFactory<Dell>()val dell = dellFactory.produce()println(dell)
}
- 1)通过将
invoke
方法用inline
定义为内联函数,我们就可以引入reified
关键字,使用具体化参数类型的语法特性; - 2)要具体化的参数类型为
Computer
,在invoke
方法中我们通过判断它的具体类型,来返回对应的工厂类对象。
用具名可选参数而不是构建者模式
在 Java 开发中,你是否写过这样像蛇一样长的构造函数:
// Boolean 类型的参数表示 Robot 是否含有对应固件
Robot robot = new Robot(1, true, true, false, false, false, false, false, false)
刚写完时回头看你还能看懂,一天后你可能已经忘记大半了,再过一个星期你已经不知道这是什么东西了。面对这样的业务场景时,我们惯常的做法是通过 Builder(构建者)模式来解决。
构建者模式
构建者模式主要做的事情就是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂模式和构造函数都存在相同的问题,就是不能很好地扩展到大量的可选参数。假设我们现在有个机器人类,它含有多个属性:代号、名字、电池、重量、高度、速度、音量等。很多产品都不具有其中的某些属性,比如不能走、不能发声,甚至有的机器人也不需要电池。
一种糟糕的做法就是设计一个在上面你所看到Robot
类,把所有的属性都作为构造函数的参数。或者,你也可能采用过重叠构造器(telescoping constructor)模式,即先提供一个只有必要参数的构造函数,然后再提供其他更多的构造函数,分别具有不同情况的可选属性。虽然这种模式在调用的时候改进不少,但同样存在明显的缺点。因为随着构造函数的参数数量增加,很快我们就会失去控制,代码变得难以维护。
构建者模式可以避免以上的问题,我们用 Kotlin 来实现 Java 中的构建者模式:
class Robot private constructor(val code: String,val battery: String?,val height: Int?,val weight: Int?) {class Builder(val code: String) {private var battery: String? = nullprivate var height: Int? = nullprivate var weight: Int? = nullfun setBattery(battery: String?): Builder {this.battery = batteryreturn this}fun setHeight(height: Int): Builder {this.height = heightreturn this}fun setWeight(weight: Int): Builder {this.weight = weightreturn this}fun build(): Robot {return Robot(code, battery, height, weight)}}
}fun main() {val robot = Robot.Builder("007").setBattery("R6").setHeight(100).setWeight(80).build()
}
这种链式调用的设计看起来确实优雅了不少,同时对于可选参数的设置也显得比较语义化,它有点近似柯里化语法。此外,构建者模式另外一个好处就是解决了多个可选参数的问题,当我们创建对象实例时,只需要用set
方法对需要的参数进行赋值即可。
然而,构建者模式也存在一些不足:
- 1)如果业务需求的参数很多,代码依然会显得比较冗长;
- 2)你可能会在使用
Builder
的时候忘记在最后调用build
方法; - 3)由于在创建对象的时候,必须先创建它的构造器,因此额外增加了多余的开销,在某些十分注重性能的情况下,可能就存在一定的问题。
事实上,当用 Kotlin 设计程序时,我们可以在绝大多数情况下避免使用构建者模式。《Effective Java》在介绍构建者模式时,是这样描述它的:本质上 Builder 模式模拟了具名的可选参数,就像 Ada 和 Python 中的一样。幸运的是,Kotlin 也是这样一门拥有具名可选参数的编程语言。
具名的可选参数
Kotlin 中的函数和构造器都支持这一特性,现在再来回顾下。它主要表现为两点:
- 1)在具体化一个参数的取值时,可以通过带上它的参数名,而不是它在所有参数中的位置决定;
- 2)由于参数可以设置默认值,这允许我们只给出部分参数的取值,而不必是所有的参数。
因此,我们可以直接使用 Kotlin 中原生的语法特性来实现构建者模式的效果。现在重新设计以上的 Robot
例子:
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)
可以发现,相比构建者模式,通过具名的可选参数构造类具有很多优点:
- 1)代码变得十分简单,这不仅表现在
Robot
类的结构体代码量,我们在声明Robot
对象时的语法也要更加简洁; - 2)声明对象时,每个参数名都可以是显式的,并且无须按照顺序书写,非常方便灵活;
- 3)由于
Robot
类的每个对象都是val
声明的,相较构建者模式者中用var
的方案更加安全,这在要求多线程并发安全的业务场景中会显得更有优势。
此外,如果你的类的功能足够简单,更好的思路是用data class
直接声明一个数据类。如你所知,数据类同样支持以上的所有特性。
require 方法对参数进行约束
构建者模式的另外一个作用,就是可以在build
方法中对参数添加约束条件。
举个例子,假设一个机器人的重量必须根据电池的型号决定,那么在未传入电池型号之前,你便不能对weight
属性进行赋值,否则就会抛出异常。
fun build(): Robot {if (weight != null && battery == null) {throw IllegalArgumentException("Battery should be determined when setting weight")} else {return Robot(code, battery, height, weight)}
}
运行下具体的测试用例:
val robot = Robot.Builder("007").setWeight(100).build()
然后就会发现以下的异常信息:
Exception in thread "main" java.lang.IllegalArgumentException:Battery should be determined when setting weight
这种在build
方法中对参数进行约束的手段,可以让业务变得更加安全。那么,通过具名的可选参数来构造类的方案该如何实现呢?
显然,我们同样可以在Robot
类的init
方法中增加以上的校验代码。然而在 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."}}
}
如果我们在创建Robot
对象时有不符合require
条件的行为,就会导致抛出异常。
val robot = Robot(code="007", weight = 100)
>>> java.lang.IllegalArgumentException: Battery should be determined when setting weight
可见,Kotlin 的require
方法可以让我们的参数约束代码在语义上变得更加友好。
总的来说,在 Kotlin 中我们应该尽量避免使用构建者模式,因为 Kotlin 支持具名的可选参数,这让我们可以在构造一个具有多个可选参数类的场景中,设计出更加简洁并利于维护的代码。
行为型模式
主流的行为型模式有:观察者模式、策略模式、模板方法模式、迭代器模式、责任链模式及状态模式。
Kotlin 中的观察者模式
观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。
简单来说,观察者模式无非做两件事情:
- 订阅者(观察者observer)添加或删除对发布者(被观察者)的状态监听;
- 发布者状态改变时,将事件通知给监听它的所有观察者,然后观察者执行响应逻辑。
Observable
Java 自身的标准库提供了 java.util.Observable
类 和 java.util.Observer
接口,来帮助实现观察者模式。
下面用它们来实现一个动态更新股价的例子:
import java.util.*class StockUpdate: Observable() {val observers = mutableSetOf<Observer>();fun setStockChanged(price: Int) {this.observers.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}.")}}
}
fun main() {val su = StockUpdate()val sd = StockDisplay()su.observers.add(sd)su.setStockChanged(100)
}
在上述例子中,创建了一个可被观察的发布者类StockUpdate
,它维护了一个监听其变化的观察者对象observers
,通过它的add
和remove
方法来进行管理。当StockUpdate
类对象执行setStockChanged
方法之后,那么就会将更新的股价传递给观察者,执行其update
方法来执行响应逻辑。
Delegates.Observable
事实上,Kotlin 的标准库额外引入了可被观察的委托属性,也可以利用它来实现同样的场景。
import kotlin.properties.Delegatesinterface 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 {var listeners = mutableSetOf<StockUpdateListener>()var price: Int by Delegates.observable(0) { _, old, new ->listeners.forEach {if (new > old) it.onRise(price) else it.onFall(price)}}
}fun main() {val su = StockUpdate()val sd = StockDisplay()su.listeners.add(sd)su.price = 100su.price = 98
}
在该版本中,我们实现了更加具体的需求:当股价上涨或下跌时,打印不同的个性化报价文案。
如果你仔细思考,会发现实现java.util.Observer
接口的类只能覆写update
方法来编写响应逻辑,也就是说如果存在多种不同的逻辑响应,我们也必须通过在该方法中进行区分实现,显然这会让订阅者的代码显得臃肿。
换个角度,如果我们把发布者的事件推送看成一个第三方服务,那么它提供的 API 接口只有一个,API 调用者必须承担更多的职能。
显然,使用 Delegates.observable()
的方案更加灵活。它提供了 3 个参数,依次代表委托属性的元数据KProperty
对象、旧值以及新值。
通过额外定义一个StockUpdateListener
接口,我们可以把上涨和下跌的不同响应逻辑封装成接口方法,从而在StockDisplay
中实现该接口的onRise
和onFall
方法,实现了解耦。
Delegates.Vetoable
有些时候,我们并不希望监控的值可以被随心所欲地修改。Kotlin 的标准库中除了observable
这个委托属性之外,还提供了一个 vetoable
属性,顾名思义,veto
代表的是“ 否决” 的意思,vetoable
提供了一种功能,在被赋新值生效之前提前进行截获,然后判断是否接受它。
例如:
import kotlin.properties.Delegatesvar value: Int by Delegates.vetoable(0) { prop, old, new ->new > 0
}value = 1
println(value)
>>> 1value = -1
println(value)
>>> 1
这里创建了一个可变的Int
对象value
,同时用by
关键字增加了Delegates.vetoable
委托属性。它的初始化值为0
,只接收被正整数赋值。所以,当我们试图把value
改成-1
的时候,打印的结果仍然为旧值1
。
高阶函数简化策略模式、模板方法模式
遵循开闭原则:策略模式
假设现在有一个表示游泳运动员的抽象类Swimmer
,有一个游泳的方法swim
,表示如下:
class Swimmer {fun swim() {println("I am swimming...")}
}fun main() {val shaw = Swimmer()shaw.swim()
}
由于shaw
在游泳方面很有天赋,他很快掌握了蛙泳、仰泳、自由泳多种姿势。所以我们必须对swim
方法进行改造,变成代表3种不同游泳姿势的方法。
class Swimmer {fun breaststroke() {println("I am breaststroking...")}fun backstroke() {println("I am backstroke...")}fun freestyle() {println("I am freestyling...")}
}
然而这并不是一个很好的设计。首先,并不是所有的游泳运动员都掌握了这3种游泳姿势,如果每个Swimmer
类对象都可以调用所有方法,显得比较危险。其次,后续难免会有新的行为方法加入,通过修改Swimmer
类的方式违背了开放封闭原则。
因此,更好的做法是将游泳这个行为封装成接口,根据不同的场景我们可以调用不同的游泳方法。策略模式就是一种解决这种场景很好的思路。
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
本质上,策略模式做的事情就是将不同的行为策略(Strategy
)进行独立封装,与类在逻辑上解耦。然后根据不同的上下文(Context
)切换选择不同的策略,然后用类对象进行调用。下面我们用熟悉的方式重新实现游泳的例子:
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(private val strategy: SwimStrategy) {fun swim() {strategy.swim()}
}
fun main() {// tom会自由泳val tom = Swimmer(Freestyle())tom.swim()// jack会蛙泳val jack = Swimmer(Breaststroke())jack.swim()
}
这个方案实现了解耦和复用的目的,且很好实现了在不同场景切换采用不同的策略。然而,该版本的代码量也比之前多了很多。
高阶函数抽象算法
我们用高阶函数的思路来重新思考下策略类,显然将策略封装成一个函数然后作为参数传递给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()}
}fun main() {// tom会自由泳val tom = Swimmer(::freestyle)tom.swim()// jack会蛙泳val jack = Swimmer(::breaststroke)jack.swim()
}
可以看到,代码量骤减,且结构也清晰易读。由于策略算法都封装成了一个个函数,我们在初始化Swimmer
类对象时,可以用函数引用的语法传递构造参数。当然,我们也可以把函数用val
声明成Lambda
表达式,那么在传递参数的时候会变得更加简洁直观。
模板方法模式:高阶函数代替继承
另一个可用高阶函数函数改良的设计模式,就是模板方法模式。
某种程度上,模板方法模式和策略模式要解决的问题是相似的,它们都可以分离通用的算法和具体的上下文。然而,如果说策略模式采用的思路是将算法进行委托,那么传统的模板方法模式更多是基于继承的方式实现的。
来看看模板方法模式的定义:
- 定义一个算法中的操作框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
与策略模式不同,模板方法模式的行为算法具有更清晰的大纲结构,其中完全相同的步骤会在抽象类中实现,可个性化的某些步骤在其子类中进行定义。
举个例子,如果我们去市民事务中心办事时,一般都会有以下几个具体的步骤:
- 1)排队取号等待;
- 2)根据自己的需求办理个性化的业务,如获取社保清单、申请市民卡、办理房产证;
- 3)对服务人员的态度进行评价。
这是一个典型的适用模板方法模式的场景,办事步骤整体是一个算法大纲,其中步骤1)和步骤3)都是相同的算法,而步骤2)则可以根据实际需求个性化选择。接下来我们就用代码实现一个抽象类,它定义了这个例子的操作框架:
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");}abstract fun askForHelp()
}
其中askForHelp
方法是一个抽象方法。接下来我们再定义具体的子类来继承CivicCenterTask
类,然后对抽象的步骤进行实现。
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")}
}
调用:
fun main() {val task = PullSocialSecurity()task.execute()val task2 = ApplyForCitizenCard()task2.execute()
}
在 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")
}fun main() {val task1 = CivicCenterTask()task1.execute(::pullSocialSecurity)val task2 = CivicCenterTask()task2.execute(::applyForCitizenCard)
}
可见,在高阶函数的帮助下,我们可以更加轻松地实现模板方式模式。
运算符重载和迭代器模式
有些时候,我们会定义某些容器类,这些类中包含了大量相同类型的对象。如果你想给这个容器类的对象直接提供迭代的方法,如hasNext
、next
、first
等,那么就可以自定义一个迭代器。然而通常情况下,我们不需要自己再实现一个迭代器,因为Java标准库提供了java.util.Iterator
接口,你可以用容器类实现该接口,然后再实现需要的迭代器方法。
这种设计模式就是迭代器模式,它的核心作用就是将遍历和实现分离开来,在遍历的同时不需要暴露对象的内部表示。
实现Iterator
接口的简单例子:
data class Book(val name:String)class Bookcase(books: List<Book>): Iterator<Book> {private val iterator: Iterator<Book> = books.iterator()override fun hasNext() = this.iterator.hasNext()override fun next() = this.iterator.next()
}fun main() {val bookcase = Bookcase(listOf(Book("DiveintoKotlin"),Book("ThinkinginJava")))while (bookcase.hasNext()) {println("Thebooknameis${bookcase.next().name}")}
}
由于Bookcase
对象拥有与List<Book>
实例相同的迭代器,我们就可以直接调用后者迭代器所有的方法。
当然,我们一般会使用更简洁的遍历打印方式如下:
for (book in bookcase) {println("The book name is ${book.name}")
}
重载 iterator 方法
Kotlin 还有更好的解决方案。Kotlin 有一个非常强大的语言特性,那就是利用operator
关键字内置了很多运算符重载功能。
我们就可以通过重载Bookcase
类的iterator
方法,实现一种语法上更加精简的版本:
data class Book(val name:String)class Bookcase(val books: List<Book>) {operator fun iterator(): Iterator<Book> = this.books.iterator()
}
我们用一行代码就实现了以上所有的效果。还没完,由于 Kotlin 还支持扩展函数,这意味着我们可以给所有的对象都内置一个迭代器。
通过扩展函数重载 iterator 方法
假设现在的Bookcase
是引入的一个类,你并不能修改它的源码,下面我们就演示如何用扩展的语法来给Bookcase
类对象增加迭代的功能:
data class Book(val name: String)
class Bookcase(val books: List<Book>) {}
operator fun Bookcase.iterator(): Iterator<Book> = books.iterator()
代码依旧非常简洁,假如你想对迭代器的逻辑有更多的控制权,那么也可以通过object
表达式来实现:
operator fun Bookcase.iterator(): Iterator<Book> = object : Iterator<Book> {val iterator = books.iterator()override fun hasNext() = iterator.hasNext()override fun next() = iterator.next()
}
总的来说,迭代器模式并不是一种很常用的设计模式,但通过它我们可以进一步了解 Kotlin 中的扩展函数的应用,以及运算符重载功能的强大之处。
用偏函数实现责任链模式
简单来说,责任链模式的目的就是避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
现在我们来举一个更加具体的例子。计算机学院的学生会管理了一个学生会基金,用于各种活动和组织人员工作的开支。当要发生一笔支出时,如果金额在100元之内,可由各个分部长审批;如果金额超过了100元,那么就需要会长同意;但假使金额较大,达到了500元以上,那么就需要学院的辅导员陈老师批准。此外,学院里还有一个不宣的规定,经费的上限为1000元,如果超出则默认打回申请。
当然我们可以用最简单的if-else
来实现经费审批的需求。然而根据开闭原则,我们需要将其中的逻辑进行解耦。下面我们就用面向对象的思路结合责任链模式,来设计一个程序。
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}.")}}
}fun main() {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"))
}
运行结果:
Group Leader handled application: buy a pen.
President handled application: team building.
College handled application: hold a debate match.
College: This application is refused.
在这个例子中,我们声明了GroupLeader
、President
、College
三个类来代表学生会部长、分部长、会长及学院,它们都实现了ApplyHandler
接口。接口包含了一个可空的后继者对象successor
,以及对申请事件的处理方法handleEvent
。当我们把一个申请经费的事件传递给GroupLeader
对象进行处理时,它会根据具体的经费金额来判断是否将申请转交给successor
对象,也就是President
类来处理。以此类推,最终形成了一个责任链的机制。
现在我们再来重新思考下责任链的机理,你会发现整个链条的每个处理环节都有对其输入参数的校验标准,在上述例子中主要是对申请经费事件的金额有要求。当输入参数处于某个责任链环节的有效接收范围之内,该环节才能对其做出正常的处理操作。在编程语言中,我们有一个专门的术语来描述这种情况,这就是“偏函数” 。
实现偏函数类型:PartialFunction
什么是偏函数?
偏函数是个数学中的概念,指的是定义域X
中可能存在某些值在值域Y
中没有对应的值。
为了方便理解,我们可以把偏函数与普通函数进行比较。在一个普通函数中,我们可以给指定类型的参数传入任意该类型的值,比如(Int) -> Unit
,可以接收任何Int
值。而在一个偏函数中,输入类型的参数值不一定被接收,比如:
fun mustGreaterThan5(x: Int): Boolean {if (x > 5) return trueelse throw Exception("x must be greator than 5")
}mustGreaterThan5(6)
>>> truemustGreaterThan5(1)
>>> java.lang.Exception: x must be greator than 5 at Line57.mustGreaterThan5(Unknown Source) // 必须传入大于5的值
之所以提到偏函数是因为在一些函数式编程语言中,如 Scala,有一种PartialFunction
类型,我们可以用它来简化责任链模式的实现。由于 Kotlin 的语言特性足够灵活强大,虽然它的标准库并没有支持 PartialFunction
,然而一些开源库已经实现了这个功能。我们来看看如何定义PartialFunction
类型:
class PartialFunction<in P1, out R>(private val defineAt : (P1) -> Boolean,private val f : (P1) -> R
) : (P1) -> R {override fun invoke(p1 : P1) : R {if (defineAt(p1)) {return f(p1)} else {throw IllegalArgumentException("Value: ($p1) isn't supported by this function")}}fun isDefinedAt(p1: P1) = defineAt(p1)
}
PartialFunction
类的具体作用:
- 声明类对象时需接收两个构造参数,其中
definetAt
为校验函数,f
为处理函数; - 当
PartialFunction
类对象执行invoke
方法时,definetAt
会对输入参数p1
进行有效性校验; - 如果校验结果通过,则执行
f
函数,同时将p1
作为参数传递给它;反之则抛出异常。
PartialFunction
类可以解决责任链模式中各个环节对于输入的校验及处理逻辑的问题,但是依旧有一个问题需要解决,就是如何将请求在整个链条中进行传递。
接下来我们再利用 Kotlin 的扩展函数给 PartialFunction
类增加一个 orElse
方法。在此之前,我们先注意下这个类中的isDefinedAt
方法,它其实并没有什么特殊之处,仅仅只是作为拷贝definetAt
的一个内部方法,为了在orElse
方法中能够被调用。
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
方法中可以传入另一个PartialFunction
类对象that
,它也就是责任链模式中的后继者。当isDefinedAt
方法执行结果为false
的时候,那么就调用that
对象来继续处理申请。
这里用infix
关键字来让orElse
成为一个中缀函数,从而让链式调用的语法变得更加直观。
用 orElse 构建责任链
接下来我们就用设计好的PartialFunction
类及扩展的orElse
方法,来重新实现一下最开始的例子。
val groupLeader = run {val definetAt: (ApplyEvent) -> Boolean = { it.money <= 200 }val handler: (ApplyEvent) -> Unit = { println(" groupLeader ... ") }PartialFunction(definetAt, handler)
}val president = run {val definetAt: (ApplyEvent) -> Boolean = { it.money <= 500 }val handler: (ApplyEvent) -> Unit = { println(" president ... ") }PartialFunction(definetAt, handler)
}val college = run {val definetAt: (ApplyEvent) -> Boolean = { true }val handler: (ApplyEvent) -> Unit = { println(" college ... ") }PartialFunction(definetAt, handler)
}
然后调用如下:
fun main() {val applyChain = groupLeader orElse president orElse collegeapplyChain(ApplyEvent(10, "buy a pen"))applyChain(ApplyEvent(200, "team building"))applyChain(ApplyEvent(600, "hold a debate match"))applyChain(ApplyEvent(1200, "annual meeting of the college"))
}
这里用orElse
获得了更好的语法表达。
ADT 实现状态模式
状态模式与策略模式存在某些相似性,它们都可以实现某种算法、业务逻辑的切换。以下是状态模式的定义:
- 状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
状态模式具体表现在:
- 状态决定行为,对象的行为由它内部的状态决定。
- 对象的状态在运行期被改变时,它的行为也会因此而改变。 从表面上看,同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样。
再次与策略模式做比较,你也会发现两种模式之间的不同:
- 策略模式通过在客户端切换不同的策略实现来改变算法;
- 而在状态模式中,对象通过修改内部的状态来切换不同的行为方法。
来看个饮水机的例子,假设一个饮水机有 3 种工作状态,分别为未启动、制冷模式、制热模式。可以用密封类来封装一个代表不同饮水机状态的 ADT。
class WaterMachine {var state : WaterMachineState = WaterMachineState.Off(this)fun turnHeating() {this.state.turnHeating()}fun turnCooling() {this.state.turnCooling()}fun turnOff() {this.state.turnOff()}
}sealed class WaterMachineState(open val machine: WaterMachine) {class Off(override val machine: WaterMachine): WaterMachineState(machine)class Heating(override val machine: WaterMachine): WaterMachineState(machine)class Cooling(override val machine: WaterMachine): WaterMachineState(machine)fun turnHeating() {if (this !is Heating) { machine.state = Heating(machine)println("turn heating")} else {println("The state is already heating mode.")}}fun turnCooling() {if (this !is Cooling) { machine.state = Cooling(machine)println("turn cooling")} else {println("The state is already cooling mode.")}}fun turnOff() {if (this !is Off) { machine.state = Off(machine)println("turn off")} else {println("The state is already off.")}}
}
利用上面的ADT数据结构,来实现这样一个需求:Shaw早上上班的时候会把饮水机调整为制冷模式,他想泡面的时候,就会把饮水机变为制热,所以每次他吃了泡面,下一个喝水的同事就需要再切换回制冷。最后要下班了,Kim就会关闭饮水机的电源。
enum class Moment{EARLY_MORNING, // 早上上班DRINKING_WATER, // 日常饮水INSTANCE_NOODLES,// Shaw吃泡面AFTER_WORK // 下班
}fun waterMachineOps(machine: WaterMachine, moment: Moment){when(moment){Moment.EARLY_MORNING,Moment.DRINKING_WATER -> machine.turnCooling()Moment.INSTANCE_NOODLES -> machine.turnHeating()Moment.AFTER_WORK -> machine.turnOff()}
}fun main() {val machine = WaterMachine()waterMachineOps(machine, Moment.DRINKING_WATER)waterMachineOps(machine, Moment.INSTANCE_NOODLES)waterMachineOps(machine, Moment.DRINKING_WATER)waterMachineOps(machine, Moment.AFTER_WORK)
}
执行结果:
turn cooling
turn heating
turn cooling
turn off
结构型模式
装饰者模式:用接口委托减少样板代码
在 Java 中,当我们要给一个类扩展行为的时候,通常有两种选择:
- 设计一个继承它的子类;
- 使用装饰者模式对该类进行装饰,然后对功能进行扩展。
不是所有场合都适合采用继承的方式来满足类扩展的需求(需遵循“里氏替换原则”),所以很多时候装饰者模式成了我们解决此类问题更好的思路。
装饰者模式:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。该模式通过创建一个包装对象,来包裹真实的对象。
总结来说,装饰者模式做的是以下几件事情:
- 创建一个装饰类,包含一个需要被装饰类的实例;
- 装饰类重写所有被装饰类的方法;
- 在装饰类中对需要增强的功能进行扩展。
可以发现,装饰者模式很大的优势在于符合 “组合优于继承” 的设计原则,规避了某些场景下继承所带来的问题。然而,它有时候也会显得比较啰唆,因为要重写所有的装饰对象方法,所以可能存在大量的样板代码。
在 Kotlin 中,我们可以利用 by
关键字委托特性,将装饰类的所有方法委托给一个被装饰的类对象,然后只需覆写需要装饰的方法即可,让装饰者模式的实现变得更加优雅。
interface MacBook {fun getCost(): Intfun getDesc(): Stringfun getProdDate(): String
}
class MacBookPro: MacBook {override fun getCost() = 10000override fun getDesc() = "Macbook Pro"override fun getProdDate() = "Late 2011"
}
// 装饰类
class MacBookUpgrade(val macBook: MacBook) : MacBook by macBook {override fun getCost() = macBook.getCost() + 219override fun getDesc() = macBook.getDesc() + ", + 1G Memory"
}fun main() {val macBookPro = MacBookPro()val macBookUpgrade = MacBookUpgrade(macBookPro)println(macBookUpgrade.getCost())println(macBookUpgrade.getDesc())
}
如代码所示,我们创建一个代表 MacBook Pro
的类,它实现了MacBook
的接口的3个方法,分别表示它的预算、机型信息,以及生产的年份。
当你觉得原装MacBook
的内存配置不够的时候,需要对其进行一下配置升级,比如再加入 1G 的内存,这时候配置信息和预算方法都会受到影响。
所以通过 Kotlin 的类委托语法, 我们实现了一个MacBookUpgrade
类,该类会把MacBook
接口所有的方法都委托给构造参数对象macbook
。
因此,我们只需通过覆写的语法来重写需要变更的cost
和getDesc
方法。由于生产年份是不会改变的,所以不需重写,MacBookUpgrade
类会自动调用装饰对象的getProdDate
方法。
总的来说,Kotlin 通过类委托的方式减少了装饰者模式中的样板代码,否则在不继承Macbook
类的前提下,我们得创建一个装饰类和被装饰类的公共父抽象类。
通过扩展函数代替装饰者
class Printer {fun drawLine() {println("————————")}fun drawDottedLine() {println("- - - - -")}fun drawStars() {println("********")}
}
这里我们定义了一个Printer
绘图类,它有3个画图方法,分别可以绘制实线、虚线及星号线。
现在,我们有一个新的需求,就是希望在每次绘图开始和结束后有一段文字说明,来标记整个绘制的过程。
一种思路是对每个绘图的方法装饰新增的功能,然而这肯定显得冗余,尤其是未来Printer
类可能新增其他的绘图方法,这不是一种优雅的设计思路。
我们来看看如何用扩展来代替装饰类,提供更好的解决方案:
fun Printer.startDraw(decorated: Printer.() -> Unit) {println("+++ start drawing +++")this.decorated()println("+++ end drawing +++")
}fun main() {Printer().run {startDraw { drawLine() }startDraw { drawDottedLine() }startDraw { drawStars() }}
}
还记得之前介绍的run
方法吗?它接收一个lambda
函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return
的表达式。结合run
的语法,我们就可以比较优雅地实现我们的需求。
总结
设计模式 | Kotlin 中的解决方式 | 备注 |
---|---|---|
工厂方法模式 | 单例 object 类 + invoke 重载伴生对象 companion object + invoke 重载伴生对象扩展方法 | 创建型模式 |
抽象工厂模式 | 内联函数 inline + reified | 创建型模式 |
构建者模式 | 具名可选参数 + require 方法约束 | 创建型模式 |
观察者模式 | Delegates.Observable 委托语法Delegates.Vetoable 委托语法 | 行为型模式 |
策略模式 | 高阶函数(:: 函数引用语法) | 行为型模式 |
模板方法模式 | 高阶函数(:: 函数引用语法) | 行为型模式 |
迭代器模式 | 运算符重载 iterator 扩展函数重载 iterator | 行为型模式 |
责任链模式 | 仿造偏函数 | 行为型模式 |
状态模式 | 利用 ADT(代数数据类型) | 行为型模式 |
装饰者模式 | 接口委托 by 语法扩展函数 | 结构型模式 |
相关文章:

《Kotlin核心编程》笔记:设计模式
创建型模式 主流的创建型模式有:工厂方法模式、抽象工厂模式、构建者模式 伴生对象增强工厂模式 在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。 这里主要介绍简单工厂的模式,它的核心作用就是通过一个工厂类隐藏对象实例的创建…...

hive企业级调优策略之数据倾斜
测试所用到的数据参考: 原文链接:https://blog.csdn.net/m0_52606060/article/details/135080511 本教程的计算环境为Hive on MR。计算资源的调整主要包括Yarn和MR。 数据倾斜概述 数据倾斜问题,通常是指参与计算的数据分布不均࿰…...

MATLAB版本、labview版本、UHD版本 互相对应
LabVIEWMATLABUHD2019R2021bUHD_3.15.0.0-vendor2020R2022bUHD_4.1.0.4-vendorR2023bUHD_4.2.0.0-vendor 更换固件 MATLAB 更换固件指令 status sdruload(Device "X310", IPAddress 192.168.10.2)...

13 v-show指令
概述 v-show用于实现组件的显示和隐藏,和v-if单独使用的时候有点类似。不同的是,v-if会直接移除dom元素,而v-show只是让dom元素隐藏,而不会移除。 在实际开发中,v-show也经常被用到,需要重点掌握。 基本…...

23级新生C语言周赛(6)(郑州轻工业大学)
题目链接:ZZULIOJ 3110: 数(shu)数(shu)问题 分析: 看到这个题第一步想的是 先把每个平方数给求出来 然后枚举 但是时间复杂度大于1e8 交了一下TLE 但后来打表发现,好数太多了要是枚举的话 注定TLE 能不能间接的去做呢? 把不是的减去,那不就是好数了吗? 这个时候又是打表,会…...

关于“Python”的核心知识点整理大全24
目录 编辑 10.1.6 包含一百万位的大型文件 pi_string.py 10.1.7 圆周率值中包含你的生日吗 10.2 写入文件 10.2.1 写入空文件 write_message.py programming.txt 10.2.2 写入多行 10.2.3 附加到文件 write_message.py programming.txt 10.3 异常 10.3.1 处理 Ze…...

Vue - 基于Element UI封装一个表格动态列组件
1 组件需求背景 在后台管理系统中,表格的使用频率非常高,统一封装表格动态列组件并全局注册使用,可大大提升代码的复用性和可维护性。 2 全局注册 src/plugins/index.js: import columns from ./columns/indexexport default …...

计算机网络:DNS域名解析系统
我最近开了几个专栏,诚信互三! > |||《算法专栏》::刷题教程来自网站《代码随想录》。||| > |||《C专栏》::记录我学习C的经历,看完你一定会有收获。||| > |||《Linux专栏》࿱…...

java面试:==和equals有什么区别?
在 Java 中,"" 和 "equals" 有着不同的作用: "" 运算符: 在基本数据类型(如 int、char 等)中,"" 用于比较它们的值是否相等。 在引用类型中,"&q…...

数字人SaaS系统无限生成AI数字人!
市面上数字人软件层出不穷,选择一款适合的数字人软件是成功的第一步,只需要一款软件就解决数字人直播和数字人短视频的制作,青否数字人SaaS系统(数字人源码:zhibo175)你值得拥有! 青否数字人Saa…...

【MySQL】——数据类型及字符集
🎃个人专栏: 🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客 🐳Java基础:Java基础_IT闫的博客-CSDN博客 🐋c语言:c语言_IT闫的博客-CSDN博客 🐟MySQL:…...

Redis cluster集群设置密码
Redis cluster集群设置密码 1 备份数据 # 链接redis集群,执行rdb快照 bgsave # 备份dump.rdb文件 cp /data/redis/cluster/dump.rdb /data/redis/cluster/backup/dump.rdb.202312202 设置密码 必须保证每个节点的密码保持一致,不然 Redirected 的时候会失败 2.1…...

Docker 核心技术
Docker 定义:于 Linux 内核的 Cgroup,Namespace,以及 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器Docke…...

15 使用v-model绑定单选框
概述 使用v-model绑定单选框也比较常见,比如性别,要么是男,要么是女。比如单选题,给出多个选择,但是只能选择其中的一个。 在本节课中,我们演示一下这两种常见的用法。 基本用法 我们创建src/component…...

go语言指针变量定义及说明
go语言指针主要需要记住两个特殊符号, 一个是 & 用来获取变量对应的内存地址 另一个是 * 用来获取指针对应的变量值 下面是个最简单的go语言指针说明 package mainimport "fmt"//指针为内存地址func main() {var a string "指针对应的变量&…...

基于“Galera+MariaDB”搭建多主数据库集群的实例
1、什么是多主数据库集群 多主数据库集群是一种数据库集群架构,每个节点都可以接收写入操作和读取操作,并且通过心跳机制同步数据,保证数据一致性和高可用性。因多主数据库集群每个节点都可以承担读写操作,因此它可以充分利用各个…...

arcgis javascript api4.x加载天地图cgs2000坐标系
需求:arcgis javascript api4.x加载天地图cgs2000坐标系 效果: 示例代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"wid…...

算法学习——回溯算法
回溯算法 理论基础回溯法的效率回溯法解决的问题回溯法模板 组合思路回溯法三部曲 代码 组合(优化)组合总和III思路代码 电话号码的字母组合思路回溯法来解决n个for循环的问题回溯三部曲代码 组合总和思路代码 组合总和II思路代码 理论基础 什么是回溯法…...

C语言—小小圣诞树
这个代码会询问用户输入圣诞树的高度,然后根据输入的高度在控制台上显示相应高度的圣诞树。 #include <stdio.h>int main() {int height, spaces, stars;printf("请输入圣诞树的高度: ");scanf("%d", &height);spaces height - 1;st…...

Android消息公告上下滚动切换轮播实现
自定义控件 通过继承TextSwitcher实现 直接上代码 public class NoticesSwitcher extends TextSwitcher implements ViewSwitcher.ViewFactory {private Context mContext;private List<Notice> mData;private final long DEFAULT_TIME_SWITCH_INTERVAL 1000;//1spri…...

tensorflow入门 自定义模型
前面说了自定义的层,接下来自定义模型,我们以下图为例子 这个模型没啥意义,单纯是为了写代码实现这个模型 首先呢,我们看有几个部分,dense不需要我们实现了,我们就实现Res,为了实现那个*3,我们…...

虚拟机启动 I/O error in “xfs_read_agi+0x95“
1.在选择系统界面按e 进入维护模式 2.找到ro把ro改成 rw init/sysroot/bin/sh 然后按Ctrlx 3.找到坏掉的分区,以nvme0n1p3为例进行修复 xfs_repair -d /dev/nvme0n1p3 4.init 6 重新启动 以下情况 先umount 再修复 则修复成功...

【MYSQL】-库的操作
💖作者:小树苗渴望变成参天大树🎈 🎉作者宣言:认真写好每一篇博客💤 🎊作者gitee:gitee✨ 💞作者专栏:C语言,数据结构初阶,Linux,C 动态规划算法🎄 如 果 你 …...

网络协议小记
一、TCP/IP协议 作为一个小萌新,当然我无法将tcp/ip协议的大部分江山和盘托出,但是其中很多面试可能问到的知识,我觉得有必要总结一下! 首先,在学习tcp/ip协议之前,我们必须搞明白什么是tcp/ip协议。 1、…...

STM32-I2C通讯-AHT20温湿度检测
非常感谢,提供的视频学习 https://www.bilibili.com/video/BV1QN411D7ak/?spm_id_from333.788&vd_source8ca4826038edd44bb618801808a5e076 该文章注意:串口显示中文会乱码,必须选用支持ASCII的串口助手,才能正常显示中文。…...
【机器学习】043_准确率、精确率、召回率
一、定义 在处理偏斜数据集时,通常使用不同的误差度量,而不仅仅是使用分类误差来衡量算法性能。 1. 混淆矩阵的概念 二分类问题的混淆矩阵为2X2矩阵,由四部分组成: 假阴性(FN):模型预测为负…...

【Qt开发流程】之文件目录、文件、输入和输出
概述 应用程序操作过程中,经常要对设备或文件进行读或者写操作。也会经常对文件及目录进行操作。 在Qt中,QIODevice类是Qt中所有进行I/O操作的设备的基类,比如QFile、 QIODevice为支持数据块读写的设备(如QFile、QBuffer和QTcpSo…...

CSS的基本选择器及高级选择器(附详细示例以及效果图)
Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍HTML中CSS的基础选择及高级选择器(详解)以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获…...

股票价格预测 | Python实现基于Stacked-LSTM的股票预测模型,可预测未来(keras)
文章目录 效果一览文章概述模型描述源码设计效果一览 文章概述 以股票价格预测为例,基于Stacked-LSTM的股票预测模型(keras),可预测未来。 模型描述 LSTM 用于处理序列数据,如时间序列、文本和音频。相对于传统的RNN,LSTM更擅长捕获长期依赖关系,...

数据可视化---离群值展示
内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…...