设计模式-建造模式
在阎宏博士的《JAVA与模式》一书中开头是这样描述建造(Builder)模式的:
建造模式是对象的创建模式。
建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。(内容转自此文章)
产品的内部表象
一个产品常有不同的组成成分作为产品的零件,这些零件有可能是对象,也有可能不是对象,它们通常又叫做产品的内部表象(internal representation)。不同的产品可以有不同的内部表象,也就是不同的零件。使用建造模式可以使客户端不需要知道所生成的产品有哪些零件,每个产品的对应零件彼此有何不同,是怎么建造出来的,以及怎么组成产品。
建造模式的结构
在这个示意性的系统里,最终产品Product只有两个零件,即part1和part2。相应的建造方法也有两个:buildPart1()和buildPart2()、同时可以看出本模式涉及到四个角色,它们分别是:
抽象建造者(Builder)角色:给 出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者 (ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的两种方法:一种是建造方法(buildPart1和 buildPart2),另一种是返还结构方法(retrieveResult)。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少 零件,就有多少相应的建造方法。
具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序调用下创建产品的实例。这个角色要完成的任务包括:1.实现抽象建造者Builder所声明的接口,给出一步一步地完成创建产品实例的操作。2.在建造过程完成后,提供产品的实例。
导演者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。应当指出的是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。
产品(Product)角色:产品便是建造中的复杂对象。一般来说,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以是不相关联的。
导演者角色是与客户端打交道的角色。导演者将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但是却不为客户端所知。
一般来说,每有一个产品类,就有一个相应的具体建造者类。这些产品应当有一样数目的零件,而每有一个零件就相应地在所有的建造者角色里有一个建造方法。
代码
/** * 产品类 * */ class Product { var part1: String = "" var part2: String = "" }
/** * 抽象建造者类IBuilder * */ interface IBuilder { fun builderPart1() fun builderPart2() fun retrieveResult(): Product }
/** * 具体建造者 * */ class ConcreteBuilder : IBuilder { private val product = Product(); override fun builderPart1() { product.part1 = "编号:9527" } override fun builderPart2() { product.part2 = "名称:xxxx" } override fun retrieveResult(): Product { return product } }
/** * 导演者类 * */ class Director constructor(builder_: IBuilder) { /** * 持有当前需要使用的建造器对象 */ private var builder: IBuilder? = null init { this.builder = builder_ } fun construct() { //开始构建产品 this.builder?.builderPart1() this.builder?.builderPart2() } }
测试
val builder = ConcreteBuilder()
//客户端把具体建造者对象交给导演者,导演者操作具体建造者,开始创建产品
val director = Director(builder)
//开始创建产品
director.construct()
//当产品完成后,建造者把产品返还给客户端。
val product = builder.retrieveResult()
println(product.part1)
println(product.part2)
使用场景
使用建造模式构建复杂对象
考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。这个对象里有很多类似这样的约束,采用建造模式来构建复杂的对象,通常会对建造模式进行一定的简化,因为目标明确,就是创建某个复杂对象,因此做适当简化会使程序更简洁。大致简化如下:
● 由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。
● 对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“导演者”,把导演者的功能和Client的功能合并起来,也就是说,Client这个时候就相当于导演者,它来指导构建器类去构建需要的复杂对象。
/** * 保险合同对象 */ class InsuranceContract { //保险合同编号 private var contractId = "" /** * 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订 * 也就是说,“被保险人员”和“被保险公司”这两个属性,不可能同时有值 */ private var personName = "" //被保险公司的名称 private var companyName = "" //将产品对象的构造函数私有化,防止客户端不使用构建器来构建产品对象, private constructor(concreteBuilder_: ConcreteBuilder) { this.contractId = concreteBuilder_.contractIdValue this.personName = concreteBuilder_.personNameValue this.companyName = concreteBuilder_.companyNameValue } fun someOperation() { println("current operation contract id is: ${this.contractId}") } //成员内部类 //关键在于需要用inner关键字来修饰内部类的声明,才能让Kotlin中默认的静态内部类变为非静态的成员内部类 //Kotlin默认内部类是静态内部类,和Java相反 class ConcreteBuilder { private var contractId = "" private var personName = "" private var companyName = "" constructor(contractId: String) { this.contractId = contractId } //为了不让创建builder时让外部设置到这里相关属性,所以用以下by lazy方式,并只读 //大家有没其它更好方法。。 val contractIdValue: String by lazy { this.contractId }
val personNameValue: String by lazy { this.personName }
val companyNameValue: String by lazy { this.companyName } fun setPersonName(personName: String): ConcreteBuilder { this.personName = personName return this } fun setCompanyName(companyName: String): ConcreteBuilder { this.companyName = companyName return this } fun build(): InsuranceContract { if (contractId.isEmpty()) throw IllegalArgumentException("contract id cannot be null") val signPerson = personName.isNotEmpty() val signCompany = companyName.isNotEmpty() if (signPerson && signCompany) { throw IllegalArgumentException("一份保险合同不能同时与个人和公司签订") } if (!signPerson && !signCompany) { throw IllegalArgumentException("一份保险合同不能没有签订对象") } return InsuranceContract(this) } } }
客户端
val builder = InsuranceContract.ConcreteBuilder("95237") //设置需要的数据,然后构建保险合同对象 val contract = builder.setCompanyName("google").build() contract.someOperation()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)