前言
近期学习设计模式时尝试实现各种代码,却对什么样的代码才是更加优质的产生了疑惑。
我想了想,比较切实的有以下几点:
- 可维护性,意为好读(可读性)、好改(灵活性)、好加(扩展性)。
这通常就是我们常说的低耦合。后面详细说
- 可复用性(Reusability),如果用到相同的功能时,可以在不同上下文中重复使用。
- 简洁性(Simplicity) :避免了不必要的复杂性,遵循“Keep It Simple, Stupid”(KISS)原则。
... 其余的性能、可测试性等暂且不管。
低耦合
当一个模块依赖于另一个模块时,我们不应该要求这个被依赖的模块是如何实现对应功能的(具体),而只是希望其提供应有的功能。这在Java中通常体现为接口,也就是抽象。低耦合的本质我认为是,能为被依赖的模块提供最大限度的自由程度,这个自由通常表现为实现的自由以及扩展的自由。
1. 低依赖
耦合经常被解释为类之间的依赖,我认为这是不够恰当的。假设有一个Friend类,内部有个friends属性,用于表明自身的朋友(以Friend类的对象进行存储),我认为这同样是一种依赖。所以,应该说是对象之间的依赖,而不仅是类之间。
依赖问题通常是无法完全解决的,你总不能把一大堆代码全写到客户端中不使用任何模块。解决依赖问题通常是将依赖转移到另一个模块中,实现耦合的聚集(依赖的聚集),依赖没有消失,也无法消失,只是转移了,通常被称为解耦。
class Friend {
name: string;
friends: Set<Friend> = new Set<Friend>();
constructor(name: string) {
this.name = name;
}
add(friend: Friend) {
this.friends.add(friend);
}
showFriends() {
this.friends.forEach((e) => {
console.log(e.name);
});
}
}
function client() {
let a = new Friend("a");
let b = new Friend("b");
let c = new Friend("c");
a.add(b);
a.add(c);
a.showFriends();
}
client();
2. 减少重复
我举个简单的例子,假如现在有两个完全不同的类(基类不同),然而我希望其拥有相同的功能,即相同的完全一致的方法。假如我通过接口来约束这两个类,我需要在这两个类中实现一模一样的方法(因为我希望拥有相同功能),这就是一种重复,假如我现在要修改这个功能,我需要到这两个类中各自进行修改。
在Python等语言中,可以通过多继承来解决这种问题。Java中使用接口的默认实现。TS中使用Mixins。无论是哪种,都是一种组合/混合的思想。
3. 低约束/最少知识/单一职责
低约束,当一个类依赖于另一个类时,应当给予最低限度的约束,最大限度的自由。简单来说其实就是多态,通常表现为接口,即依赖于抽象,接口的好处就是一个接口可以有多种不同实现,表现为类名可以不同,而依赖于具体则只能是固定的类名,那么多种不同的实现就不能共存了。我们希望修改或者扩展时某个类能够尽量不影响其他类。
外部对内部的低约束,爱咋咋,能用就行。
最少知识,我更喜欢叫最少知道原则,只需要知道如何输入(参数),会输出什么(返回值)。至于被依赖的类内部究竟有什么属性,有哪些方法,都完全不关心(通过某种手段使得不暴露这些属性和方法,比如接口、抽象类继承)。
内部对外部的高约束(权限约束),能够实现功能的同时,尽量收窄外部的权限。
至于单一职责,我更喜欢叫做分离职责,至于是否单一应该是内聚的概念。(只不过如果职责多了,这个类就会更多地被依赖,从而导致集中耦合,而单一职责则是把耦合分散到其他模块去)当我们修改某个功能时,我们希望只需要修改一个类;当我们修改一个类时,我们希望是由于特定功能的变化。之所以提到单一职责,是因为如果不实现单一职责,未来有可能会使得两个模块的功能产生重复,从而耦合。
在设计时(定义时),就考虑清楚这个类应该拥有什么职责。
如何实践?
其实我也不知道,每个人写代码总有自己的风格。
学习一种理念首先应该考虑其作用,我认为各种原则本质上都是为了偷懒(偷懒是人类进步的阶梯)。我举个极端的例子,假如一段代码只需要运行一次(不需要维护),也不需要发给其他人看(不需要可读性),那设计原则统统不需要考虑,怎么快怎么来就行了,比如算法比赛。之所以要设计代码,是因为很多情况下,代码需要维护,需要被人看,需要修改。我们希望最大限度地节省时间,只不过节省的是维护者的时间。可读性是因为修改代码时需要翻看原有的代码,扩展性是因为不看代码比看代码更快。所以在我的观点中,扩展性是高于可读性的,比如使用别人的库,如果不是学习的目的,你不需要去读他的源码,只要会配置(扩展)就行了。(但是实际上很难做到完全的扩展性,所以大多数情况下,可读性显得重要)。
我写的代码未必比你多,这时要给你提建议多少有些傲慢了。