Android设计模式之Builder模式在实际项目的运用

时间:2024-10-08 09:35:30

文章目录

        • 背景
        • 为什么要用?
          • 使用场景:
          • UML图解:
        • Builder模式的优缺点
        • 变种Builder模式
        • 在实际项目的案例:
          • 网络请求时通用参数配置
          • MaterialDialog对话框
        • 小结

背景

Builder模式是一种设计模式,Android源码中AlertDialog就是使用Build设计模式,这种模式的主要特点就是链式的,方便使用者的调用,使用者无需关心内部如何实现就可以方便调用。

为什么要用?

首先了解一下定义:

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

使用场景:
  • 相同的方法,不同的执行顺序,产生不同的事件结果时;

  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;

  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适;

  • 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时。

UML图解:

UML

在建造者模式结构图中包含如下几个角色:

● Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

● ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

● Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。

● Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

Builder模式的优缺点

优点:

  • 良好的封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节
  • 建造者独立,容易扩展

缺点:

  • 会产生多余的Builder对象以及Director对象,消耗内存
变种Builder模式

变种Builder模式在现实Android开发中会经常用到。现实开发中,Director角色会经常被省略。而直接使用一个Builder来进行对象的组装,这个Builder通常为链式调用,它的关键点是每个setter方法都返回自身,即return this。调用如下:

new TestBuilder().setA("A").setB("B").create()
  • 1

好处:

目的在于减少对象创建过程中引入的多个重载构造函数、可选参数以及setter过度使用导致不必要的复杂性。

运用实例可以在最下文的参考资料3中查看关于User对象属性的例子,此处不在赘述。

再举一例:

参加面试的同学可能会有被问到StringStringBufferStringBuilder的区别,这里就有StringBuilder这个字符串构建器,我们来简单看一下这个构建器的用法吧!

  String sb = new StringBuilder().append("I ")
                .append("am ")
                .append("student.").toString();

	//我们看一下这里用的append()函数的源码
	@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可知这个append()将传入的str通过调用父类的方法加到了该对象字符串的后面,并将该对象返回了,这样就可以连续的调用append()方法,并且保持对象的唯一性(String每次都会创建一个新的对象)。

在实际项目的案例:
  • 网络请求时通用参数配置
 	private const val DEFAULT_CONNECT_TIMEOUT = 5_000L
    private const val OTHER_TIME_OUT = 5_000L    

	/**
     * 测试环境网络请求配置
     */
    fun testConfig() {
        val httpConfig = ().baseUrl()
            // 打印使用http请求日志
            .setLogLevel()
            // 设置全局超时时间
            .connectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT)
            .readTimeoutMillis(OTHER_TIME_OUT)
            .writeTimeoutMillis(OTHER_TIME_OUT).build()
        (httpConfig)
    }

	/**
     * 网络请求配置构建者
     */
    class Builder {
        private var baseUrl = ""
        private var interceptors: ArrayList<Interceptor> = ArrayList()
        private var networkInterceptors: ArrayList<Interceptor> = ArrayList()
        private var defaultConnectTimeout = 10_000L
        private var defaultReadTimeout = 10_000L
        private var defaultWriteTimeout = 10_000L
        private var retryOnConnectionFailure = false
        private var isUseCookie = false
        private var isUseCache = false
        private var logLevel = 
        private val commonHeaders = ArrayMap<String, String>()
        private val commonParams = ArrayMap<String, String>()
        private var sslParam: SSLParam = ()
        private var hostnameVerifier: HostnameVerifier = 

        fun baseUrl(url: String):  {
            baseUrl = url
            return this
        }

        fun addInterceptor(interceptor: Interceptor):  {
            (interceptor)
            return this
        }

        fun addNetworkInterceptor(interceptor: Interceptor):  {
            (interceptor)
            return this
        }

        /**
         * 连接超时时间
         * @param millis 单位是毫秒(默认10秒)
         */
        fun connectTimeoutMillis(millis: Long):  {
            if (millis <= 0) {
                throw IllegalArgumentException("connect timeout must Greater than 0")
            }
            defaultConnectTimeout = millis
            return this
        }

        /**
         * 读取超时时间
         * @param millis 单位是毫秒(默认10秒)
         */
        fun readTimeoutMillis(millis: Long):  {
            if (millis <= 0) {
                throw IllegalArgumentException("read timeout must Greater than 0")
            }
            defaultReadTimeout = millis
            return this
        }

        /**
         * 写入超时时间
         * @param millis 单位是毫秒(默认10秒)
         */
        fun writeTimeoutMillis(millis: Long):  {
            if (millis <= 0) {
                throw IllegalArgumentException("write timeout must Greater than 0")
            }
            defaultWriteTimeout = millis
            return this
        }

        /**
         * 连接失败时是否重新进行网络请求
         * @param retryOnConnectionFailure 默认为false
         */
        fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean):  {
             = retryOnConnectionFailure
            return this
        }

        /**
         * 是否开启cookie
         * @param isUseCookie 默认为false
         */
        fun useCookie(isUseCookie: Boolean):  {
             = isUseCookie
            return this
        }

        /**
         * 是否使用缓存
         * @param isUseCache 默认为false
         */
        fun useCache(isUseCache: Boolean):  {
             = isUseCache
            return this
        }

        /**
         * 设置日志级别,参考[]
         * @param level 默认为[]
         */
        fun setLogLevel(level: ):  {
            logLevel = level
            return this
        }

        /**
         * 设置通用请求header
         * @param key header键
         * @param value header值
         */
        fun commonHeader(key: String, value: String):  {
            commonHeaders[key] = value
            return this
        }

        /**
         * 设置通用请求参数
         * @param key 参数键
         * @param value 参数值
         */
        fun commonParam(key: String, value: String):  {
            commonParams[key] = value
            return this
        }

        /**
         * 配置ssl
         * @param param ssl参数,默认不对证书做任何检查
         */
        fun sslSocketFactory(param: SSLParam):  {
            sslParam = param
            return this
        }

        /**
         * 主机名验证
         * @param verifier 默认允许所有主机名
         */
        fun hostnameVerifier(verifier: HostnameVerifier):  {
            hostnameVerifier = verifier
            return this
        }

        fun build(): HttpConfig {
            return HttpConfig(
                baseUrl, interceptors, networkInterceptors, defaultConnectTimeout
                , defaultReadTimeout, defaultWriteTimeout, retryOnConnectionFailure, isUseCookie
                , isUseCache, logLevel, commonHeaders, commonParams, sslParam, hostnameVerifier
            )
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • MaterialDialog对话框
   private fun showSavePromptDialog() {
        MaterialDialog(this)
            .message(text ="消息提示标题")
            .positiveButton("保存") {
               // do something
            }
            .negativeButton(“取消”) {
               // do something
            }
            .showByCZConfig()
    }

/**
*MaterialDialog扩展
*/
fun (): MaterialDialog {
    show()
    doAsync {
        (50L)
        uiThread {
         val positiveButton = findViewById<AppCompatButton>(.md_button_positive)
         val negativeButton = findViewById<AppCompatButton>(.md_button_negative)
         val neutralButton = findViewById<AppCompatButton>(.md_button_neutral)

         ((.common_font_red))
         ((.common_font_black))
         ((.common_font_black))
        }
    }
    return this
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

其他:如开源的图片框架ImageLoader就是通过ImageLoaderConfig进行配置等等。

小结

在开发中,当遇到一个类的构造器或者静态工厂中具有多个参数,特别是大多数参数是可选的时候,可以考虑使用Builder模式,避免过多的setter方法。Builder模式比较常见的实现形式是通过调用链实现,这样使得代码更简洁、易懂。

参考资料:

1.设计模式系列——建造者模式-Builder Pattern

2.创建型设计模式之Builder模式

: Builder模式 详解及学习使用

4.变种 Builder 模式:优雅的对象构建方式