当前位置:网站首页>Review实战经典:2 种封装风格,你偏爱哪种?
Review实战经典:2 种封装风格,你偏爱哪种?
2022-08-01 17:41:00 【华为云】
背景交代
一直想做一个调研,到底有多少比例的 web 前端工友工作内容以 开发后台管理系统 为主。
本瓜虽然以前也做过小程序项目、H5项目,但现实仍是以开发后台管理系统为主,是个不折不扣的“后管前端er”。大部分 web 前端工友应该也相似吧,这或许是由市场需求决定的。
所以大家在后台管理系统项目中遇到的很多问题也都是相似的,代码很值得 review,本篇带来一例(真实)~
闲言少叙,开冲!!ヾ(◍°∇°◍)ノ゙
众所周知,在后台管理项目中,这样色的 table 是最常见的:
这个 table 中,主题模板有很多状态,比如:草稿 状态、已发布 状态、待审核 状态等等;
点击这些状态又进入到不同的操作页中,比如:草稿对应 新建 操作页,已发布对应 修改 操作页,待审核对应 审核 操作页等等。
在不同的操作页中意味着要请求不同的 接口、不同的接口意味着有不同的请求 参数。
你会惊讶的发现 新建页、修改页、审核页 其实都长得差不多! 大概像下图这样色的(因为它们都会列出 table 的大部分字段,不同的字段存在不同的展现,有些只读、有些可写、有些存在关联项等等):
实际项目中的 form 会比上述截图所示复杂得多。比如本瓜所在项目中,这样的操作页,有几十个字段,包括查看、编辑、状态关联等。此处仅做抽象示意处理。
基于上面的背景,我们可以想象:如果每个操作页面都是独立的,新建页有几十个表格字段、编辑页有几十个表格字段、审核页有几十个表格字段……而这些字段大部分类似,只是在部分字段上有区别或定制化的写法,那肯定不至于:有几个操作页,就复制几个页面吧?!
这样没有任何复用思维,每当需要增删改字段的时候,都要在多个页面中进行操作,这是“大聪明”做法。
于是乎,我们需要复用!!
(复用!又是复用!写业务代码最牛b的地方就是复用。高耦合、低内聚就是说如何复用。设计模式也是为了合理复用;就连写算法的时候,复用也是最牛b的,如何复用题干条件、或者复用空间来换取时间等等)
手段、方法、途径有很多,这里拎出来单讲的一点复用是:对请求参数的封装复用。
代码抽象
我们尝试将上一节的背景做伪代码的抽象(也可稍作修改在控制台调试):
如果不做任何封装,它将会是类似这样的:
// 设一个页面的全局表格对象const ruleForm = { type:"fe", time:"20200721", title:"day day up", status:"draft", weight:999, comment:"great", balabala:"我是冗余参数1", bilibilib:"我是冗余参数2" }
- 新建操作页参数
const {type,time,.title,status.weight,comment} = ruleForm // 解构得值const getCreateParam = function(){ // ...省略一系列操作 return {only: "create"}}const createParam = getCreateParam() // 得到特殊传参const param = { // 公共传参 type, time, title, status, weight, comment, // 特殊传参 createParam}createRequest(param).then(res=> ... )
- 修改操作页参数
const {type,time,.title,status.weight,comment} = ruleFormconst getEditParam1= function(){ // ... return {only1: "edit1"}}const editParam1 = getEditParam1()const getEditParam2= function(){ // ... return {only2: "edit2"}}const editParam2 = getEditParam2()const param = { // 公共传参 type, time, title, status, weight, comment, // 特殊传参 editParam1, editParam2}editRequest(param).then(res=> ... )
- 审核操作页参数
const {type,time,.title,status.weight,comment} = ruleFormconst getReviewParam1= function(){ // ... return {only1: "review1"}}const reviewParam1= getReviewParam1()const getReviewParam2= function(){ // ... return {only2: "review2"}}const reviewParam2= getReviewParam2()const getReviewParam3= function(){ // ... return {only3: "review3"}}const reviewParam3= getReviewParam3()const param = { // 公共传参 type, time, title, status, weight, comment, // 特殊传参 reviewParam1, reviewParam2, reviewParam3}reviewRequest(param).then(res => ...)
试想一下这个参数是 10 个或20 个,甚至30 个呢?
实际的业务代码中变量赋值、变形,比这个会复杂的多的多!
1. 面向对象封装
第一种,我们先用面向对象的思路来封装上述过程:
// 获取公共参数
class CommonParam { constructor(){ this.ruleForm = ruleForm } get(){ const {type,time,title,status,weight,comment} = this.ruleForm return{type,time,title,status,weight,comment} }}
// 获取创建操作的参数
class CreateParam extends CommonParam { getCreateParam(){ // ...省略一系列操作 return {only: "create"} } get(){ const para = super.get() return {...para,...this.getCreateParam()} }}let creatParam = new CreateParam().get()createRequest(creatParam).then(res=> ... )
// 获取编辑操作的参数
class EditParam extends CommonParam { getEditParam1(){ return {only1: "edit1"} } getEditParam2(){ return {only2: "edit2"} } get(){ const para = super.get() return {...para,...this.getEditParam1(),...this.getEditParam2()} }}let editParam = new EditParam().get()editRequest(editParam).then(res=> ... )
// 获取审核操作的参数
class ReviewParam extends CommonParam { getReviewParam1(){ return {only1: "review1"} } getReviewParam2(){ return {only2: "review2"} } getReviewParam3(){ return {only3: "review3"} } get(){ const para = super.get() return {...para,...this.getReviewParam1(),...this.getReviewParam2(),...this.getReviewParam3()} }}let reviewParam = new ReviewParam().get()reviewRequest(reviewParam).then(res=> ... )
有工友问,这里继承也就只继承了公共参数,有必要写 class 类吗?
本瓜觉得,如果你的代码都是面向过程的风格,那这里写成这样,多少有点突兀;但如果大量都是用类做了封装的,这里也有必要做封装。
- 代码风格保持一致;
- class 还有一个好处,代码呈现块状,声明和调用是分开的,这一点很重要;
- 当然也利于后续扩展、改造。自上而下的过程式代码写多了,会很难阅读以及 debugger。
2. 函数式编程封装
第二种,我们用函数式编程的思维去封装上述过程:
要借助我们的老朋友, compose 函数进行函数组合~
- 简易版本 compose 函数代码如下:
export const compose = function(...fns) { return function composed(result) { // 拷贝一份保存函数的数组 var list = fns.slice() while (list.length > 0) { // 将最后一个函数从列表尾部拿出并执行它 result = list.pop()(result) if (result === 'breakCompose') { // * 任一步骤 renturn "breakCompose" 可以提前结束流程; break } } return result }}
有一位工友问过 compose 过程中如何 break,以上代码来自本瓜实战项目,就能实现;compose 函数可以做很多改造,可以自己动手试试~
封装本篇实战的请求参数:
/******函数声明部分******//** 公共参数*/const step_getCommonParam = function(ruleForm){ const {type,time,title,status,weight,comment} = ruleForm return{type,time,title,status,weight,comment}}/** 创建步骤函数*/const step_getCreateParam= function(args){ // ...省略一系列操作 return {...args,only: "create"}}const step_createRequest = function(creatParam){ return createRequest(creatParam).then(res=> ... )}/** 编辑步骤函数*/const getEditParam1= function(args){ // ... return {...args,only: "edit1"}}const getEditParam2= function(args){ // ... return {...args,only: "edit2"}}const step_editRequest = function(editParam){ return editRequest(editParam).then(res=> ... )}/** 审核步骤函数*/const getReviewParam1= function(args){ return {...args,only1: "review1"}}const getReviewParam2= function(args){ return {...args,only1: "review2"}}const getReviewParam3= function(args){ return {...args,only1: "review3"}}const step_reviewRequest = function(reviewParam){ return reviewRequest(reviewParam).then(res=> ... )}/******函数调用部分******/const handleCreate = (...args) => {// 创建操作 const steps = [step_createRequest, step_getCreateParam, step_getCommonParam] // 步骤从右自左读 compose(...steps)(...args)}handleCreate(ruleForm)const handleEdit = (...args) => {// 编辑操作 const steps = [step_editRequest, step_getEditParam2,step_getEditParam1, step_getCommonParam] compose(...steps)(...args)}handleEdit(ruleForm)const handleReview = (...args) => {// 审核操作 const steps = [step_reviewRequest,step_getReviewParam3,step_getReviewParam2,step_getReviewParam1,step_getCommonParam] compose(...steps)(...args)}handleReview(ruleForm)
如何?老观众有种熟悉的味道吧?和上面的代码对比,可以明显的感受风格的不一样吧?
Review 小结
本篇带来了后台管理系统常见的接口参数的封装 2 种风格。
最开始的代码,不做任何设计,复制多份,自上而下、过程式的风格;
优化1:借助 es6 class,js 可以简单的写出 面向对象的代码风格;
优化2:用 compose 函数组合的方式,实现 函数式编程风格。
过程式、面向对象、函数式,这种代码风格的学名是什么?
答:编程范式。
编程范式的思路不仅在原生 js 可以用,在 jq 中也可用,在 vue 、react 框架等等都可以用。感受感受,其实有些框架本身的一些设计是偏向不同风格的,js 是多范式语言,jq 偏向操作型过程式、vue 无明显倾向、过程式、面向对象、函数式都行、react 有点偏向函数式……
上述是本瓜项目实战的一点 review 体会。
由于本篇部分代码是伪代码,示意参考,或不尽完善,请各位斧正。
OK,以上便是本篇分享。点赞关注评论,为好文助力
我是掘金安东尼 100 万人气前端技术博主 INFP 写作人格坚持 1000 日更文 关注我,安东尼陪你一起度过漫长编程岁月
边栏推荐
猜你喜欢
随机推荐
AIOps智能运维的领跑者擎创科技正式入驻InfoQ 写作社区!
变量交换;复合赋值;增递减运算符
opencv real-time face detection
Isometric graph neural networks shine in drug discovery
GRUB2的零日漏洞补丁现已推出
完美指南|如何使用 ODBC 进行无代理 Oracle 数据库监控?
浅谈游戏音效测试点
Bugku-Misc-贝斯手
自定义注解实现日志打印时屏蔽特定字段不打印
MySQL 45 讲 | 09 普通索引和唯一索引,应该怎么选择?
我在启牛开户安全吗?谁能告诉我开不靠谱?
sql添加索引
【二叉树】奇偶树
TCP million concurrent server optimization parameters
RecSys'22|CARCA: Cross-Attention-Aware Context and Attribute Recommendations
B005 – 基于STC8的单片机智能路灯控制系统
成都理工大学&电子科技大学|用于强化学习的域自适应状态表示对齐
XAML WPF item groupBox control
B001 - 基于STM32的智能生态鱼缸
OpenCV安装、QT、VS配置项目设置