Android设计模式-更优雅实现复杂的逻辑关系

 

我对设计模式的个人理解

设计模式是为了更优雅地解决一些业务上必须产生强耦合联系的逻辑而产生的一些解决方案

设计模式不是必须的,相反,不使用设计模式在某些情况下甚至会让你的代码更加简洁易读

那么为什么我们仍然需要使用设计模式呢?

因为在项目中,只有你自己能读懂自己的代码这样是不行的,当然,你可以对产生业务耦合的代码使用尽可能详细的注释来说明你的方法做了什么业务,怎么样做的这个业务,但是注释相较于逻辑是有滞后性的,开发者可能不会有精力在修改完业务之后还来得及对注释进行修正。

所以,我们引入设计模式,将大段大段的逻辑拆分成尽可能小的小点

设计模式并不难,难就难在怎么样找到适合使用的场景

创建型

建造者模式

建造者模式是提前预设定的相关方法来一步步完善对象的功能,最后组合在一起实现强大的功能

优点在于易扩展,粒度细,缺点是变化复杂

建造者模式在很多地方都有用到过,比如系统的AlertDialog就是建造者模式实现,比如Glide加载图片就是建造者模式实现

其实在Kotlin中,可变参和建造者模式已经差不多了,但是经典的建造者模式写起来更优雅,而不是所有的参数都写在方法里面,并且建造者模式是链式调用的,非常容易阅读

接下来是建造者的一个简单Demo,这个demo相对简单,调用方式是doDemodoDemoInline其中doDemoInline是kotlin很方便的一点,采用内联方法来进行构建

class BuilderDemo {
    companion object {
        fun Builder(): build {
            return BuilderDemo.build()
        }

        fun doDemo(context: Context) {
            BuilderDemo.Builder().with(context).setColor1(Color.RED).setColor2(Color.LTGRAY)
                .setColor3(Color.GREEN).setColor4(Color.BLUE).create().show()
        }

        fun doDemoInline(context: Context) {
            BuilderDemo().show {
                this.context = context
                this.color1 = Color.RED
                this.color2 = Color.LTGRAY
                this.color3 = Color.GREEN
                this.color4 = Color.BLUE
            }
        }
    }


    private lateinit var context: Context
    private var color1: Int = Color.parseColor("#66CCCC")
    private var color2: Int = Color.parseColor("#66CCCC")
    private var color3: Int = Color.parseColor("#66CCCC")
    private var color4: Int = Color.parseColor("#66CCCC")

    class build {
        private var builderDemo = BuilderDemo()

        fun with(context: Context): build {
            builderDemo.context = context
            return this
        }

        fun setColor1(color: Int): build {
            builderDemo.color1 = color
            return this
        }

        fun setColor2(color: Int): build {
            builderDemo.color2 = color
            return this
        }

        fun setColor3(color: Int): build {
            builderDemo.color3 = color
            return this
        }

        fun setColor4(color: Int): build {
            builderDemo.color4 = color
            return this
        }


        fun create(): BuilderDemo {
            return builderDemo
        }
    }

    fun show() {
        //生成一个View,显示在Dialog上
        val paint = Paint()
        val view = object : View(context) {
            override fun onDraw(canvas: Canvas?) {
                super.onDraw(canvas)
                paint.color = color1
                canvas?.drawCircle(100F, this.height / 2F, 100F, paint)
                canvas?.save()
                canvas?.translate(200F, this.height / 2F)
                paint.color = color2
                canvas?.drawCircle(100F, 0F, 100F, paint)
                canvas?.restore()
                canvas?.save()
                canvas?.translate(400F, this.height / 2F)
                paint.color = color3
                canvas?.drawCircle(100F, 0F, 100F, paint)
                canvas?.restore()
                canvas?.save()
                canvas?.translate(600F, this.height / 2F)
                paint.color = color4
                canvas?.drawCircle(100F, 0F, 100F, paint)
                canvas?.restore()

            }
        }
        view.layoutParams = ViewGroup.LayoutParams(500, 200)
        AlertDialog.Builder(context).setTitle("建造者模式").setMessage("生成的图形")
            .setView(view)
            .show()
    }

    //内联方式调用
    //这里使用了 内联函数(inline) 以避免lambda的额外开销。
    inline fun show(func: BuilderDemo.() -> Unit) {
        this.func()
        this.show()
    }

}

工厂模式

工厂模式是大家耳熟能详的模式了,Spring的工厂模式就十分出名,Android没有这么多的对象创建需求,其实很少用到工厂模式,但是在平时开发的时候也有用到不少,在创建对象托管的方便十分方便

比如说接入GooglePlayBilling的情况下,根据用户手机上的GP商店版本不同,需要创建不同的实现方案(普通模式和SKU),在这种场景下,可以将对象创建全部交给工厂,无需关心实例化的细节

下面是一个经典的工厂模式实现

调用方式

 fun doDemo(context: Context) {
        val factoryManager = FactoryManager()
        val f1 = factoryManager.getFactoryInstance(1)
        val f2 = factoryManager.getFactoryInstance(2)
        f1.getDialog(context).show()
        f2.getDialog(context).show()
    }

工厂管理者实现

class FactoryManager {
    fun getFactoryInstance(type: Int): IFactory {

        return when (type) {
            1 -> {
                FactoryType1()
            }

            2 -> {
                FactoryType2()
            }

            else -> {
                FactoryType1()
            }
        }
    }
}

对象接口

interface IFactory {
    fun getDialog(context:Context): AlertDialog
}

对象具体实现

class FactoryType1 : IFactory {
    override fun getDialog(context: Context): AlertDialog {
        return AlertDialog.Builder(context).setTitle("工厂1").setMessage("工厂1")
            .create()
    }
}

在上面这个Demo中,我们根据getFactoryInstance传递的不同的数值,实例化了不同的Dialog,这是一个经典的工厂模式,在实际开发中,我们可以将getFactoryInstance交给功能内部实现,从而进一步封装

结构型

装饰器模式

装饰器模式一开始我有点搞不清楚,专业的术语来说是

允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

后面了解到装饰器模式是对原来的类进行拓展,增强其原来的功能,和Kotlin的拓展类其实是一样的作用,经典的装饰器就是一个类,构造接收需要增强的类,继承需要增强的类,下面的Demo很轻松地解释了经典装饰器模式

class DecoratorActivity : AppCompatActivity() {
    private val binding by lazy { ActivityDecoratorBinding.inflate(LayoutInflater.from(this)) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        initView()
    }

    private fun initView() {
        binding.tvEnableDecorator.setOnClickListener(AntiShakeClickDecorator(2000L) {
            Toast.makeText(this, "这个是带2000ms防抖的哦", Toast.LENGTH_SHORT).show()
        })
        binding.tvDisEnableDecorator.setOnClickListener {
            Toast.makeText(this, "这个是不带防抖的哦", Toast.LENGTH_SHORT).show()
        }
    }

    class AntiShakeClickDecorator(
        private val delay: Long = 2000,
        private val onClickListener: View.OnClickListener
    ) : View.OnClickListener {
        private var lastClickTime: Long = 0

        override fun onClick(v: View?) {
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastClickTime > delay) {
                lastClickTime = currentTime
                onClickListener.onClick(v)
            }
        }
    }
}

行为型

行为型的一些总结

  • Q 命令模式,状态模式,责任链模式的区别?

    A 状态模式是If else if else, 职责链模式是Switch case, 命令模式是将多个命令交个一个对象

责任链模式

责任链模式在Android中有相当广泛的应用,比如点击事件的传递就是采用的责任链模式,比如OkHTTP的过程就是采用了责任链模式,我们用OHKHTTP举例,通常开发者在自定义拦截器的时候,标准的实现接口有返回我们一个链,该链可以拆分,既可以拿到Request的参数,也能拿到Responce的参数

责任链用很简单的话概括就是,你将参数给到链头,他会自动向下执行,直到找到某个链上的执行器宣布他进行了执行,这样的好处是链上的每一个执行器都有平等的处理机会

下面是一个较为简单的Demo用来说明责任链模式

处理单元接口

interface IChainOfResponsibilityHandler {
    fun setNextUnit(handler: IChainOfResponsibilityHandler)
    fun handlerRequest(context: Context)
}

处理单元实现

class DialogUnit1 : IChainOfResponsibilityHandler {
    private var mNextHandler: IChainOfResponsibilityHandler? = null

    override fun setNextUnit(handler: IChainOfResponsibilityHandler) {
        mNextHandler = handler
    }

    override fun handlerRequest(context: Context) {
        AlertDialog.Builder(context).setTitle("处理器1").setMessage("处理器1")
            .setNegativeButton("处理") { dialog, which ->
                mNextHandler?.handlerRequest(context)
            }.setNeutralButton("取消处理") { dialog, which ->
                return@setNeutralButton
            }.show()
    }
}

使用

 val dialogUnit1 = DialogUnit1()
 val dialogUnit2 = DialogUnit2()
 val dialogUnit3 = DialogUnit3()
 dialogUnit1.setNextUnit(dialogUnit2)
 dialogUnit2.setNextUnit(dialogUnit3)
 dialogUnit1.handlerRequest(context)

在这个demo中,我仅是依次弹了三个Dialog,可选处理和取消处理,这个就组成了一个简单的责任链模式,责任链的写法一般是写好后就是固定的,将参数给进去后按照写定的逻辑执行

状态模式

状态模式将对象内部的行为和状态进行了分离,在运行时根据状态的变化来改变对象的行为,整体使用不难,在开发中,多用于fragment切换等场景,比如加载,失败,成功的状态切换

状态接口定义如下

interface IFragmentState {
    fun showLoading()
    fun showSuccess()
    fun showFailure()
}

由activity实现接口,并且实现其中的相关行为逻辑,使用延迟来模拟各种场景中切换的功能,该模式非常简单地完成了fragment切换的封装

class StateActivity : AppCompatActivity(), IFragmentState {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_state)
        lifecycleScope.launch(Dispatchers.Default) {
            showLoading()
            delay(3000)
            showFailure()
            delay(3000)
            showLoading()
            delay(3000)
            showSuccess()
        }
    }

    override fun showLoading() {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.fl_content, StateLoadingFragment())
        transaction.commit()
    }

    override fun showSuccess() {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.fl_content, StateSuccessFragment())
        transaction.commit()
    }

    override fun showFailure() {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.fl_content, StateFailureFragment())
        transaction.commit()
    }
}

命令模式

命令模式我在开发中很少用到,不过从命令模式的调用方法来倒推实现过程倒是挺容易的

官方套话:命令模式中,将一个请求封装成一个对象,从而使得请求的发送者和接收者之间解耦。这样,发送者只需要知道如何发送请求,而不需要知道请求的具体内容和接收者的身份,也不需要知道请求是如何被处理的。接收者只需要知道如何处理请求,而不需要知道请求的来源和具体的处理方式。

我的理解:一次设定进去,下面一次执行,命令模式是责任链模式的反义词,命令将多个命令交个一个对象,而责任链将一个命令交给多个处理对象

先来看看线程池的命令模式使用

val executor: Executor = Executors.newSingleThreadExecutor()
val runnable = Runnable { Log.i(TAG, "do yourself in Runnable") }
executor.execute(runnable)
val thread: Thread = object : Thread() {
    override fun run() {
        super.run()
        Log.i(TAG, "do yourself in Thread")
    }
}
executor.execute(thread)

这是一个不标准的命令模式,是一个命令模式的变种,命令对象不再设置接收者,命令对象本身就完成了具体任务。

在上述代码中,先是获得了线程池的实例

之后用线程池来执行runnable或thread

这一种变种的写法在实际开发中用的更多

下面我也给出标准的经典命令模式的Demo,以供参考

调用方式

val commandReceiver = CommandReceiver()
val commandTimeImpl = CommandTimeImpl(commandReceiver)
val commandDateImpl = CommandDateImpl(commandReceiver)
val commandInvoker = CommandInvoker(commandDateImpl, commandTimeImpl)
commandInvoker.executeToastDate(context)
commandInvoker.executeToastTime(context)

命令接收者

class CommandReceiver {
    fun toastTime(context: Context){
        val simpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.US)
        Toast.makeText(context, simpleDateFormat.format(Date(System.currentTimeMillis())), Toast.LENGTH_SHORT).show()
    }
    fun toastDate(context: Context){
        val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
        val currentDate = System.currentTimeMillis()
        val formattedDate = simpleDateFormat.format(Date(currentDate))
        Toast.makeText(context, formattedDate, Toast.LENGTH_SHORT).show()
    }
}

命令接口

interface Command {
    fun execute(context: Context)
}

命令实现

class CommandDateImpl(private val commandReceiver: CommandReceiver):Command {
    override fun execute(context: Context) {
        commandReceiver.toastDate(context)
    }
}
class CommandTimeImpl(private val commandReceiver: CommandReceiver) : Command {
    override fun execute(context: Context) {
        commandReceiver.toastTime(context)
    }
}

命令调用器(我更喜欢叫管理者)

class CommandInvoker(var commandDateImpl: CommandDateImpl, var commandTimeImpl: CommandTimeImpl) {
    fun executeToastDate(context: Context) {
        commandDateImpl.execute(context)
    }

    fun executeToastTime(context: Context) {
        commandTimeImpl.execute(context)
    }
}