①从继承中解脱-策略模式

继承真的合理么

想象一下有一个汽车类,你用它创造了很多汽车,他们都跑在虚拟游戏世界里面。

1
2
3
4
5
6
7
8
9
10
11
class CarSuper {
public run() {
console.log("快快跑");
}
public blow() {
console.log("滴滴叫");
}
}

const benz = new CarSuper();
const bmw = new CarSuper();

但是并不是所有的汽车都是 快快跑 ,它们都有自己的极限速度,所以你很容易想到应该有一个子类,让子类去实现特有方法。

1
2
3
4
5
6
class Benz extends CarSuper {
public run() {
console.log("飞速跑");
}
}
const benz = new Benz();

现在你已经在继承中体会到了好处,突然有一天,你觉得创造出来的汽车应该可以载人,这正是你擅长的东西,所以不假思索的在 CarSuper类上添加了 carrying 公用方法,并开心的睡觉去了。

1
2
3
4
5
6
7
8
9
class CarSuper {
public run() {
console.log("快快跑");
}
public blow() {
console.log("滴滴叫");
}
public carrying() {}
}

第二天起来,你不敢相信自己的眼睛,一位乘客上了一辆玩具车,所以你赶紧关停了游戏服务。并紧急的思考对策,显然载人的行为并不通用,每一个不能载人的车,都需要重写载人的方法,例如玩具车。

其实你还有一个想法,既然载人的方法不通用,不如把类似载人这样的功能看作是一个接口,每一个子类都需要实现这个接口

1
2
3
4
5
6
7
8
9
10
11
12
interface Carrying {
carrying: () => void;
}

class Benz extends CarSuper implements Carrying {
public run() {
console.log("飞速跑");
}
public carrying() {
console.log("拉商人");
}
}

这显然不是一个好办法,接口不具有实现代码,虽然子类自己实现增加了灵活性,但却使子类显的非常冗余,每一个子类后面都跟着一串各不相同且需要独立实现的接口。

为了复用而是用继承可能是导致难以维护
比如上面的玩具车可以载人,是因为没有重写子类载人的方法。

找到变化之处

类的行为在不断改变,所以一旦把这个行为变成类的一部分,就需要大量的经历去跟踪这些行为会在那里造成影响,有一个原则能帮助我们 ❤‍🔥 把变化的内容独立出来,不要和稳定的代码混在一起

现在就可以着手于将载人的行为分离出来,那么这个行为应该以哪种形式存在呢,方法,类,还是对象? 其实一谈到设计模式绕不开的就是 OO(面向对象设计模式),所以我们还是采用 OO 的思想,使用类来实现这个行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface CarryingBehavior {
carrying: () => void;
}

class BusinessCarrying implements CarryingBehavior {
public carrying() {
console.log("载商人");
}
}

class ToyCarrying implements CarryingBehavior {
public carrying() {
console.log("载玩具");
}
}

class CarSuper {
protected carryingBehavior: CarryingBehavior;
// 提供初始化时的参数,以提供默认的行为
constructor({ carryingBehavior }: { carryingBehavior: CarryingBehavior }) {
this.carryingBehavior = carryingBehavior;
}
public run() {}
public blow() {}
public carrying() {
this.carryingBehavior.carrying();
}
// 增加set方法是为了可以在运行时改变对象的行为
public setCarryingBehavior(behavior: CarryingBehavior) {
this.carryingBehavior = behavior;
}
}

class Benz extends CarSuper {
constructor() {
super({
carryingBehavior: new BusinessCarrying(),
});
}
public run() {
console.log("飞速跑");
}
}
const benz = new Benz();
benz.carrying();
benz.setCarryingBehavior(new ToyCarrying());
benz.carrying();

我们将汽车的行为抽象为 BusinessCarrying 类,抽象为类和抽象为接口的区别就是分离了实现

BusinessCarrying 类有自己的行为类接口,可就保证了行为类的灵活性,为相同的行为实现不同的效果,例如汽车载人坐在前排还是后排。

通过上面的改造,只需要让汽车类提供一个能设置行为的方法,就可以实现行为的动态化,这也是另一个设计原则 ❤‍🔥 针对接口,而不是实现编程

这样的话和私有方法有什么区别? 行为是可抽象的,是可以被穷举的,他会动态的散布在各种各样的汽车子类中。而私有方法是无法被穷举的,一旦定义一个新的子类,那么他就有自己的私有方法和私有属性。

现在我们分离了一种行为类,在共有类中被声明但不会实现,而是在子类中实例化这个行为类,这其中的原则就是 ❤‍🔥 多用组合,少用继承, 巧合的是 React 哲学中也提到了这样的思想。

最后用官方语言定义策略模式: 定义算法族,分别封装起来,让他们之间可以相互替换,让算法的变换独立于使用算法的客户

  • 知道 OO 基础,并不足以让你设计出良好的 OO 系统。
  • 良好的 OO 设计必须具备可复用、可扩充、可维护三个特性。
  • 模式可以让我们建造出具有良好 OO 设计质量的系统。
  • 模式被认为是历经验证的 O0 设计经验。
  • 模式不是代码,而是针对设计问题的通用解决方案。你可把它们应用到特定的应用中。
  • 模式不是被发明,而是被发现。
  • 大多数的模式和原则,都着眼于软件变化的主题。
  • 大多数的模式都允许系统局部改变独立于其他部分。
  • 我们常把系统中会变化的部分抽出来封装。
  • 模式让开发人员之间有共享的语言,能够最大化沟通的价值。
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信