【参与评论有奖】把书读薄 | 《设计模式之美》总结篇

2,574 阅读8分钟

0x1、引言

🐶 从六月开始,断断续续,算是把王争的《设计模式之美》看得差不多了,实战部分没来得及看,不过也是获益良多,思维方式上的一些变化。肚子里的墨水不多,不知道如何描述这种感觉,说两个实际的应用场景,读者自行意会哈,顺便带出总结思维导图~

image.png


日常开发应用示例一

拿到新的业务需求,不再是直接无脑拆分输出排期,复制粘贴改改代码,而是考虑更多:后续可能有哪些扩展、重复业务能否抽象复用等。举两个例子:

1、App里有不同的角色:二级代理、二级负责人、三级代理、三级负责人,然后一个列表页,不同的角色有不同的交互

如果是你用代码实现,你会怎么做?以前的我可能会这样做,把所有逻辑业务都塞到Fragment,一堆if-else

class UserInfoFragment: Fragment() {
    // 加载Fragment
    fun loading() {
        if(isLeader) {
            if (isSecond) {
                // 二级负责人
            } else {
                // 三级负责人
            }
        } else {
            if (isSecond) {
                // 二级代理
            } else {
                // 三级代理
            }
        }
    }
    
    // 添加客户
    fun addCustomer() {
        if(isLeader) {
            if (isSecond) {
                // 二级负责人
            } else {
                // 三级负责人
            }
        } else {
            if (isSecond) {
                // 二级代理
            } else {
                // 三级代理
            }
        }
    }
    
    // 还有下面这些,也是如法炮制
    
    // 搜索
    // 选择代理项目
    // 筛选
    // 排序
    // tab切换
}

写if-else写到想哭,如果再来一类角色,又得套一层if-else,改动量巨大,每个方法都要改。

你可能会说,可以简化判断,定义一个常量属性/枚举来标记一种角色,开头走下判断条件,区分出角色,就不用每次都if-else了:

class UserInfoFragment: Fragment() {
    var role = 0    // 1二级代理人、2二级负责人、3三级代理、4三级负责人
    
    fun init() {
        if(isLeader) {
            if (isSecond) {
                role = 2
            } else {
                role = 4
            }
        } else {
            if (isSecond) {
                role = 1
            } else {
                role = 3
            }
        }
    }
    
    // 加载Fragment
    fun loading() {
        when(role) {
            1 -> 二级代理
            2 -> 二级负责人
            3 -> 三级代理
            4 -> 三级负责人
        }
    }

实际上,还是逃避不了判断,当角色发生改变,依旧要动很多代码,怎么写才能保持可扩展性呢?

抽象 + 多态 + 策略模式

可以把 业务逻辑抽象成一个对象,不同的角色进入这个页面,尽管表现不同,但是执行的业务逻辑是一样的,抽象出一个行为接口:

interface IOperate {
    fun loading(container: Int)
    fun addCustomer()
    fun search(select: SelectItem? = null)
    fun selectAgentProject(select: SelectItem? = null)
    fun filter(height: Int, y: Int, select: SelectItem? = null, after: (() -> Unit)? = null)
    fun order(height: Int, y: Int, after: (() -> Unit)? = null)
    fun switchTap(pos: Int)
}

接着定义一个 抽象角色类 来实现这个接口,原则是这样的:

  • 不同角色,相同的逻辑在此重写,相同包括全部相同和部分相同。部分相同指的是部分代码逻辑相同,比如埋点,执行这个逻辑表现不同,但埋点是相同的。子类重写方法,调下super.xxx()复用相同代码,然后重写不同逻辑。
  • 完全不同的逻辑,就不在父类中实现,而是交给具体的子类实现
abstract class Role(protected var mFragment: BaseFragment) : IOperate {
    abstract var type: String   // 描述字段,便于区分而已,没用
    
    //...
    
    override fun addCustomer() {
        if (ClickFastUtils.isFastClick()) return
        mFragment.go(AddCustomerFragment::class.java)
        umAddClick.um(mFragment.context)
    }
    
    override fun filter(height: Int, y: Int, select: SelectItem?, after: (() -> Unit)?) {
        umScreenClick.um(mFragment.context)
    }
    
    //...
}

然后是 具体角色类,示例如下:

class SecondAgentRole(fg: BaseFragment) : Role(fg) {
    override var type = "二级代理/经纪人"
    
    override fun loading(container: Int) {
        mFragment.childReplace(SecondAgentListFragment.newInstance(), container)
    }

    override fun filter(height: Int, y: Int, select: SelectItem?, after: (() -> Unit)?) {
        super.filter(height, y, select, after)
        if (select == null) {
            shortToast("您当前不在任何代理项目中挂职,无法查看项目客户!")
            return
        }
        after?.invoker()
        //...其他逻辑
    }
}

接着就到Fragment了,因为 具体逻辑都分化到对应的角色 里了,所以它非常简单:

class CustomerFragment : BaseFragment() {
    override fun initData(bundle: Bundle?) {
        diffRole()?.loading(R.id.fly_content) // 区分用户角色并加载对应Fragment
    }
    
    // 区分用户角色
    private fun diffRole(): Role? = when {
        isLeader && isSecond -> SecondPrincipalRole(this)
        isLeader && !isSecond -> ThirdPrincipalRole(this)
        !isLeader && isSecond -> SecondAgentRole(this)
        !isLeader && !isSecond -> ThirdPrincipalRole(this)
        else -> null
    }
}

啧啧额,此时发生角色更改,改动的内容就非常小了:

  • 修改判断逻辑 → 只在diffRole()方法中发生改动
  • 增加角色 → 继承Role写一个角色子类,修改diffRole()方法
  • 删除角色 → 直接把角色子类干掉,修改diffRole()方法

妙啊,当时硬是要吹毛求疵的话,也有副作用,就是写多了几个类,23333。

另外还有一点要注意,设计模式不能强行硬套,还要看具体场景,比如这里,因为业务逻辑比较固定,可以这样抽,但是有些业务逻辑经常变动或者角色间逻辑差异过大的,这种玩法就不是很适合了,可能还需要进一步的抽象。


日常开发应用示例二

App里有很多列表页,布局都是 RefreshLayout套一个Recyclerview,然后逻辑都是这样的:

  • 数据初始化 (赋初值,解析各种传参,自定义Adapter初始化)
  • UI初始化 (参数异常校验错误显示错误UI、RefreshLayout设置上拉刷新和下拉刷新逻辑、RecyclerView设置)
  • 请求接口,更新UI(正常状态、异常状态)

没写一个列表页,都要重复一遍,太蠢了,一种解决方式是利用AndroidStudio模板来自动生成。

也可以像笔者一样利用:抽象 + 泛型 + 多态 + 回调 来简化偷懒,核心还是 抽取变化

  • 视图 → 列表可能显示不同的布局 → 由Adapter进行控制 → 关联不同的Bean
  • 数据 → 列表可能显示不同的数据 → 由请求进行控制 → 关联不同的Request和Response → Response中包含一个Bean的列表,变化的是这个Bean

不难抽出三个变化的类:

  • Bean → 与Adapter数据绑定,与Response里的list绑定
  • Request → 不同列表请求接口和参数不一样
  • Response → 不同列表请求接口响应结果不一样

可以定义成泛型,示例如下:

abstract class DefaultPrlRecFragment<D, Q : Serializable, S : Serializable> : BaseFragment() {
    abstract val mAdapter: BaseQuickAdapter<D, BaseViewHolder>? // 适配器
    abstract val mReq: Q    // 请求
    abstract val mObservable: Observable<Response<Bean<S>>> // 接口
    protected var mData = ArrayList<D>()  // 数据
}

子类实现时,传入对应的类型即可,接着到逻辑抽象,先是数据处理这块:

// 读取各种传入数据或进行一些准备,返回true代表正常加载、false表示出现异常
abstract fun prepareData(bundle: Bundle?): Boolean

override fun initData(bundle: Bundle?) {
    if (prepareData(bundle)) {
        // 正常显示
    } else {
        // 显示异常页面
    }
}

// 发起请求前的一些准备操作
abstract fun beforeRequest()

// 加载请求
protected fun loadRequest(isRefresh: Boolean = false) {
    // 请求处理
}

// 请求响应内容解析,提取数据
abstract fun parseData(s: S): DataEntity?

// 解析后的数据类
inner class DataEntity(
    var list: ArrayList<D>?,
    var allRows: Int,   // 总记录数
    var pageNum: Int   // 总条数
)

再接着是视图,把交互都抽到一个接口中,然后在具体的业务逻辑回调:

private var mListener: InteractiveListener? = null

// 交互接口
interface InteractiveListener {
    fun listEmptyCallback() // 列表空
    fun listNormalCallback()    // 列表正常显示
    fun listItemClickCallback(position: Int)    // 列表点击
    fun listItemLongClickCallback(view: View, position: Int)    // 列表长按
    fun onRefreshCallback() // 刷新列表
    fun onLoadMoreCallback()    // 加载更多
}

// 抽象类(提供默认实现,不用重写全部方法)
abstract class SimpleInteractiveListener : InteractiveListener {
    override fun listEmptyCallback() {}
    override fun listNormalCallback() {}
    override fun listItemClickCallback(position: Int) {}
    override fun listItemLongClickCallback(view: View, position: Int) {}
    override fun onRefreshCallback() {}
    override fun onLoadMoreCallback() {}
}

// 添加交互接口
fun addInteractiveListener(listener: InteractiveListener) {
    this.mListener = listener
}

调用示例如下:

Fragment写完,接着就到怎么去用它了,继承这个类,传入三个数据类型,重写抽象成员和方法:

接着就是 填字游戏 了,示例如下:

只需三步,即可实现一个带分页功能的Fragment,又可以偷懒摸鱼了,美滋滋~


0x2、核心总结图

上述两个例子跟笔者做所业务有较强关联,应该很难直接搬运,不过借鉴应该是可以的。当然,笔者勉强算初窥门径,肯定有更好的实现方式,算是抛砖引玉,指出学习设计模式的重要性吧。接着总结一波之前的章节(面试速成必背),具体详细内容可移步至:《把书读薄 | 设计模式之美》 阅读学习,感谢~

概念与OOP

设计原则

设计模式


0x3、小结

最近杂事缠身,除了公司日常开发迭代,还要背八股文,东奔西走面试,累,但也收获不少,挺多感悟的(技术、学习、与人交流等),等挪了坑再来总结分享一波哈~

image.png


🤩🤩🤩 掘金官方福利

🤡 前段时间申请了一波 掘金周边,本以为希望渺茫,结果上榜了,哈哈😆,送一波小礼品哇🎁 ~

抽奖方式

截止到 9月10日24点,评论区超过10人互动 (不包括我哈),评论区随机抽两只幸运儿🧒 分别送出 BlingBling✨✨掘金徽章一枚!!!

开奖时间9月12日中午12:00,到时我会联系中奖的小伙伴哈,感谢 掘金官方 对本次活动的大力支持🥰!

ouqi.gif