鸭子问题
1.有各种各样的鸭子,eg:野鸭,北京鸭,水鸭
2.鸭子有各种行为:叫、飞行
3.显示鸭子信息
传统方案
Duck
野鸭
北京鸭
水鸭
传统的方式实现的问题分析和解决方案
1.其它鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的
2.上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,容易引起溢出效应。
3.为了改进1问题,我们可以通过覆盖fly方法来解决 => 覆盖解决
策略模式
定义算法族,分别封装放入独立的类中,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组 合 聚合,少用继承(客户通过组合方式使用策略)
策略模式结构
1.上下文 (Context)
维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。
2.策略 (Strategy) 接口
是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。
3.具体策略 (Concrete Strategies)
实现了上下文所用算法的各种不同变体。
4.客户端 (Client)
会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。
当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。
策略模式适合应用场景
1.当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。
2.当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。
3.如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
4.当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。
实现方式
- 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。
- 声明该算法所有变体的通用策略接口。
- 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。
- 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。
- 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。
策略模式优缺点
优点:
✔️ 你可以在运行时切换对象内的算法。
✔️ 你可以将算法的实现和使用算法的代码隔离开来。
✔️ 你可以使用组合来代替继承。
✔️ 开闭原则。 你无需对上下文进行修改就能够引入新的策略。
缺点:
❌ 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
❌ 客户端必须知晓策略间的不同——它需要选择合适的策略。
❌ 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。
解决鸭子问题
代码
//飞行接口
public interface FlyBehavior {
//飞行方法
void fly();
}
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术一般");
}
}
public class GoodFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞翔技术高超");
}
}
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞翔");
}
}
public interface QuackBehavior {
void quack();
}
public class GaGaQuackBehavior implements QuackBehavior {
@Override
public void quack() {
System.out.println("Ga~Ga~");
}
}
public class NoQuackBehavior implements QuackBehavior{
@Override
public void quack() {
System.out.println("该鸭子不会叫");
}
}
public abstract class Duck {
//策略接口
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();//显示鸭子信息
public void quack(){
quackBehavior.quack();
}
public void fly(){
if (flyBehavior != null)
flyBehavior.fly();
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
//野鸭
public class WildDuck extends Duck{
public WildDuck() {
//飞行能力好,GaGa叫
flyBehavior = new GoodFlyBehavior();
quackBehavior = new GaGaQuackBehavior();
}
@Override
public void display() {
System.out.println("野鸭");
}
}
//玩具鸭
public class ToyDuck extends Duck{
public ToyDuck() {
//不会飞、不会叫
flyBehavior = new NoFlyBehavior();
quackBehavior = new NoQuackBehavior();
}
@Override
public void display() {
System.out.println("玩具鸭");
}
}
//北京鸭
public class PekingDuck extends Duck{
public PekingDuck(){
//飞行能力一般,GaGa叫
flyBehavior = new BadFlyBehavior();
quackBehavior = new GaGaQuackBehavior();
}
@Override
public void display() {
System.out.println("北京鸭");
}
}
public class Client {
public static void main(String[] args) {
Duck wildDuck = new WildDuck();
wildDuck.display();
wildDuck.fly();
wildDuck.quack();
Duck toyDuck = new ToyDuck();
toyDuck.display();
toyDuck.fly();
toyDuck.quack();
}
}
策略模式的注意事项和细节
1.策略模式的关键是: 分析项目中变化部分与不变部分
2.策略模式的核心思想是:多用组合聚合,少用继承;用行为类组合,而不是行为的继承。更有弹性
3.体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可避免了使用多重转移语句( if…else if…else)
4.提供了可以替换继承关系的办法策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
5.需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大