前言
先从简单的模拟鸭子应用做起
假设有个公司做了一套相当成功的模拟鸭子的游戏。游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫,所有鸭子的区别在于外观,比如有白色,黑色,黄色等,所以我们需要设计一个父类(也可以叫超类),然后让各种鸭子去继承这个父类。
上面这图体现出了所有鸭子都有鸭叫和游泳的方法,唯一的区别在于外观不一样,所以这时候外观需要使用抽象,然后在各个子类上实现自己的行为。
现在我们得让鸭子飞起来
有一天呢,公司内部头发风暴后产生了一个新的创意,需要有会飞的鸭子,有人肯定会说我花一下功夫就可以解决了,在鸭子类上加个会飞的方法就行了,这有什么困难?
可怕的问题发生了......
某天游戏中出现了很多“橡皮鸭子”在屏幕上飞来飞去,到底是怎么回事呢?A经理忽略了一件事,并非所有的鸭子都会飞,A直接在父类上加了个飞行的方法,导致本不应该存在的行为发生了。我们在对类处理时,尤其涉及“维护” 时,为了“复用”目的而使用继承,结局并不完美。
那该如何解决呢?
A经理想到那就从继承上来解决吧,既然橡皮鸭子不能飞,那就在橡皮鸭子的子类上去覆盖变成什么都不做,可是,如果以后加入了别的鸭子比如说木头鸭子那该怎么办呢,不会叫也不会飞,难道又要再一次覆盖吗?所以说继承也有一些缺点:
- 代码在多个子类中重复
- 运行时的行为不容易改变
- 很难知道所有鸭子的全部行为
- 改变会影响全身,造成其他鸭子不想要的改变
那利用接口如何呢?
我们知道,并非“所有”的子类都具有飞行和鸭叫的行为,所以继承并不是适当的解决方式。虽然飞行接口和鸭叫接口可以解决“一部分问题”,但是却造成代码无法复用,因为接口不具有实现代码,所以继承接口无法达到代码的复用。意味着,无论何时你需要修改某个行为的时候,你必须追踪每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!
这只能算是从一个噩梦跳到另一个噩梦里。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化....?但幸运的是,有一个设计原则,恰好适用于此状况。(终于有救了)
设计原则
重要的事说三遍!!!
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
换句话来说就是每次有新的需求时,都会使某方面的代码发生变化,那么你可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。好,接下来我们开始把鸭子的行为从鸭子类中取出的时候了!
从哪里开始呢?就我们目前所知,除了飞行和鸭叫的问题之外,鸭子类还算一切正常,似乎没有特别需要经常变化或修改的地方。所以,除了某些小改变之外,我们不打算对鸭子类做太多处理。
接下来我们准备建立两组类(完全远离鸭子的类),一个是飞行相关的的,一个是鸭叫相关的,每一组类将实现各自的动作
设计鸭子的行为
如何设计那组实现飞行和鸭叫的行为的类呢?
我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们还想指定行为到鸭子的实例,比如说,我们想产生一个新的绿头鸭实例,并指定特定类型的飞行行为给它。干脆让鸭子的行为可以动态变更好了。换句话来说,我们应该在鸭子类中包含设定行为的方法,这样就可以在运行时去动态的改变绿头鸭的飞行行为。(前面说过继承在运行时不容易动态的改变),有了这些目标要实现,接着看下第二个设计原则
重要!!! (不了解接口和实现的同学,可以自行google了解下)
针对接口编程,而不是针对实现编程!
针对接口编程,而不是针对实现编程!
针对接口编程,而不是针对实现编程!
我们利用接口代表每个行为,比方说,FlyBehavior 和 QuackBehavior,而行为的每个实现都将实现其中的一个接口。
所以这次鸭子类不会负责实现 Flying 和 Quacking 接口,反而是由我们制造一组其它类专门实现,这就称为“行为”类。由行为类而不是鸭子类来实现接口。
这样的迥异于以往,以前的做法是:行为来自鸭子父类的具体实现,或是继承某个接口并由子类自行实现而来,这两种做法都是依赖于“实现”。
在我们新的设计中,鸭子的子类将使用接口 FlyBehavior 和 QuackBehavior 表示行为,所以实现不会绑定在鸭子的类中。
两个接口具体实现
// 鸭子行为接口
interface FlyBehavior {
fly(): void
}
// 鸭子会飞实现
class FlyWithWings implements FlyBehavior {
fly() {
console.log('i am flying')
}
}
// 鸭子不会飞的接口和实现
class FlyNoWay implements FlyBehavior {
fly() {
console.log("i can't flying")
}
}
// 鸭叫接口
interface QuackBehavior {
quack(): void
}
// 嘎嘎叫
class Quack implements QuackBehavior {
quack() {
console.log('quack')
}
}
// 不叫
class MuteQuack implements QuackBehavior {
quack() {
console.log('slient')
}
}
// 吱吱叫
class Squeak implements QuackBehavior {
quack() {
console.log('squeak')
}
}
整合鸭子的行为
关键在于,鸭子现在会将飞行和鸭叫的动作委托别人处理,而不是使用定义在鸭类或者子类的鸭叫和飞行方法。
-
首先,在父类中加入两个实例的变量,分别为 flyBehavior 与 quackBehavior,那个为接口类型(而不是具体类实现类型),每个鸭子对象都会动态设置这些行为(FlyWithWings, Squeak等)。 在父类下删除 fly 和 quack 两个方法,并且用一个替代方法去替代 fly 和 quack,比如 performFly() 和 performQuack()
-
实现 performFly()
class Duck { abstract flyBehavior: FlyWithThings abstract quackBehavior: QuackBehavior public performfly() { this.flyBehavior.fly() } public performquack() { this.quackBehavior.quack() } public swim() { console.log('all swim') } public abstract display(): void }
-
将子类继承父类(也叫抽象类)
class ModelDuck extends Duck { flyBehavior = new FlyNoWay() quackBehavior = new Squeak() public display() { console.log('display') } }
动态设定行为
-
在鸭子类中,加入两个新方法
class ModelDuck extends Duck { flyBehavior = new FlyNoWay() quackBehavior = new Squeak() public display() { console.log('display') } public setflyBehavior(fb: FlyBehavior) { this.flyBehavior = fb } public setQuackBehavior(qb: QuackBehavior) { this.quackBehavior = qb } }
-
建立一个新的FlyBehavior模型
class FlyRocketPowered implements FlyBehavior { public fly() { console.log("I'm flying with a rocket! ") } }
-
加上火箭动力
var modelduck = new ModelDuck(); modelduck.performfly(); modelduck.setflyBehavior(new FlyRocketPowered()); modelduck.performfly();
总结
有一个关系相当有趣,每一个鸭子都有一个 FlyBehavior 和 一个 QuackBehavior,好将飞行和鸭叫委托给它们代为处理。
当你讲两个类结合起来使用,如同本例一般,就是组合(composition)。这种做法和继承不同的地方在于行为不是继承来的,而是和适当的行为对象“组合”来的。
这是一个很重要的技巧。其实使用了我们的第三个设计原则
重要!!!
多用组合,少用继承
多用组合,少用继承
多用组合,少用继承
如你所见,使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
最后恭喜你,学会了策略模式。
???????
源码地址:
codesandbox.io/s/typescrip…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!