策略模式
定义
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
似乎不太好理解
这里我们通过一个例子来讲解 策略模式
如果有这样一个需求
你的公司要求你设计一款鸭子游戏,游戏中鸭子会呈现各种形态,有的会游泳,有的会叫,有的会飞
怎么设计 ?
你可能这样
//使用标准的 oo 设计
//1.设计一个鸭子超类 Duck 类,并让各种鸭子继承这个超类
class Duck{
quack();
swim();
display();
//鸭子的其他方法
}
//2.继承
class MallardDuck{
display(){
//绿头鸭
}
}
class RedheadDuck{
display(){
//红头鸭
}
}
那么恭喜你 , 你的 java 已经入门了
新需求
游戏需要会飞的鸭子
这时你会怎么做呢?
解决方案
在鸭子超类上添加抽象方法 fly();
,每个子类继承
可怕的事情发生了 , 测试的时候发现有一只橡皮鸭子在屏幕上飞来飞去 , 可是并非所有的鸭子都会飞
当涉及 维护 时,为了复用[reuse] 而使用继承,并不是一个可行的解决方案
当然你用继承也能实现 , 比如
//把超类的fly()方法覆盖掉
class RubberDuck{
quack(){//吱吱叫};
display(//橡皮鸭);
fly(){
//覆盖,什么都不做
}
}
新的问题
新鸭子的需求提上日程 , 诱饵鸭 , 这是一种不会飞 , 也不会叫的鸭子
当然 , 问题仍然可以解决
class DecoyDuck{
quack(){
//覆盖,什么事都不做
}
display(){
//诱饵鸭
}
fly(){
//覆盖,什么都不做
}
}
看到这里 , 想必你已经知道了这种设计的缺点 ,
- 你的扩展伴随着大量的冗余代码
- 你的扩展会影响其他功能
- …
实现 implement
当发现继承不能很好的解决问题的时候 , 你会想到利用接口吗 ?
或许可以这样 ?
- 把
fly()
取出来 , 放进一个Flyable
接口中; - 同理,也可设计
Quackable
接口,因为并不是所有的鸭子都会飞,也不是所有的鸭子都会叫 - 只有拥有对应能力的鸭子才去实现对应的接口
仔细想想 , 这其实是一个超极笨的注意 , 如果 , 每个Duck
的子类都要稍微修改下飞行的行为 , 怎么办!!!
我们知道
- 并非「所有」的子类都具有飞行和呱呱叫的行为,所以继承 并不是适当的解决方式
- Flyable与Quackable可以解决「一部分」 的问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用,
- 这只能 算是从一个恶梦跳进另一个恶梦。甚至,在会飞的鸭子中,飞行的动作 可能还有多种变化…
重新设计
把问题归零
- 现在我们知道使用继承有一些缺失,因为改变鸭子的行为会 影响所有种类的鸭子,而这并不恰当。
- Flyable与Quackable 接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才 继承Flyable),
- 但是Java的接口不具有实现代码,所以继承接口无法达到代码的复用。这意味着:无论何时你需要修改 某个行为,你必须得往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误。
幸运地,有一个设计原则,正适用于此状况
分开会变的和不会变的部分
如何开始?
1.就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常,所以,我们不打算对Duck类做 太多处理。
2.为了要分开「变化和不会变化的部分」,我们准备建立两组类(完全远离Duck类), 一个是「fly」相关的,一个是「quack」相关的,每一组类将实现各自的动作
3.比方说,我 们可能有一个类实现「呱呱叫」,另一个类实现「吱吱叫」,另一个类实现「安静」。
4.我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要把这两个行为从Duck类中分开,我们将把它们自Duck类中 取出,建立一组新类代表每个行为
动态的改变鸭子的行为
我们希望一切能有弹性
毕竟,正是因为一开始的鸭子行为没有弹性,才让我们走上现在这条路。
我们还想能够「指定」行为到鸭子的实例,比方说,想要产生绿头鸭实例,并指定特定 「类型」的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,就可以在「运行时」动态地「改变」绿头鸭的飞行行为
为了达到这个目标,引入一个很重要的概念
面向接口编程 , 而不是面向实现编程
面向接口编程 , 而不是面向实现编程
面向接口编程 , 而不是面向实现编程
// 1.利用接口代表行为 比方说,FlyBehavior 与 QuackBehavior,而行为的每个实现都必须实现这些接口之一。
// 2. 所以这次鸭子类不会负责实现Flying与Quacking接口,反而是 由其他类专门实现FlyBehavior与QuackBehavior,这就称为 「行为」类。由行为类实现行为接口,而不是由Duck类实现行为 接口。
跟前面不一样的是 : 前面的两种做法都是依赖实现 , 我们被实现捆绑的死死的,没办法灵活的扩展代码,除非写更多的重复代码
在新的设计中 , 鸭子的子类将使用接口来代表行为,而实现不会绑定在鸭子的子类中,他们会在实现FlyBehavior
和QuackBehavior
的特定的实现类中
在各种设计模式中或多或少的会有 多态的参与
多态
看一下下面这段代码
Abstract class Animal{
makeSound();
}
class Dog extends Animal{
makeSound(){
bark();
}
bark(){
// 汪汪
}
}
class Cat extends Animal{
makeSound(){
meow();
}
meow(){
// 喵喵
}
}
前面所说的面向接口编程 , 并不是狭义的接口 interface
,可以理解为面向超类型编程
比如对于实际调用我们可以这样
//面向实现编程
Dog d = new Dog();
d.bark();
//面向接口编程
Animal animal = new Dog();
animal.makeSound();
这样做的好处是什么呢
- 子类型实例化的动作不再需要在代码中硬编码,而是在运行时指定
- 我们不需要知道子类具体的实现,我们只关心如何正确的调用就 ok 了
回到鸭子的问题
实现鸭子的行为
//我们会有两个接口
interface FlyBehavior{
fly();
}
interface QuackBehavior{
quack();
}
// 行为具体的实现
class FlyWithWings implements FlyBehavior{
fly(){
//实现鸭子飞
}
}
class FlyNoWay implements FlyBehavior{
fly(){
//飞不了
}
}
class Quack implements QuackBehavior{
quack(){
//鸭子呱呱叫
}
}
class Squeak implements QuackBehavior{
quack(){
//鸭子吱吱叫
}
}
class MuteQuack implements QuackBehavior{
quack(){
//不会叫
}
}
这样的设计, 可以让飞行和叫的动作被其他对象复用,因为这些行为和鸭子没关系了
我们甚至可以扩展一些行为,不会影响到现有的类,也不会影响到已经使用到飞行行为的鸭子
这样一来,有了继承复用的好处,同时避免了继承的问题
所以如果还有的新需求呢
新的需求
需要加上一个火箭飞行的动作到这个游戏中, 怎么做,基本不用多说
聚合优于实现
接下来对这个案例进行整合
- 在鸭子中加入两个接口类型的实例变量,该变量会在运行时指定对应正确的行为类型
- 以前的没用的方法移除掉
- 我们用performFly()和performQuack()取代Duck类中的fly()与quack()
class Duck{
// 接口类型变量,在运行时持有特定行为的引用
FlyBehavior flyBehavior ;
QuackBehavior quackBehavior
performQuack();
swim();
display();
performFly();
}
再比如我们实现quack
public class Duck {
//每 只 鸭 子 都 会 引 用 实 现 QuackBehavior接口的对象
QuackBehavior quackBehavior;
// 还有更多
public void performQuack() {
//不亲自处理呱呱叫行为,而是委托 给quackBehavior对象。
quackBehavior.quack();
}
}
重构一开始的需求
Duck 基类
package org.apdoer.strategy.model;
import org.apdoer.strategy.behavior.FlyBehavior;
import org.apdoer.strategy.behavior.QuackBehavior;
/**
* @author apdoer
* @version 1.0
* @date 2019/10/8 15:52
*/
public abstract class Duck {
/**
* 为行为接口类型声明两个 引用变量,所有鸭子子类 (在同一个 package)都 继承它们。
*/
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void performFly() {
//委托给行为类
flyBehavior.fly();
}
public void performQuack() {
//委托给行为类
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
MallardDuck
package org.apdoer.strategy.model;
import org.apdoer.strategy.behavior.impl.FlyWithWings;
import org.apdoer.strategy.behavior.impl.Quack;
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("I’m a real Mallard duck");
}
}
两个行为接口
package org.apdoer.strategy.behavior;
public interface FlyBehavior {
void fly();
}
package org.apdoer.strategy.behavior;
public interface QuackBehavior {
void quack();
}
对应的实现类
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can’t fly");
}
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I’m flying!!");
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Silence >>");
}
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak”");
}
}
测试类
/**
* @author apdoer
* @version 1.0
* @date 2019/10/8 16:01
*/
public class Test {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
}
可以看到,能够实现我们想要的结果
动态的设定行为
可以看到,刚才的例子中定义了一堆动态功能却没有使用到
现在我们来解决这个
在Duck 类中加入 setter方法
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
创建一个 ModelDuck
package org.apdoer.strategy.model;
import org.apdoer.strategy.behavior.impl.FlyNoWay;
import org.apdoer.strategy.behavior.impl.Quack;
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I’m a model duck");
}
}
创建一个新的行为类型
package org.apdoer.strategy.behavior.impl;
import org.apdoer.strategy.behavior.FlyBehavior;
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I’m flying with a rocket!");
}
}
在测试类中添加如下测试代码
public class Test {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
可以看到,动态改变改变行为,不是问题
封装行为的大局观
我们重构后的设计
- 鸭子继承Duck, 飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口
- 我们描述事情的方式也稍有改变。不再把鸭子的行为说成「一组行为」,我们开始把行为想成是「一族算法 ]
- has-a 和 is-a 的区别
有一个 可能比 是一个更好
行为不是通过是xxx来继承 , 而是通过有xxx来使用
多用聚合 , 少用继承
如果你看到了这里 , 那么恭喜你,你大概对策略模式有了基本的了解
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
关于设计模式,相关代码已经上传到github
点击跳转
欢迎大家加入qq群859759121
,大量免费vip学习资源,一起成长,一起进步