前言
这段时间一直在解析Android常用的开源框架(绞尽脑汁),关于网络框架中的OkHttp和retrofit(目前进度),其中对于retrofit用到了非常多的设计模式,所以为了更好的理解源码,这里提前复习下设计模式
可以看看retrofit中用到哪些设计模式
1.Retrofit构建过程
建造者模式、工厂方法模式
2.创建网络请求接口实例过程
外观模式、代理模式、单例模式、策略模式、装饰模式(建造者模式)
3.生成并执行请求过程
适配器模式(代理模式、装饰模式)
工厂方法模式
适用于复杂对象的创建,定义一个创建对象的类,让子类去决定实例化哪个类
实现方式
下面我们来实际下工厂模式如何实现
- 创建产品,实现公共接口
interface Product {
val name:String
fun show()
}
- 创建具体产品多个,需要继承Product
//具体产品A
class ProductA(override val name: String = "ProductA") : Product {
override fun show() {
Log.e("ProductA", "show: ")
}
}
//具体产品B
class ProductB(override val name: String = "ProductB") : Product {
override fun show() {
Log.e("ProductB", "show: ")
}
}
- 创建抽象工厂,实现公共接口
interface Factory {
fun makeProduct() : Product
}
- 实现具体工厂,继承Factory
class FactoryA : Factory {
override fun makeProduct(): Product {
return ProductA()
}
}
class FactoryB:Factory {
override fun makeProduct(): Product {
return ProductB()
}
}
Android中的应用
下面以BitmapFactory为例,生成
// 生成 Bitmap 对象的工厂类 BitmapFactory
public class BitmapFactory {
···
public static Bitmap decodeFile(String pathName) {
···
}
···
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
}
···
return bm;
}
···
}
总结
- 优点
- 符合单一职责原则,每个具体工厂只负责对应的产品
- 当我们需要新增产品的时候,只需要新增一个产品的具体类和相应工厂的子类
- 缺点
- 一个具体工厂类只能创建一个具体产品,如果需要新增一种产品的话,我们还需要创建一个新的对应工厂类,这样无疑会增加开销
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,适用于初始化的对象比较复杂且参数较多的情况。
实现方式
- 首先我们看下创建个dialog是怎么用的,怎么去构建复杂对象的,在kotlin中一般使用apply函数就可以实现了
fun createDialog(context: Context) {
val dialog = Dialog(context).apply {
setTitle("DialogA")
setCancelable(true)
setCanceledOnTouchOutside(true)
setContentView(contentView)
}
}
- 看到上面Lambda表达式里可以调用**Dialog.show()**等其他与构建对象无关的方法,或者不想公开构造函数,只想通过Builder来构建对象,这时可以使用Type-Safe Builders
class CarsBuilder
(
val model: String?,
val name: String?,
val year: Int
) {
//使用的是Types-Builder来进行创建
constructor(builder: Builder) : this(builder.model, builder.name,builder.year)
class Builder {
var model:String? = null
var name:String? = null
var year:Int = -1
fun build() = CarsBuilder(this)
}
companion object {
inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
}
}
val cars = CarsBuilder.build {
model = "丰田凯美瑞"
name = "双擎2021"
year = 2021
}
- 这样写的好处在于Builder类中的成员函数返回Builder对象自身,让它支持链式调用,如果只想传入部分参数,即可在定义build的时候写就可以了,如果之后需要用新的配置参数,直接添加进来,这样也提高了代码的可扩展性和可读性
现在我们来简单总结下Builder模式的创建
- 定义内部类Builder,它的成员变量和外部类是一样的
- 构造方法对成员变量赋值,然后返回自己本身this
- 创建一个外部创建方法build(),使用Type-Safe Builders来进行创建,外部类提供一个私有构造函数供内部类调用,在该函数中完成成员变量的赋值,取值为Builder对象中对应的成变量的值
Android中的应用
- 对话框的创建 可以看下AlertDialog。Builder()
fun showDialog(context: Context) {
val builder = AlertDialog.Builder(context)
.setTitle("TEST")
.setMessage("Test")
val dialog = builder.create()
dialog.show()
}
AlertDialog.java
public class AlertDialog extends Dialog implements DialogInterface {
···
public static class Builder {
private final AlertController.AlertParams P;
···
public Builder(Context context) {
this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
}
···
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
···
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
···
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
···
return dialog;
}
···
}
}
- 网络框架Okhttp(之前写的开源框架有说到过)
val okHttpClient = OkHttpClient()
val request = Request.Builder()
.url(BASE_URL)
.build()
总结
- 优点
- Builder模式的好处在于将配置的构建和表示抽离出来,避免写过多的setter,getter方法;
- builder创建常用链式调用,这样代码显得简洁,易懂
- 缺点 因为内部和外部都有引用,相对来说,内存消耗会比较大,但是对于现在手机应用来说,问题不太
单例模式
确保某一个类只有一个实例,并自动实例化向整个系统提供这个实例,且可以避免产生多个对象消耗资源。
对于上述单例模式的定义,可以说是很官方了,在移动开发中具体有什么好处呢?在于它至始至终都只有一个实例,每次只实例一次大大提高了我们的性能
实现方式
对于单例模式大致实现方式有两种:饿汉模式和懒汉模式
饿汉模式
对于kotlin实现方式,直接调用object实现即可
object SingleTest {
...
}
//kotlin调用
SingleTest.xx()
//java调用
SingleTest.INSTANCE.xx()
这个其实和饿汉式实现原理是一样的,其实可以反编译看看
public final class SingleTest {
@NotNull
public static final SingleTest INSTANCE;
private SingleTest() {
}
static {
SingleTest var0 = new SingleTest();
INSTANCE = var0;
}
}
实际上看出object还是利用了INSTANCE静态变量,这样看来在类加载的时候就去实例化了,但是还是会存在一些问题,在这个object类不允许有自定义构造函数constractor;由于一开始创建了对象但是并没有调用,会导致资源浪费;如果在里面做了耗时操作的话,会导致加载较慢
懒汉模式
这种单例是在你调用的时候再去实例化,在kotlin当中Lazy就刚好适合这种场景,可以看下使用方式
class SingleTon constructor() {
companion object {
val instance: SingleTon by lazy { SingleTon() }
}
}
这样看起来是不是简洁了许多,那么延迟属性Lazy到底做了什么操作呢?其实它默认也是线程安全的,synchronized 修饰方法、双重检查锁定、静态内部类都是线程安全的懒汉式
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE // 声明为 volatile 变量
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) { // 第一次检查
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) { // 加锁锁定
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) { // 第二次检查
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
...
}
- 可以看到延迟属性Lazy的代码中也是用到了双重校验模式进行延迟初始化,同样是线程安全的,相对来说调用起来简洁方便了许多
- 其中可以传入三种线程安全模式LazyThreadSafetyMode,默认是LazyThreadSafetyMode.SYNCHRONIZED同步锁
- 第一种SYNCHRONIZED,使用同步锁保证只有一个线程可以进行实例化
- PUBLICATION,可以允许多个线程进行实例化,但是最先返回的那个线程才会做为lazy的初始化实例.
- NONE,不是线程安全的,也没有任何保证和开销(我是基本用不到)
Android中的应用
在输入法开发当中,使用到的InputMethodManager,这个类初始化的时候就用到了单例模式,用于控制显示或隐藏输入法面板,下面是它的代码
InputMethodManager.java
/**
* Retrieve the global InputMethodManager instance, creating it if it
* doesn't already exist.
* @hide
*/
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
try {
sInstance = new InputMethodManager(Looper.getMainLooper());
} catch (ServiceNotFoundException e) {
throw new IllegalStateException(e);
}
}
return sInstance;
}
}
总结
优缺点
- 优点
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 避免对共享资源的多重占用。
- 缺点
- 如果对于同一个对象要在不同的场景中发送变化的,使用单例会引发数据错误,不能保存彼此的状态
- 如果使用的单例对象的功能过于繁重,在一定程度上违背了单一职责原则(具体可以去看面向对象设计原则)
- 不要泛用单例模式,如果你去实例化的对象没有被调用,有可能会被系统GC,从而对象状态就丢失了
适用场景
单例模式因为只实例化一个对象,所以那些对象可以公用的场景都可以使用
-
需要频繁访问数据库的对象
-
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
-
有状态的工具类对象。
-
那种频繁调用又被销毁的对象