设计模式-建造模式

在阎宏博士的《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()

 

posted @   johnny_zhao  阅读(188)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示