开宗明义一句话,我认为规划方式的中心便是“封装变化点”,用古早软件工程的言语系统说便是“低耦合,高内聚”,用糙一点的话说便是既不要重复代码,又要好扩展。

比方:

  • 工厂方式的中心是解除了目标创立导致的详细依靠,由于在目标的传递过程中能够运用父类型,可是在目标创立时必定是要依靠到特定类型的;
  • 桥接方式的中心是防止多维度形成的子类数量指数级的膨胀;
  • 单例方式便是防止创立重复目标并使其便利分享;
  • Builder方式的中心是防止初始化是结构函数参数过多;
  • 常说组合优于承继,其实是由于承继其实也是一种耦合,子类与父类的耦合。而组合能够解除这种耦合

这些东西吧,便是了解的人不必多说,不了解的人多说也没用。我是一个极度不擅长回忆的人,特别年龄大了,n 年前看过的规划方式,早忘的一尘不染了。所以根本当他人扯到规划方式的时分,我一般都敬而远之,由于许多时分他人说一个规划方式的时分,我都不记得这个规划方式到底是干嘛用的了。这儿还刚妄言规划方式,纯粹是想表述一下我对规划的了解与实践,话不多说,直接 show code。

举例

以 Android 中的列表举例,咱们能够先把元素列出,看看哪些属于模版代码

  1. url
  2. 回来的数据结构
  3. 网络恳求结构
  4. 列表展现的 UI
  5. 分页逻辑
  6. 下拉改写
  7. 网络恳求失利展现错误提示
  8. 列表条目点击事情处理

暂时只列这几个比较公共的逻辑,咱们能够挨个分析一下这些元素哪些是“公共”的,哪些是独有的

url

以 restful api 为例,格式为 ${domain}/${version}/${targetObj}?offset=${offsetNum}&limit=${limitNum}
咱们能够看到其实其间的五个参数,只要 ${targetObj} 是与本次事务相关,其他都是公共代码

  • 推荐运用 restful api
  • 客户端与服务器端在定 api 时必定要慎之又慎,能够简单了解为客户端与服务器端交互便是通过 api 的,api 规划的合理则前后端解藕。后续不论是前端重构仍是后端重构就会互不影响。假如事务耦合,那前后端动代码都要互相同步,这样的后果不必多说,便是咱们深陷泥潭,动身不得。

回来的数据结构

回来的数据如下:

{
  "errorCode": "",
  "errorMsg": "",
  "results": [
    {
      "id": "xxxxxx1",
      ...
    },
    {
      "id": "xxxxxx2",
      ...
    }
  ]
}

其间 errorCode、errorMsg、results 也都是款式代码,都能够通过 json 解析一次性处理问题,只要 results 中的数据是不同的

在 kotlin 中根本都是一行代码处理问题:

data class DataAItem(val id: String, val others: String, ...)

网络恳求结构

这个不多说,Android 端现在的最佳实践便是 retrofit + okhttp

列表展现的 UI

在 Android 中,能够简单了解为单条目的 UI 对应的其实便是 holder

class DataAItemHolder(context: Context, root: ViewGroup) : BaseViewHolder<DataAItem>(context, root, R.layout.layout_data_a_item) {
    override fun bindData(item: DataItem) {
        binding.idView.text = item.id
        binding.othersView.setContent(item.others)
        binding.idView.setOnClickListener {
            context.startActivity(...)
        }
    }
}

为了篇幅,这儿就不列 R.layout.layout_data_a_item 了,相信 Androider 都了解

分页逻辑、下拉改写、网络恳求失利展现错误提示

与 url 结合,只要其时约定的 api 是格式化的,那么这儿的分页逻辑与下拉改写其实也都是公共的,由于一切类似列表页面的方式都是相同的
至于错误展现,根本逻辑也都是公共的

不同点:

  • 部分页面不需要分页和下拉改写
  • 下拉改写依据内容不同动画作用不同
  • 网络恳求失利依据内容不同展现提示不同

咱们最终说这些问题的处理

列表条目点击事情处理

这个是依据内容不同事情是不同的,可是这部分的逻辑是能够些在 DataItemAHolder 中的,见上文中定义的 DataItemAHolder

抱负完整形状

所以一个独自的列表页面理论上的一切代码便如下:

data class DataAItem(val id: String, val others: String, ...)
class DataAItemHolder(context: Context, root: ViewGroup) : BaseViewHolder<DataAItem>(context, root, R.layout.layout_data_a_item) { ... }
class DataAListFragment : BaseListFragment<CommonListBinding>() {
    init {
        setPageUrlTarget("${targetObj}");
        registerHolder(DataAItemHolder::class.java)
    }
}
// 假如用注解方式,则更简练
@Endpoint("targetObj")
@Holder(DataAItemHolder::class.java)
class DataAListFragment : BaseListFragment<CommonListBinding>() {}

还有一个 layout_data_a_item.xml

一切变化点都在代码里了,以这种方式去完成一个列表,就只要 layout_data_a_item.xml 会略微费点时刻,一共加起来也不会超越 1 小时,并且逻辑清晰、代码简练、便于保护。

不需要 adapter,不需要 LayoutManager,不需要 ItemDecoration,甚至,这个 DataAListFragment.kt 都是模版代码,既然是模版代码,那就能够动态生成。
哪怕上述代码只能够替代 50% 的真实列表需求,其实都是极大的劳动力的解放。

至于为什么是抱负完整形状,是由于我也没有彻底完成上述逻辑,首要是以往写 sdk 居多,少写 UI,以上逻辑都是在我大约五六年前写过的一个结构的基础上优化而来。

问题

上边看着舒服,可是其实问题仍是许多的。假如一切逻辑都往 base 或者 common 中塞,不必我多说,咱们也知道是废物规划
咱们做到了不要重复代码,那好扩展怎样办呢?
像上边 分页逻辑、下拉改写、网络恳求失利展现错误提示 中所述的不同点,还有其他的:

  • 部分页面不需要分页和下拉改写
  • 下拉改写依据内容不同动画作用不同
  • 网络恳求失利依据内容不同展现提示不同
  • 自定义 LayoutManager
  • 自定义 ItemDecoration
  • 自定义 adapter
  • DataAItemHolder 怎样创立,即 registerHolder 到底怎样完成(在一个模版代码中创立详细类)
  • 支撑数据缓存

咱们拿其间的几个举例:

分页开关

BaseBindingFragment 中:

fun enablePaging(): Boolean {
    return true
}

这便是简单的模版方式。假如 DataAListFragment 是动态生成的,那能够运用 Builder 方式。

Holder 怎样创立

这儿其实出现了反向依靠。正常来讲,假如要创立详细的 DataAItemHolder,那么模版代码必定要依靠 DataAItemHolder,不然无法调用结构函数。
这儿处理方案是固定结构函数:

  class DataAItemHolder(context: Context, root: ViewGroup) : BaseViewHolder<DataAItem>(context, root, R.layout.layout_data_a_item) {}

即一切 holder 的结构函数都是固定的 context: Context, root: ViewGroup,那能够在 Adapter 中

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder<T> {
    holderTypeMap[viewType]?.let {
        val holder = it.getConstructor(Context::class.java, ViewGroup::class.java).newInstance(parent.context, parent)
        holder.setOnClickListener(viewHolderClickListener)
        return holder
    }
    throw IllegalArgumentException("The type :$viewType create exception")
}

通过这种方式创立,这儿的 holderTypeMap 能够了解成 DataAItem 的缓存,即去看一下是否已经注册过该 Holder 了,由于 DataAItem 与 DataAItemHolder 是 1v1 绑定的关系。

当然还有其他处理方案,不过我个人目前感觉这应该算是比较好的。

其他

其他问题怎样处理咱们能够自己想办法,这儿不做赘述。

大总结

以上运用了哪些规划方式?我也不知道。其实只要知道了 抱负完整形状,那剩下的便是想办法去处理详细细节问题了,这些细节工作量占 80%,可是从规划角度讲大约只占 20%。不论怎样搞,只要能完成既不要重复代码,又要好扩展的目标,其实就不必管啥规划方式了。重意不重形。

番外篇

我这些年大多是做 sdk 开发,呆过的公司不少,亲眼见证了不少公司从 native -> h5 -> RN -> native -> flutter(or kmm) 的技能道路修改,五味杂陈。变得仅仅技能道路,写代码的仍然仍是3年+ 初级工程师
再一个比如,前公司为了增效专门聘请了一个敏捷教练,这其实与换技能道路千篇一律。这就像是拿着规划方式往里套,套不进去再换一个。

大部分人不是极力去处理问题,而是把时光和精力花在绕过问题上。换技能道路并不能处理产品逻辑问题,也不能处理3年+ 初级工程师的问题。这两个问题才是中心。
产品逻辑问题由人去处理,3年+ 初级工程师的问题也是人的问题。而人的问题要从处理,而不应该从外部下手。所谓的无非也是两个,一是才干,二是责任心

责任心问题

正常开发中,必定是前后端协同、rd pm 协同、产研与运营销售等部门协同,我一向认为好的事务(产品的抱负完整形状)必定能够引导出代码上的合理架构。假如代码架构杂乱无章,原因无非两个,一是产品逻辑问题,二是程序员才干问题。这种通过代码规划过程中体现出的问题,绝大多数都能够追溯到产品逻辑上。这是产品优化的及其重要的一条途径,可是惋惜的大多数时分,这条途径名存实亡。原因无非也是两个,一是程序员的责任心问题、二是 pm 的责任心问题,大多数人本着能少一事就少一事的准则混饭吃,放任不合理的产品规划,自己也写不负责任的代码。当然万方有罪,罪在朕躬

我是亲眼见过运维的同学在月度总结会上把影响公司营收10%以上的事故当成笑话讲,我也亲眼见过只做营销活动而一点不关心产品的事业部总监。其实我想说,程式化对应职工的公司,必定也会收成程式化应对的职工,这便是你欺骗我,我欺骗你,最终双输的局面,这其实是我离职这么多家公司的最中心原因。

才干问题

程序员更应该增加的对产品和事务的感知才干,产品、迭代流程、管理,其实都是可重构的,中心从来都不是规划方式,而是找到抱负完整形状并落实。只要锻炼审美才干,才干知道代码的丑、产品的丑以及管理的丑。

关于二者的处理方案其实很简单,便是人为本。公司中其实是职工占主体,但管理层是大脑。管理层树立正向循环机制,逐步剔除混日子职工。你要是还问我怎样树立正向循环机制,那你是没了解人为本

闲庭漫笔,大话闲谈,鄙俚浅陋,诸君勿怪。