赞
踩
本博客中,我们将介绍有关Kotlin Builder模式的几个方面。我们了解如何创建Builder模式以及是否应该在Kotlin中使用它
许多程序员使用一种模式来连接成员函数的调用,以避免重新键入对象的名称。此模式通常与Builder模式本身结合使用。人们必须小心,因为有些人混淆了术语并说连接是Builder模式。实际上,一些开发人员会抱怨这不是“Kotlin方式”(例如在StackOverflow")的一些论坛中)。然而,我们认为这种配置对象的方法是许多程序员都知道的并且非常明确。所以不适用它是没有意义的。
以下代码就是一个示例。
// 没有链接
val person = Person()
person.setName("Tom")
person.setAddress("Street")
person.setAge(18)
// 带链接
val person = Person()
person
.setName("Tom")
.setAddress("Street")
.setAge(18)
第一种方法的问题是每行都必须重写对象名称。由于典型的复制粘贴错误,这会导致潜在的错误。以下代码可以正确编译,但不是您想要的。在进行复制和粘贴时,经常会发生这种错误。
//赋值粘贴错误
val person = Person()
person.name = "Tom"
person.address = "Street"
person.age = 18
val person2 = Person()
person.name = "Tom"
person.address = "Street"
person.age = 18
因此,第二种方法要好得多。您很少需要修改对象的名称。这会减少错误。要实现这种行为,您需要在函数调用中返回对象本身。Kotlin原生提供了一个扩展函数apply来为您完成这项工作。
扩展函数apply采用lambda函数并提供所配置对象的所有公共变量的访问。使用apply的优点是它是内置的,因此不需要任何样板代码。特别是对于数据类,这是最好的方法。在下一个代码示例中,我们首先展示如何以传统方式使用链接。这是大多数程序员所熟悉的版本。第二个版本使用kotln的内置功能。由于第二版本需要较少的代码,因此它是首选方式。
// 一般方式 class Person() { var name = "" var address = "" var age = 0 fun setName(name: String): Person { this.name = name return this } fun setAddress(address: String): Person { this.address = address return this } fun setAge(age: Int): Person { this.age = age return this } }
// 使用apply class Person() { var name = "" var address = "" var age = 0 fun setName(name: String) = apply { this.name = name } fun setAddress(address: String) = apply { this.address = address } fun setAge(age: Int) = apply { this.age = age } }
请注意,apply也可以在客户端(使用该对象的函数)用作扩展函数。这对于数据类特别有用。但是,通过这种方式,您无法访问私有成员/函数(正如预期的那样)。
fun main() { val person = Person() person .setName("Tom") .setAddress("Street") .setAge(18) val person2 = Person() person2.apply { name = "Tom" address = "Street" age = 18 // only access to public properties } }
Kotlin 提供了一个实验性的_Lombok_编译器插件。要使用它,您必须按照KotlinLang中的讨论安装插件。由于它仍处于实验阶段,我们不建议使用它。Kotlin Lombok_编译器插件允许Kotlin 代码在同一个混合 Java/Kotlin 模块中生成和使用 Java 的_Lombok声明。 如果准备好,它将允许诸如 @builder 之类的注释,这将自动创建所有必需的代码。
使用这种设计模式的另一个方面是,它为提供专用软件层提供了完美的场所。该层完全负责应用有关域对象创建的业务规则。在以下示例中,可以使用PersonBuilder
构建域对象Person
。PersonBuilder
包含所有相关逻辑来检查是否可以构建Person。这又不是 GoF 书中描述的实际“构建器模式”。
class Person() { var name = "" var address = "" var age = 0 } class PersonBuilder() { private var name = "" private var address = "" private var age = 0 fun setName(name: String) = apply { this.name = name } fun setAddress(address: String) = apply { this.address = address } fun setAge(age: Int) = apply { this.age = age } fun canBuild(): Boolean { // do business rule, checks return true } fun build(): Person { val person = Person() if (canBuild()) { person.address = address person.name = name person.age = age } return person } }
使用此模式的另一个原因是将对象的构造和配置分开。这不仅是因为将这两个问题分开可能更容易。但也可能当时并非所有信息都可用。人们可以想象,在用户界面中我们可以配置一个弹出窗口。点击启动按钮时,会创建弹窗。创建和配置在时间上是完全分开的。
Build设计模式的另一个方面是创建DSL语言。DSL代表特定领域语言。Kotlin能够使用命名良好的函数作为构建器,结合函数文字作为接收器,创建类型安全、静态类型的构建器。这允许创建类型安全的特定于域的语言(DSL),适合以半声明的方式构建复杂的分层数据结构。例如想象一下下面的代码
// 组合对象
class Address {
var city = ""
var street = ""
}
class Person {
var name = ""
var age = 0
var address = Address()
}
// 陈述性协作
val person = person {
name = "John"
age = 18
address {
city = "New York"
street = "Main Street"
}
}
要实现这一点,您必须使用函数、函数名称和lambda函数。在这种情况下使用以下内容:
// DSL 示例 class Address { var city = "" var street = "" } class Person { var name = "" var age = 0 var address = Address() fun address(init: Address.() -> Unit) { val address = Address() address.init() this.address = address } } fun person(init: Person.() -> Unit): Person { val person = Person() person.init() return person }
这段代码有什么作用?首先我们声明了一个名为person
的函数。此函数接受带有人员上下文的lambda函数(init)。这允许访问lambda函数中的公共属性。其次,我们在Person类中创建一个新函数address
。该功能与Person的功能类似。
接下来会有单独一篇博客,介绍Kotlin领域特定语言支持的优缺点和最佳实践,敬请期待~
从现在开始,我们将展示如何在Kotlin中实现实际的Builder模式。我们参考《设计模式》一书中描述的额实际方式。然而,我们不会像书中那样详细介绍。我们将更多地关注Kotlin的实现方面。
将复杂对象的的构造与其表示分离,以便相同的构造过程可以创建不同的表示
构造器 - 设计模式/GoF
总体结构如下:
创建一个基本构架器类(AbstractBuilder
),它定义某些函数(actionA()
等)。在基本实现中,这些函数通常是空的。它们将在构建器类(ContreteBuilderA
等)的具体实现中被覆盖。这些具体实现还定义了返回特定蟾片的构建函数。请注意,产品中可能会有很大不同。因此它们不一定需要具有相同的接口。
Build模式的一个常见替代方案是使用抽象工厂。这种设计模式还涉及对象构建过程的分离。区别在于抽象工厂返回抽象对象,而构建器模式通常返回具体对象。在抽象工厂中,客户端不需要知道实例化了哪种对象,而在构建器模式中,客户端则知道。
为了提供更多的安全性,可以将具体对象的构造函数声明为私有。唯一的公共构造函数将接受在init块中构建对象的构建器。
考虑以下示例。在用户界面中,可以定义弹出窗口的信息。这包括Title
、Text
、ActionButton
和CancelButton
。信息存储为json
、xml
和Widget
。三种不同形式的创建是由建造者完成的。
// 弹出窗口信息的数据类 data class PopupWindowInfo( val title: String?, val text: String?, val actionButton: String?, val cancelButton: String? ) // 弹出窗口格式的接口 interface PopupFormat { fun format(info: PopupWindowInfo): String } // JSON格式的弹出窗口 class JsonPopupFormat: PopupFormat { override fun format(info: PopupWIndowInfo): String { return """ { "title": "${info.title}", "text": "${info.text}", "actionButton": "${info.actionButton}", "cancelButton": "${info.cancelButton}" } """.trimIndent() } } // XML格式的弹出窗口 class XmlPopupFormat: PopupFormat { override fun format(info: PopupWindowInfo): String { return """ <popup> <title>${info.title}</title> <text>${info.text}</text> <actionButton>${info.actionButton}</actionButton> <cancelButton>${info.cancelButton}</cancelButton> </popup> """.trimIndent() } } // Widget格式的弹出窗口 class WidgetPopupFormat: PopupFormat { override fun format(info: PopupWindowInfo): String { //在这里实现将信息渲染为Widget的逻辑 return "Widget representation of popup info" } } // 构造器类 class PopupWindowBuilder { private var title: String? = null private var text: String? = null private var actionButton: String? = null private var cancelButton: String? = null fun setTitle(title: String) = apply { this.title = title } fun setText(text: String) = apply { this.text = text } fun setActionButton(actionButton: String) = apply { this.actionButton = actionButton } fun setCancelButton(cancelButton: String) = apply { this.cancelButton = cancelButton } fun build(format: PopupFormat): String { var popupInfo = PopupWindowInfo(title, text, actionButton, cancelButton) return format.format(popupInfo) } } fun main() { // 创建一个弹出窗口的信息的Builder val builder = PopupWindowBuilder() .setText("Sample Title") .setText("This is the content of the popup window.") .setActionButton("OK") .setCancelButton("Cancel") //创建不同格式的弹出窗口 val jsonFormat = JsonPopupFormat() val xmlFormat = XmlPopupFormat() val widgetFormat = WidgetPopupFormat() val jsonPopup = builder.build(jsonFormat) val xmlPopup = builder.build(xmlFormat) val widgetPopup = builder.build(widgetFormat) println("JSON Popup") println(jsonPopup) println("\nXML Popup") println(xmlPopup) println("\nWidget Popup") println(widgetPopup) }
在这个完整的例子中,我们创建了不同格式的弹出窗口,包括JSON、XML和Widget。每个格式都有一个对应的类(JsonPopupFormat
、XmlPopupFormat
和WidgetPopupFormat
)来实现格式化逻辑。PopupWindowBuilder
的build()
方法现在接受一个格式参数,并使用适当的格式器来生成相应的信息。
在main
函数中,我们首先创建一个弹出窗口信息的Builder
,然后使用不同的格式来构建弹出窗口,并输出它们的格式化结果。这个例子演示了如何根据不同的格式要求使用Builder
模式来创建和输出弹出窗口信息。
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。