该设计模式讲解针对于一个游戏公司的场景,并根据场景的游戏功能的添加来引出策略模式的使用及其为什么要使用策略模式。
一、问题引出
场景:有一家游戏公司,制作了一款鸭子游戏,在这个游戏中,很多角色都是鸭子,不同鸭子之间有共性,为了提高代码的重用性,开发人员就制作了一个父类Duck,将这些鸭子的共性提到父类中
abstract class Duck{
public void quack(){
System.out.println("嘎嘎");
}
public void swim(){
System.out.println("游泳");
}
//不同的鸭子叫声相同,也都会游泳,故父类做出实现,但是外观不一样,所以需要写成抽象类
public abstract void display();
}
//野鸭
class MallarDuck extends Duck{
@Override
public void display() {
System.out.println("外观是野鸭");
}
}
//红头鸭
class RadHeardDuck extends Duck{
@Override
public void display() {
System.out.println("外观是红头鸭");
}
}
//=============================================================
//时光线,上面是源码,我们不能修改,下面是我们的代码
public class AppTest {
public static void main(String[] args) {
//野鸭的实现
MallarDuck mallarDuck = new MallarDuck();
mallarDuck.quack();
mallarDuck.swim();
mallarDuck.display();
RadHeardDuck radHeardDuck = new RadHeardDuck();
radHeardDuck.quack();
radHeardDuck.swim();
radHeardDuck.display();
}
}
类图分析:
分析性:就目前而言,还没有出现任何问题,请看第二点。
二、添加功能
项目改进:现在游戏公司的老总们开会得出一个提高本公司提升游戏竞争力的方案,要求让游戏中的鸭子能够飞起来,把其他竞争者远远甩在身后。
那么我们就会想到:是时候展现我们面向对象程序员的魅力了,我们只需要在父类中添加一个fly方法,那么多有duck的子类也都具备了fly方法。
此时,看似问题解决了,但是出现了更麻烦的问题,所有Duck的子类都会飞了,,要知道,不是所有鸭子都会飞的,比如:橡皮鸭不会飞结果因为橡皮鸭继承了Duck,搞得橡皮鸭也会飞了。
abstract class Duck{
public void quack(){
System.out.println("嘎嘎");
}
public void swim(){
System.out.println("游泳");
}
public void fly(){
System.out.println("我飞....");
}
//不同的鸭子叫声相同,也都会游泳,故父类做出实现,但是外观不一样,所以需要写成抽象类
public abstract void display();
}
//野鸭
class MallarDuck extends Duck{
@Override
public void display() {
System.out.println("外观是野鸭");
}
}
//红头鸭
class RadHeardDuck extends Duck{
@Override
public void display() {
System.out.println("外观是红头鸭");
}
}
//橡皮鸭
class RubberDuck extends Duck {
//因为橡皮鸭不会像真实的鸭子一样叫,故模拟了一下橡皮鸭的叫声
@Override
public void quack() {
System.out.println("吱吱叫.....");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
//因为橡皮鸭不会飞,却又继承了duck中的傅里叶方法,所以我们像重写quack一样,
//去重写fly方法
@Override
public void fly() {
System.out.println("you can you up......(橡皮鸭最终没有飞起来)");
}
}
//=============================================================
//时光线,上面是源码,我们不能修改,下面是我们的代码
public class AppTest {
public static void main(String[] args) {
//橡皮鸭
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.fly();//橡皮鸭不应该会飞
rubberDuck.display();
}
}
改进类图:
看起来,问题好像解决了,但是并没有。问题是,变化不断的出现,一会加个木头鸭子,一会加个鸭子超人,一会加个怪鸭伯爵。程序员就要在每次添加鸭子角色的时候都要判断新的鸭子角色会不会叫会不会飞,针对于不同得处到鸭子要有不同的理方法,这样做就很麻烦,我们只不过从一个噩梦进入另一个噩梦。
三、继续改进
针对于二中的问题,程序员需要判断每个鸭子类,谁会不会叫,谁会不会飞,不会叫的,就重写quack方法,不会飞的,就重写fly方法,这个工作量是很大的,很麻烦。
我们希望那些不会飞的鸭子压根就没有fly方法,不会叫的鸭子压根就没有quack方法。
改进方式: 把这两个在子类中经常变化的方法,把他从父类中拆分为两个接口:Quackable,Flyable
interface Flyable {
void fly();
}
interface Quackable{
void quack();
}
abstract class Duck{
public void swim(){
System.out.println("游泳");
}
//不同的鸭子叫声相同,也都会游泳,故父类做出实现,但是外观不一样,所以需要写成抽象类
public abstract void display();
}
//野鸭
class MallarDuck extends Duck implements Flyable,Quackable{
@Override
public void display() {
System.out.println("外观是野鸭");
}
@Override
public void fly() {
System.out.println("野鸭在飞");
}
@Override
public void quack() {
System.out.println("野鸭嘎嘎叫");
}
}
//红头鸭
class RadHeardDuck extends Duck implements Flyable,Quackable{
@Override
public void display() {
System.out.println("外观是红头鸭");
}
@Override
public void fly() {
System.out.println("红头鸭在飞");
}
@Override
public void quack() {
System.out.println("红头鸭嘎嘎叫");
}
}
//橡皮鸭
class RubberDuck extends Duck implements Quackable {
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
@Override
public void quack() {
System.out.println("橡皮鸭吱吱叫");
}
}
//诱饵鸭
class DecoyDuck extends Duck{
@Override
public void display() {
System.out.println("诱饵鸭");
}
}
//=============================================================
//时光线,上面是源码,我们不能修改,下面是我们的代码
public class AppTest {
public static void main(String[] args) {
MallarDuck mallarDuck = new MallarDuck();
mallarDuck.quack();
mallarDuck.swim();
mallarDuck.fly();
mallarDuck.display();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.display();
}
}
设计类图:
这样问题并没有解决:
以前: 是每加入一个鸭子角色,程序员就要判断,这个鸭子是否会飞,是否会叫,不会飞就重写飞方法,不会叫的重写叫方法。
现在: 是每加入一个鸭子角色,程序员就要判断,这个鸭子是否会飞,是否会叫,不会飞就不实现Flyable接口,不会叫的就不实现Quackable接口。
缺点:
- 程序员并没有减少工作量,任然要不断的判断新加入的鸭子角色;
- fly和quack并没有重用性可言,比如48中鸭子8中不会飞,那么飞的方法就要在40个鸭子子类中重复40次。
杆点: 从JDK1.8开始,接口中开始产生默认方法,那么不就能重用了么。此时48中鸭子8种不会飞,那么只需要在Flyable中定义一次即可。
解释: 在默认方法中,只能定义一种起飞方式(也就是说只能重用一种飞行方式),那么对于48种鸭子,有十二种飞行方法(比如原地起飞,助跑起飞等等),那么飞行方法,仍然在子类里面重复。又该如何解决?
四、策略模式
针对于三中的问题,修改代码如下:
是时候,把飞行方法和叫方法从鸭子类中分离出来(注意,这里的分离,和三中的分离不一样)。
抽取形式:
类表示形式:
interface FlyBehavior{
void fly();
}
//----飞行的具体方法
class FiyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("用翅膀飞~~~~");
}
}
class FlyWithRocket implements FlyBehavior{
@Override
public void fly() {
System.out.println("背上绑着火箭飞!!!");
}
}
class FlyWithKich implements FlyBehavior{
@Override
public void fly() {
System.out.println("被人踢一脚飞。。。。。");
}
}
class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞不起来。。。。。");
}
}
interface QuackBehavior{
void quack();
}
//---叫的实现方式
class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("嘎嘎叫.....");
}
}
class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("吱吱叫。。。。。");
}
}
class MuteQuack implements QuackBehavior{
@Override
public void quack() {
System.out.println("<<Slience(静默的)>>");
}
}
//=====鸭子
abstract class Duck{
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
public void swim(){
System.out.println("游泳");
}
//不同的鸭子叫声相同,也都会游泳,故父类做出实现,但是外观不一样,所以需要写成抽象类
public abstract void display();
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
//野鸭(会飞会叫)
class MallarDuck extends Duck {
public MallarDuck() {
this.flyBehavior = new FiyWithWings();
this.quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("外观是野鸭");
}
}
//红头鸭(会飞会叫)
class RadHeardDuck extends Duck{
public RadHeardDuck() {
this.flyBehavior = new FiyWithWings();
this.quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("外观是红头鸭");
}
}
//橡皮鸭(不会飞会吱吱叫)
class RubberDuck extends Duck {
public RubberDuck() {
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new Squeak();
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
//诱饵鸭(不会飞不会叫)
class DecoyDuck extends Duck{
public DecoyDuck() {
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new MuteQuack();
}
@Override
public void display() {
System.out.println("诱饵鸭");
}
}
//=============================================================
//时光线,上面是源码,我们不能修改,下面是我们的代码
public class AppTest {
public static void main(String[] args) {
MallarDuck mallarDuck = new MallarDuck();
mallarDuck.performFly();
mallarDuck.performQuack();
mallarDuck.display();
System.out.println("========");
//还可以进行运行时替换
//本来橡皮鸭是不会飞和吱吱叫,现在在运行时将他改为被人一脚踢飞
Duck rubberDuck = new RubberDuck();
System.out.print("原本:");
rubberDuck.performFly();
rubberDuck.setFlyBehavior(new FlyWithKich());
System.out.print("在运行时修改为:");
rubberDuck.performFly();
}
}
策略模式类图:
此时针对于48种鸭子,有12种飞行方法而言,每种飞行方法,只写一次。
这就是传说的策略模式。