• 设计模式-建造模式


    在阎宏博士的《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()
  • 相关阅读:
    LeetCode 230. Kth Smallest Element in a BST
    LeetCode 114. Flatten Binary Tree to Linked List
    LeetCode 222. Count Complete Tree Nodes
    LeetCode 129. Sum Root to Leaf Numbers
    LeetCode 113. Path Sum II
    LeetCode 257. Binary Tree Paths
    Java Convert String & Int
    Java Annotations
    LeetCode 236. Lowest Common Ancestor of a Binary Tree
    LeetCode 235. Lowest Common Ancestor of a Binary Search Tree
  • 原文地址:https://www.cnblogs.com/johnnyzhao/p/10407381.html
Copyright © 2020-2023  润新知