开宗明义一句话,我认为规划方式的中心便是“封装变化点”,用古早软件工程的言语系统说便是“低耦合,高内聚”,用糙一点的话说便是既不要重复代码,又要好扩展。
比方:
- 工厂方式的中心是解除了目标创立导致的详细依靠,由于在目标的传递过程中能够运用父类型,可是在目标创立时必定是要依靠到特定类型的;
- 桥接方式的中心是防止多维度形成的子类数量指数级的膨胀;
- 单例方式便是防止创立重复目标并使其便利分享;
- Builder方式的中心是防止初始化是结构函数参数过多;
- 常说组合优于承继,其实是由于承继其实也是一种耦合,子类与父类的耦合。而组合能够解除这种耦合
- …
这些东西吧,便是了解的人不必多说,不了解的人多说也没用。我是一个极度
不擅长回忆的人,特别年龄大了,n 年前看过的规划方式,早忘的一尘不染了。所以根本当他人扯到规划方式的时分,我一般都敬而远之,由于许多时分他人说一个规划方式的时分,我都不记得这个规划方式到底是干嘛用的了。这儿还刚妄言规划方式,纯粹是想表述一下我对规划的了解与实践,话不多说,直接 show code。
举例
以 Android 中的列表举例,咱们能够先把元素列出,看看哪些属于模版代码
- url
- 回来的数据结构
- 网络恳求结构
- 列表展现的 UI
- 分页逻辑
- 下拉改写
- 网络恳求失利展现错误提示
- 列表条目点击事情处理
暂时只列这几个比较公共的逻辑,咱们能够挨个分析一下这些元素哪些是“公共”的,哪些是独有的
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%以上的事故当成笑话讲,我也亲眼见过只做营销活动而一点不关心产品的事业部总监。其实我想说,程式化对应职工的公司,必定也会收成程式化应对的职工,这便是你欺骗我,我欺骗你
,最终双输的局面,这其实是我离职这么多家公司的最中心原因。
才干问题
程序员更应该增加的对产品和事务的感知才干,产品、迭代流程、管理,其实都是可重构的,中心从来都不是规划方式,而是找到抱负完整形状
并落实。只要锻炼审美才干,才干知道代码的丑、产品的丑以及管理的丑。
关于二者的处理方案其实很简单,便是人为本
。公司中其实是职工占主体,但管理层是大脑。管理层树立正向循环机制,逐步剔除混日子职工。你要是还问我怎样树立正向循环
机制,那你是没了解人为本
。
闲庭漫笔,大话闲谈,鄙俚浅陋,诸君勿怪。