Android 23种设计模式:(零)面向对象的六大原则

设计模式是安卓最重要的进阶知识之一,好的代码结构必定由各种设计模式组成,而学习设计模式之前,我们要了解面向对象的六大原则

六大原则是设计模式的基础理念,这里只需要理解记忆,学习完所有的设计模式后,再回来看六大原则,应该能有很大的收获

1、单一职责原则

单一原则很简单,就是将一组相关性很高的函数、数据封装到一个类中,每个方法的职责单一

这个原则是最简单,也是最难的一点,因为方法职责单一的界定,一般需要开发者自己的经验来判断

举例说明

class A{
    // 执行第一步
    private fun methodStep1(){}

    // 执行第二步
    private fun methodStep2(){}

    // 执行第三步
    private fun methodStep3(){}

    // 真正的执行方法
    fun realMethod(){
        methodStep1()
        methodStep2()
        methodStep3()
    }
}

上面的代码就符合单一职责的规则,即每个方法都有自己的职责,我们真正调用的方法是由多个单一职责的方法组成的

有人会说好麻烦,为什么不把这三个methodStep的代码放到realMethod()里面,让这个类只有一个方法呢?

我的回答是,不建议这么做,因为如果外部只需要调用某个步骤,例如methodStep2(),在只有一个realMethod()方法的情况下,我们必然要新增一个方法,无论是从realMethod()把那一段代码提取出来作为新方法,还是直接拷贝出来作为新方法,分别会出现修改原本的代码和重复代码的情况,这两种方式都是不建议的,所以设计这个类的时候,我们就要把单一职责的方法拆分出来,以便后续

为什么我的回答是“不建议”,而不是“不允许”呢,因为我们前面说过,“方法职责单一的界定,一般需要开发者自己的经验来判断”,如果一个方法虽然内部逻辑较多,但除了这个方法以外,不会再有其他方法会调用它里面的部分逻辑了,我们可以写在这个方法内,这样其他开发者也不会被一个类里的一堆方法弄得晕头转向,这种情况就要靠开发者的经验来判断了

2、开闭原则

它的定义是:一个类应该对于扩展是开放的,但是对于修改是封闭的

我们上面讲单一职责的时候,“修改原本的代码不被建议”,因为我们无法保证在修改的过程中不会引入Bug,这样会导致项目出现风险,一个好的设计是基于扩展的,我们的老代码稳定后可能从始至终都不会做逻辑的修改,我们只会在原来的基础上做扩展,实现新的需求

举例说明

class A{
    // 执行第一步
    private fun methodStep1(){}

    // 执行第二步
    private fun methodStep2(){}

    // 执行第三步
    private fun methodStep3(){}

    // 真正的执行方法
    fun realMethod(){
        methodStep1()
        methodStep2()
        methodStep3()
    }

    // 新的扩展方法
    fun newMethod(){
        methodStep1()
        methodStep2()
        methodStep4()
    }

    // 步骤四
    private fun methodStep4(){}
}

我们新的方法newMethod()在不改变老代码逻辑的情况下,即实现了自己的功能,又不会影响原有功能,这就是开闭原则的好处,也是我们遵守单一原则的好处

3、里式替换原则 

它的定义是:所有引用基类的地方必须能透明地使用其子类对象

定义比较抽象,简单来说就是我们要多使用类的继承和多态

举个例子

// 父类
Class Parent {
    fun method1()
    fun method2()
}

// 子类 1
class ChildOne:Parent{}

// 子类 2
class ChildTwo:Parent{}

// 在方法里使用这两个类

 var mChild:Parent? = null

// 初始化并调用父类的两个方法
fun init(child:Parent){
    mChild = child
    mChild.method1()
    mChild.method2()
}
// 分别用子类一和二来实例化mChild对象
init(ChildOne())
init(ChildTwo())

var mChild:Parent

我们为什么要这样申明一个子类对象,而不是用

var mChild:ChildOne

var mChild:ChildTwo

原因其实很简单,如果我们用父类型来声明子对象,就可以使用父对象的所有子类来初始化这个对象了,而初始化后的子对象不管是任何类型,都可以调用method1()和method2(),这就是里式替换原则,即所有引用基类的地方必须能透明地使用其子类对象

那么这么做的好处是什么呢?这就要提到开闭原则了,它的定义建议我们扩展,我们上面那个

fun init(child:Parent)方法写好后,我们只需要扩展一个又一个child,就可以实现各种各样的method1()和method2(),在这个过程中,init方法所在的类甚至不需要做任何修改,这就是里式替换原则的好处

里氏替换原则,会作为一个技巧,出现在大部分设计模式里面,所以非常重要

4、依赖倒置原则

它的定义是:

(1)高层模块不应该依赖底层模块(具体实现),二者都应该依赖其抽象(抽象类或接口)
(2)抽象不应该依赖细节(废话,抽象类跟接口肯定不依赖具体的实现了)
(3)细节应该依赖于抽象(同样废话,具体实现类肯定要依赖其继承的抽象类或接口)

一句定义,两句废话,开个玩笑,2,3点主要是对第一点做出补充,这个原则看似复杂,其实学习了上面的里氏替换原则案例代码,理解起来很轻松

具体有多简单,就是说,这个原则的意思是——面向接口(抽象)编程

下面举例

// 接口一
interface A{
    fun methodA()
}

// 接口二
abstact class B{
    fun methodB()
}

// 执行的类里面
class Demo{
    var mA:A? = null
    var mB:B? = null

    fun method(){
        mA?.methodA()
        mB?.methodB()    
    }

    ...
    //省略mA和mB的初始化
}

我们可以看到,class Demo在对象声明里面声明了两个对象,接口A的实现mA和抽象类B的实现mB,method调用时根本不关心mA或者mB的具体实现,只关心他们的接口或者抽象类,这就是

高层模块不应该依赖底层模块(不关心mA和mB底层实现),二者都应该依赖其抽象(只关心他们的接口或者抽象类)

5、接口隔离原则

它的定义是:类之间的依赖关系应该建立在最小的接口上

简单来说,就是一个类不应该被强迫实现一些他们不会使用的接口,我们应该将一个复杂的接口拆分为多个小的单一职责的接口,这样类来实现时,可以根据需求,实现对应的接口

举例如下

//定义一个包含快乐行为的接口
interface Happy{
    // 吃饭
    fun eat()
    // 唱歌
    fun sing()
    // 睡觉
    fun sleep()
    // 吃粑粑
    fun eatShit()
}

// 人实现快乐行为
class Man:Happy{
    override fun eat()
    override fun sing()
    override fun sleep()
    override fun eatShit()
}

// 狗狗实现快乐行为
class Dog:Happy{
    override fun eat()
    override fun sing()
    override fun sleep()
    override fun eatShit()
}

人和狗都实现了快乐行为接口,重写了所有的快乐方法, 看上去是不是感觉哪里不对?

问题出在 sing()唱歌和eatShit()方法上,狗狗怎么会唱歌呢?人又怎么会因为吃粑粑开心呢?

那么我们改下代码

//定义一个通用的快乐行为的接口
interface Happy{
    // 吃饭
    fun eat()
    // 睡觉
    fun sleep()
}

// 定义一个人类独有的开心行为接口
interface ManHappy:Happy{
    // 唱歌
    fun sing()
}

// 定义一个狗狗独有的开心行为接口
interface DogHappy:Happy{
     // 吃粑粑
    fun eatShit()
}

// 人实现人快乐行为
class Man:ManHappy{
    override fun eat()
    override fun sing()
    override fun sleep()
}

// 狗狗实现狗快乐行为
class Dog:AnimalHappy{
    override fun eat()
    override fun eatShit()
    override fun sleep()
}

可以看到,人实现了人类快乐接口,狗狗实现了狗狗快乐接口,他们的快乐行为都是自己想要的,通过拆分接口,让各个接口职责单一,类不会被强迫实现一些他们不会使用的接口方法,这就是让类之间的依赖关系建立在了最小的接口上

6、迪米特原则

它的定义是:一个对象应该对其他的对象有最少的了解

这是最后一个原则,也很容易理解,就是说我们类之间互相调用的时候,应该暴露尽量少的方法,这种做法有什么好处?下面举例

// 定义一个人
class Man{

    // 人的所有钱
    private var totalMoney = 100

    var mRestaurant:Restaurant? = null

    // 人从餐厅获取食物,并吃掉
    fun eat(){
        totalMoney-=5
        mRestaurant?.getFood(5)
    }
}

// 定义一个餐厅
class Restaurant{

    private val mCook:Cook? = null

    // 取餐
    fun getFood(price:Int):Food?{
        getMoney(price)
        val food = makeFood()
        return food
    }

    private fun getMoney(price:Int){
    }

    // 生产食物
    private fun makeFood():Food?{
        return mCook?.make()
    }
}

可以看到,基于一个对象应该对其他的对象有最少的了解的原则,人只知道到餐厅能取餐,但并不知道餐厅厨师的信息,以及食物的制造过程,餐厅也只知道人支付的钱,并不了解这个人的信息,这就是迪米特原则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值