定义
定义算法族,分别封装起来,让他们之间可以互相替换,让算法的变换独立于使用算法的用户。
应用
加入某公司已经非常成功的模拟鸭子的游戏:SimUDuck。游戏中包括各种各样的鸭子,一边呱呱的叫着,一边和小伙伴戏水玩耍着。
V1.0
在最开始的版本中是这么实现的:
但是由于竞争压力加剧,公司主管认为该是创新的时候了,决定让鸭子能飞。
V2.0
继承实现
只需在 ``Duck`` 中加上 ``fly()`` 的方法,然后所有鸭子继承 ``fly()`` ,该到我大显身手的时候了。
但是在展示的时候,好多“橡皮鸭子”在屏幕上飞来飞去,Boss怒了。。。后果很严重。
问题:并不是所有的鸭子都会飞,在父类中添加飞行的行为,会使得某些不适合该行为的子类也具有该行为。
所以程序员想到可以把橡皮鸭类中的``fly()``覆盖掉,就像覆盖``quack()``一样。可是如果以后加入诱饵鸭(decoyDuck)怎么办,诱饵鸭既不会飞,也不会叫。。。
第一种方法实现总结存在的不足:
1. 代码在多个子类中重复
2. 运行时的行为不容易改变
3. 很难知道所有鸭子的行为
4. 改变会牵一发而动全身,造成其他鸭子不想要的改变
接口实现
可以把``fly()``从父类中取出来,放到一个``Flyable``接口中。这么一来,只有会飞的鸭子才实现这个接口。同样把``quack()``也从父类中取出来,放进一个``Quackable``接口中,因为不是所有的鸭子都会叫。
BOSS: 真是一个stupid一个主意,这么一来重复的代码会特别多,对于48只鸭子都要稍微改一下飞行的行为,要重复多少代码?!
问题:我们知道,并非所有的鸭子都具备飞和呱呱叫的行为,所以用继承不是合适的方法,用接口实现可以解决一部分问题,但是却会造成代码无法复用。
策略模式实现
设计原则1
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
我们知道``Duck``类内的``fly()``和``quack()``会随着鸭子的不同而变化。为了把这两个行为从``Duck``中分开,将它们从``Duck``中取出来,建立一组新类来代表每个行为。
设计原则2
针对接口编程,而不是针对实现编程
现在,鸭子的行为已经被抽离出来放在分开的类里边(``FlyBehavior``和``QuackBehavior``),此类专门提供某种行为接口的实现。这样鸭子类就不用知道行为的实现细节。
我们利用接口代表每个行为,``FlyBehavior``和``QuackBehavior``,而行为的每个实现都将实现其中的一个接口。而``FlyBehavior``和``QuackBehavior``被称为行为类。
针对接口编程真正的意思是针对超类编程,利用多态,程序在执行时会根据实际状况执行到真正的行为。
1. 针对实现编程:
``Dog *d = [Dog new];
[d bark];``
2. 针对接口/超类型编程
```
Animal *dog = [Dog new];
animal.makeSound;
```
第二种编程的厉害之处在于在运行时才指定具体类型的对象。
鸭子的实现
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经和鸭子没有关系了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为
1. 首先,在 ``Duck`` 类里 “加入两个行为类的实例变量”,每个鸭子对象都可以动态地设置这些变量以在运行时引用正确的行为类型
2. 然后将所有子类中的``quack()``和``fly()``的方法移除,并在超类中添加两个实现飞行和呱呱叫的方法
3. performQuack的实现
```
performFly {
[self.flyBehavior fly];
}
performQuack {
[self.quackBehavior quack];
}
```
4. 设定``flyBehavior``和``quackBehavior``的实例变量
```
@interface MallardDuck : Duck
@end
@implementation MallardDuck
- (instancetype)init {
self = [super init];
if (self) {
self.flyBehavior = [FlyWithWings new];
self.quackBehavior = [Quack new];
}
return self;
}
@end
```