设计模式之策略模式

文章通过一个模拟鸭子游戏的场景,展示了在软件设计中遇到的扩展问题,即通过继承来添加新行为导致的代码耦合和不易维护性。策略模式被引入作为解决方案,它提倡将算法族封装为独立的类,允许在运行时动态选择和切换,从而实现行为的变化而不影响主体结构。
摘要由CSDN通过智能技术生成

1、场景推导

King上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。

游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。

此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类,并让各种鸭子继承此超类

我们看一下UML 类图

在这里插入图片描述

abstract class Duck {
    public void quack() {
        System.out.println("呱呱叫");
    }
    public void swim() {
        System.out.println("游泳");
    }
    // 因为每一种鸭子的外观都不同,所以display()方法是抽象的
    public abstract void display();
}

// 野鸭子
class MallardDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观是绿头");
    }
}
//红头鸭
class RedHeadDuck extends Duck {
    @Override
    public void display() {
        System.out.println("外观是红头");
    }
}


public class StrategyApp{
    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        duck.quack();
        duck.swim();
        duck.display();

        System.out.println("=========================================");

        duck = new RedHeadDuck();
        duck.quack();
        duck.swim();
        duck.display();
        /**
         * 呱呱叫
         * 游泳
         * 外观是绿头
         * =========================================
         * 呱呱叫
         * 游泳
         * 外观是红头
         */
    }
}

程序没啥毛病,过了一段时间…

主管们决定,此模拟程序需要会飞的鸭子来将竞争者抛在后头。当然,在这个时候,King的经理拍胸脯告诉主管们,

King只需要一个星期就可以搞定。“毕竟,King是一个OO程序员…这有什么困难??”

King认为,只需要在Duck类中加上fly()方法,然后所有鸭子都会继承fly()。

“这是我大显身手,展示OO才华的时候了!” King上来就是梭哈搞定,打完收工~

abstract class Duck {
   public void quack() {
      System.out.println("呱呱叫");
   }
   public void swim() {
      System.out.println("游泳");
   }
   public void fly() {
      System.out.println("真的飞起来了!");
   }
   // 因为每一种鸭子的外观都不同,所以display()方法是抽象的
   public abstract void display();
}
//野鸭子
class MallardDuck extends Duck {
   @Override
   public void display() {
      System.out.println("外观是绿头");
   }
}
//红头鸭
class RedHeadDuck extends Duck {
   @Override
   public void display() {
      System.out.println("外观是红头");
   }
}

//橡皮鸭
class RubberDuck extends Duck {
   // 橡皮鸭子不会呱呱叫,所以把quack()的定义覆盖成“吱吱叫”
   @Override
   public void quack() {
      System.out.println("吱吱叫");
   }
   @Override
   public void display() {
      System.out.println("外观是橡皮鸭");
   }
}

public class StrategyApp {
   public static void main(String[] args) {
      //向上转型
      Duck duck = new MallardDuck();
      duck.quack();
      duck.swim();
      duck.fly();
      duck.display();

      System.out.println("=========================================");

      duck = new RedHeadDuck();
      duck.quack();
      duck.swim();
      duck.fly();
      duck.display();

      System.out.println("=========================================");

      duck = new RubberDuck();
      duck.quack();
      duck.swim();
      duck.fly(); // 橡皮鸭飞起来了!?
      duck.display();

   }
    /**
	 * 呱呱叫
	 * 游泳
	 * 真的飞起来了!
	 * 外观是绿头
	 * =========================================
	 * 呱呱叫
	 * 游泳
	 * 真的飞起来了!
	 * 外观是红头
	 * =========================================
	 * 吱吱叫
	 * 游泳
	 * 真的飞起来了!   //橡皮鸭飞起来了,这合理吗?
	 * 外观是橡皮鸭
	 *
	 * 进程已结束,退出代码0
	 */
}

问题似乎解决了

噢? 是吗?真的是这样吗?

King没有想到,在众多的Duck子类中,有了一个RubberDuck(橡皮鸭)

King忽略了一件事,并非Duck的所有子类都会飞。King在Duck超类中加上新的行为, 会使得某些并不适合该行为的子类也具有该行为。

现在可好了!SimUDuck程序中有了 一个无生命的会飞的东西。

在这里插入图片描述

针对于上面的问题,King的解决方法是这样的:我可以把橡皮鸭类中的fly()方法覆盖掉,就好像覆盖quack()的做法一样

abstract class Duck {
   public void quack() {
      System.out.println("呱呱叫");
   }
   public void swim() {
      System.out.println("游泳");
   }
   public void fly() {
      System.out.println("真的飞起来了!");
   }
   // 因为每一种鸭子的外观都不同,所以display()方法是抽象的
   public abstract void display();
}
//野鸭子
class MallardDuck extends Duck {
   @Override
   public void display() {
      System.out.println("外观是绿头");
   }
}
//红头鸭
class RedHeadDuck extends Duck {
   @Override
   public void display() {
      System.out.println("外观是红头");
   }
}
//橡皮鸭
class RubberDuck extends Duck {
   // 橡皮鸭子不会呱呱叫,所以把quack()的定义覆盖成“吱吱叫”
   @Override
   public void quack() {
      System.out.println("吱吱叫");
   }
   @Override
   public void display() {
      System.out.println("外观是橡皮鸭");
   }
   // 橡皮鸭子不会飞,所以把fly()覆盖为什么事都不做,或者抛出一个UnsupportedOperationException
   @Override
   public void fly() {
      throw new UnsupportedOperationException("you can you up, no can no bb!");
   }
}


public class StrategyApp {
   public static void main(String[] args) {
      Duck duck = new MallardDuck();
      duck.quack();
      duck.swim();
      duck.fly();
      duck.display();

      System.out.println("=========================================");

      duck = new RedHeadDuck();
      duck.quack();
      duck.swim();
      duck.fly();
      duck.display();

      System.out.println("=========================================");

      duck = new RubberDuck();
      duck.quack();
      duck.swim();
      duck.fly(); 
      duck.display();

   }
   /**
    * 呱呱叫
    * 游泳
    * 真的飞起来了!
    * 外观是绿头
    * =========================================
    * 呱呱叫
    * 游泳
    * 真的飞起来了!
    * 外观是红头
    * =========================================
    * 吱吱叫
    * 游泳
    * Exception in thread "main" java.lang.UnsupportedOperationException: you can you up, no can no bb!
    *     at com.hh.demo.p_strategy.c.RubberDuck.fly(Test.java:47)
    *     at com.hh.demo.p_strategy.c.Test.main(Test.java:73)
    *
    * 进程已结束,退出代码1
    */
}

这样做,橡皮鸭就不会让人意外地飞起来了! 如果客户端非要让一个橡皮鸭飞,只会收到一个运行时异常!

可是,如果以后加入木鸭(WoodenDuck),又会如何??木头假鸭,不会飞也不会叫… King只能表示要疯了!!!

class WoodenDuck extends Duck {
   @Override
   public void quack() {
      // 覆盖,变成什么事都不做
   }
   @Override
   public void display() {
      System.out.println("外观是木鸭");
   }
   @Override
   public void fly() {
      // 覆盖,变成什么事都不做
   }
}

我们来看看利用继承来提供Duck的行为,会导致:

  • 很难知道所有鸭子的全部行为。

  • 改变会牵一发而动全身,造成其他鸭子不想要的改变。

  • 更糟糕的是,每当有新鸭子 子类出现,King就要被迫检查并可能覆盖fly()或quack()方法…

这简直是无穷无尽的噩梦!

我们需要一个更清晰的方法,让“某些”(而不是全部)鸭子类型可飞或可叫

通过前面的学习,我们知道:把主要功能定义在父类中,把扩展功能定义在接口中。

从目前这个状况来看,“游泳“是鸭子的主要功能,飞”和“呱呱叫”成为了鸭子的扩展功能,因为并不是所有的鸭子都会“飞”和“呱呱叫”,但是所有的鸭子都会游泳吧,这时杠精又说了,橡皮鸭和木鸭没有生命,怎么会游泳呢?
我直接好家伙!!!

解决方案

King的解决之道是:

我可以把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来,只有会飞的鸭子才实现此接口。

同样的方式,也可以用来设计一个“Quackable”接口,因为不是所有的鸭子都会叫。

看一下UML 类图
在这里插入图片描述

abstract class Duck {
	public void swim() {
		System.out.println("游泳");
	}
	public abstract void display();
}
//飞
interface Flyable {
	public void fly();
}
//叫
interface Quackable {
	public void quack();
}
//野鸭子
class MallardDuck extends Duck implements Flyable, Quackable {
	@Override
	public void quack() {
		System.out.println("嘎嘎叫");
	}
	@Override
	public void fly() {
		System.out.println("飞起来了");
	}
	@Override
	public void display() {
		System.out.println("外观是绿头鸭");
	}
}
//红头鸭
class RedheadDuck extends Duck implements Flyable, Quackable  {
	@Override
	public void quack() {
		System.out.println("嘎嘎叫");
	}
	@Override
	public void fly() {
		System.out.println("飞起来了");
	}
	@Override
	public void display() {
		System.out.println("外观是红头鸭");
	}
}
//橡皮鸭
class RubberDuck extends Duck implements Quackable {
	@Override
	public void quack() {
		System.out.println("嘎嘎叫");
	}
	@Override
	public void display() {
		System.out.println("外观是橡皮鸭");
	}
}
//木鸭子
class WoodenDuck extends Duck {
	@Override
	public void display() {
		System.out.println("外观是木头假鸭");
	}
}

public class StrategyApp {
	public static void main(String[] args) {
		MallardDuck duck = new MallardDuck();
		duck.quack();
		duck.swim();
		duck.fly();
		duck.display();

		System.out.println("=========================================");

		RedheadDuck duck2= new RedheadDuck();
		duck2.quack();
		duck2.fly();
		duck2.swim();
		duck2.display();

		System.out.println("=========================================");

		RubberDuck duck3 = new RubberDuck();
		duck3.quack();
		
		duck3.swim();
		duck3.display();
	}
	/**
	 * 嘎嘎叫
	 * 游泳
	 * 飞起来了
	 * 外观是绿头鸭
	 * =========================================
	 * 嘎嘎叫
	 * 飞起来了
	 * 游泳
	 * 外观是红头鸭
	 * =========================================
	 * 嘎嘎叫
	 * 游泳
	 * 外观是橡皮鸭
	 *
	 * 进程已结束,退出代码0
	 */
}

这样问题解决了吗?并没有

  • 以前:每加入一种新的鸭子角色,程序员就要判断,新的鸭子是否会飞,是否会叫;不会飞就重写飞方法,不会叫就重写叫方法

  • 现在:每加入一种新的鸭子角色,程序员就要判断,新的鸭子是否会飞,是否会叫;不会飞就不实现Flyable方法,不会叫就不实现Quackable方法

程序员的工作并没有减少呀,任然要不断的判断新的鸭子角色!!!

我们知道,并非“所有”的子类都具有飞和呱呱叫的行为,所以继承并不是恰当的解决方式。

虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用(fly和quack),这只能算是从一个恶梦跳进另一个恶梦。

甚至,在会飞的鸭子中,飞行的动作可能还有多种变化…

也许你会说,自jdk1.8开始,接口有默认实现。
那么对于48种鸭子的子类,共有12种飞行的方式,又该怎么说? 接口的默认实现选择哪一种飞行方式都不行!

毁灭吧!!!!

这个时候,策略模式登场了

有一个设计原则: 封装变化原则

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起

这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法,让“系统中的某部分改变不会影响其他部分”

好,该是把鸭子的行为从Duck类中取出的时候了!

我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。

为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

在这里插入图片描述

在“针对接口编程,而不是针对实现编程”的原则指导下(注意这里所说的接口指的是超类或实际的接口),

我们定义两个接口: FlyBehavior,QuackBehavior,并且让所有的行为类都实现这两个接口中的其中之一

在这里插入图片描述

//飞行行为
interface FlyBehavior {
	void fly();
}
//用翅膀飞
class FlyWithWings implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println("I'm flying!!");
	}
}
//不会飞行
class FlyNoWay implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println("I can't fly");
	}
}
//乘火箭飞行
class FlyRockedPowered implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println("I'm flying with a rocket!");
	}
}
--------------------------------------------------------------------------------------
//叫的行为
interface QuackBehavior {
	void quack();
}
//呱呱叫
class Quack implements QuackBehavior {
	@Override
	public void quack() {
		System.out.println("Quack");
	}
}
//没有声音的叫
class MuteQuack implements QuackBehavior {
	@Override
	public void quack() {
		System.out.println("<< Silence >>");
	}
}
//吱吱叫
class Squeak implements QuackBehavior {
	@Override
	public void quack() {
		System.out.println("Squeak");
	}
}
//鸭子的抽象类
abstract class Duck {
    //组合
	protected FlyBehavior flyBehavior;
	protected QuackBehavior quackBehavior;
	
	public void setFlyBehavior(FlyBehavior fb) {
		this.flyBehavior = fb;
	}
	public void setQuackBehavior(QuackBehavior qb) {
		this.quackBehavior = qb;
	}
    
	//执行 叫
	public void performQuack() {
		quackBehavior.quack();
	}
	//执行 飞
	public void performFly() {
		flyBehavior.fly();
	}
	//游泳
	public void swim() {
		System.out.println("All ducks float, even wooden duck!");
	}
	//形态
	public abstract void display();

}
//野鸭子
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");
	}
}
//模型鸭子
class ModelDuck extends Duck {

	public ModelDuck() {
		flyBehavior = new FlyNoWay();	// 一开始,鸭模型是不会飞的
		quackBehavior = new Quack();
	}

	@Override
	public void display() {
		System.out.println("I'm a model duck");
	}

}

// ==============================================================

public class StrategyApp {
	public static void main(String[] args) {
		Duck mallard = new MallardDuck();

		mallard.performQuack();
		mallard.performFly();

		System.out.println("=====================");
		Duck model = new ModelDuck();
        
		model.performFly();
		model.performQuack();
		model.setFlyBehavior(new FlyRockedPowered());
		model.performFly();
	}
    /**
	 * Quack
	 * I'm flying!!
	 * =====================
	 * I can't fly
	 * Quack
	 * I'm flying with a rocket!
	 *
	 * 进程已结束,退出代码0
	 */
}


// ==============================================
//唐老鸭
class Tang extends Duck {

	public Tang() {
		flyBehavior = new FlyWithWings();
		quackBehavior = new MuteQuack();
	}

	@Override
	public void display() {
		System.out.println("外观是唐老鸭");
	}

}

此时针对于48种鸭子,有12种飞行方法而言,每种飞行方法,只写一次
这就是传说中的策略模式!

2、策略模式基本介绍

策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

3、策略模式UML 类图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

King Gigi.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值