抽象是编写代码的基础,有效的认识代码中的抽象,对我们在项目实施中选择设计模式和架构都会有很大的帮助。
据我所知的抽象分为3种:
- 过程抽象
- 数据抽象
- 控制抽象
过程抽象
过程抽象是最常用的一种抽象,它是消除系统中重复代码头号方法。
例如,在一个App的页面上,当页面打开时,会加载数据;在App重新唤起时,会重新加载数据;当用户点击某个按钮时,会重新加载数据;
面对这种情况,我们通常会把这段代码抽象出来,将其放在一个函数中。
onLoad() {
this.loadData();
}
onShow(){
this.loadData();
}
onBuy(){
...
this.loadData();
}
这样做的好处是当数据加载逻辑有变化时,修改的地方会被限制在loadData函数中,保证了逻辑的一致性。
控制抽象
控制抽象的抽象层次比过程抽象要高,先举一个例子。
在某个场景中,图片和文字都要按照一个优先级逻辑来处理。
const img = (isConfigured(record) && img1) || (isHot(reord) && hotImg) || (isCurrent(record) && currentImg)
const text = (isConfigured(record) && '已配置') || (isHot(reord) && '热销商品') || (isCurrent(record) && '当前商品')
在上面的代码中存在重复的控制逻辑,因此对代码进行重构,得到下面的代码。
const buildPrecedence(record)=>(configHandler, hotHandler, currentHandler)=>(isConfigured(record) && configHandler()) || (isHot(reord) && hotHandler) || (isCurrent(record) && currentHandler())
const precedence = buildPrecedence(record)
const img = precedence(()=>img1, ()=>hotImg, ()=>currentImg)
const text = precedence(()=>'已配置', ()=>'热销商品', ()=>'当前商品')
有趣的是,之前的两行代码变成了现在的四行代码,这也反应出了使用控制抽象的成本。但现在,如果要改变优先级处理逻辑,就只需要修改buildPrecedence。
个人认为,控制抽象更加接近于艺术,是艺术就会有美与丑。做得好的控制抽象,整个代码就利于他人的理解和维护;做得不好的控制抽象,代码可能过了一段时间连自己都要半天才能看懂。
因此,肯定会有大咖不同意上面的重构方式,这也很正常,因为控制抽象
本身只是抽象方式的一种归类,而不是具体的实现方法。控制抽象是要去学习和创新的,这是为什么会有23中常用设计模式。
抽象带来的问题
因此,我们在实际的项目代码上可能会遇到2种典型的情况,一种是没有使用任何抽象,代码基本上就是从业务需求直接翻译过来。这种代码的好处是很容易理解,但因为同一条业务逻辑在业务流程中可能多次出现,因此按照这种思路写出来的代码也是多次重复的,因此不好维护,即一旦那条业务逻辑发生改变,就要对代码中对应的多个地方进行修改,而是否能够改完所有的地方全凭当事人的记忆,还有全文搜索的运气。
一种是使用了抽象设计,但这个抽象不够合理。注意,就算使用合理的抽象,当问题发生时,也会通过一层一层的引用去定位问题,这本身就比较麻烦。但如果这个抽象不够合理,这种麻烦的程度会大幅度加深,从而增加代码的维护难度。
我也作为万千程序员中的一员,几乎每天都在经受着抽象带来的考验。