策略模式

问题引入

有各种鸭子(如:野鸭、北京鸭),鸭子有各种行为(如:叫、飞和游泳),现在要求写一个程序将各种鸭子以及它们的行为做一个管理,并显示鸭子的信息

传统解决方案

写一个 Duck 类(可以是抽象类,也可以不是,具体看需求)

public abstract class Duck {

	public abstract void display(); //显示鸭子信息
	
	public void quack() {
		System.out.println("鸭子嘎嘎叫~~");
	}
	
	public void swim() {
		System.out.println("鸭子会游泳~~");
	}
	
	public void fly() {
		System.out.println("鸭子会飞翔~~~");
	}
}

再写一个 WildDuck 类继承 Duck 类

public class WildDuck extends Duck {

	@Override
	public void display() {
		System.out.println(" 这是野鸭 ");
	}
}

再写一个 PekingDuck 类继承 Duck 类

public class PekingDuck extends Duck {

	@Override
	public void display() {
		System.out.println("~~北京鸭~~~");
	}
	
	//因为北京鸭不能飞翔,因此需要重写fly
	@Override
	public void fly() {
		System.out.println("北京鸭不能飞翔");
	}
}

从上面的代码可以看出,为了解决北京鸭不能飞这个问题,我们必须在 PekingDuck 类中重写 fly() 方法

假如现在又有一种玩具鸭,它不具备飞和游泳的行为,那么我们就又要重写 fly() 和 swim() 方法

问题分析

上面谈到的问题,其实是由于继承带来的,主要有两个部分:

  1. 子类的行为如果和父类不一致,就需要在子类中重写父类的方法
  2. 一旦父类 Duck 发生改变,则该类的所有子类都要发生改变

为了解决这样的问题,我们可以使用策略模式

策略模式

对于概念的解释,这里不写那样让人看不懂的定义,仅结合本例和自己的理解来阐述,相信在看懂我讲的东西之后,再去看那些官方的定义,一定会有所收获

策略模式就是将我们在前面写的属于父类 Duck 的每一个行为都单独抽取出来,做成一个个的接口(策略接口),然后针对每一个接口(每一种行为)去写其实现类(具体策略类)

针对飞这种行为举例如下

把飞(fly)这种行为单独抽取出来,写一个名为 FlyBehavior 的接口(飞的接口,也即策略接口)

public interface FlyBehavior {
	void fly(); // 让实现类去具体实现
}

因为不同种类的鸭子,飞的行为可能不一样,所以我们需要写一个或多个实现类来实现 FlyBehavior 接口,以实现不同的飞的行为

比如有的鸭子不会飞,我们就写一个名为 NoFlyBehavior 的类来实现 FlyBehavior 接口

NoFlyBehavior 是飞的实现类,也即具体的策略类,放在本例来讲,就是用于阐述如何飞(使用哪种策略飞)

public class NoFlyBehavior implements FlyBehavior{

	@Override
	public void fly() {
		System.out.println("不会飞");
	}
}

再比如有的鸭子飞得比较差,我们就再写一个名为 BadFlyBehavior 的类来实现 FlyBehavior 接口

public class BadFlyBehavior implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("飞的技术比较差");
	}
}

再比如有的鸭子很会飞,我们就再写一个名为 GoodFlyBehavior 的类来实现 FlyBehavior 接口

public class GoodFlyBehavior implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("很会飞");
	}
}

接着我们再写一个名为 WildDuck 的类(相当于有些同学在其它文章中看到的 Context 类,这个类到底需不需要是抽象类,具体看需求),将前面抽取出来的行为的接口作为该类的一个成员变量(组合),如果有多个接口(叫或游泳),就在 WildDuck 类中写多个就行了

至于具体使用哪个实现类,我们可以在构造函数中指定

public class WildDuck {

	// 属性(策略接口),如果还需要其它属性,接着添加即可
	private FlyBehavior flyBehavior;
	
	// 初始化时,传入具体的策略对象
	public WildDuck(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	// 显示鸭子信息
	public void display() {
		System.out.println("野鸭");
	}

	public void executeFly() {
		// 根据具体的策略对象,调用该策略对象的方法 
		flyBehavior.fly();
	}
}

最后就是测试类的编写了

public class Test {
	public static void main(String[] args) {
	
		WildDuck wildDuck = new WildDuck(new GoodFlyBehavior());
		wildDuck.display(); // 野鸭
		wildDuck.executeFly(); // 很会飞
	}
}

类结构安排如下(不一定非得这样,个人认为这样比较清晰)
在这里插入图片描述
其实做完上面这些,策略模式基本就讲清楚了,但是为了让同学们更直观地感受到策略模式相比继承的优势,我们现在加点需求

现在要求给野鸭加上一个“叫”的行为,那我们应该如何做呢?

  1. 写一个“叫”这种行为的接口,取名为 QuackBehavior

    public interface QuackBehavior {
        void quack(); // 让实现类去实现
    }
    
  2. 写一个名为 LoudQuackBehavior 的类(怎么叫?大声叫)

    public class LoudQuackBehavior implements QuackBehavior {
    
        @Override
        public void quack() {
            System.out.println("大声叫");
        }
    }
    
  3. 在之前写的 WildDuck 类中添加 QuackBehavior 作为成员变量,并添加一个执行 quack() 函数的函数,同时还要添加一个重载的构造函数,更改后的代码如下

    public class WildDuck {
        // 属性(策略接口),如果还需要其它属性,接着添加即可
        private FlyBehavior flyBehavior;
        private QuackBehavior quackBehavior; // 新加的
    
        public WildDuck(FlyBehavior flyBehavior) {
            this.flyBehavior = flyBehavior;
        }
    
        // 新加的
        public WildDuck(QuackBehavior quackBehavior) {
            this.quackBehavior = quackBehavior;
        }
    
        // 显示鸭子信息
        public void display() {
            System.out.println("野鸭");
        }
    
        public void executeFly() {
            flyBehavior.fly();
        }
        
        // 新加的
        public void executeQuack() {
            quackBehavior.quack();
        }
    }
    

测试类的写法如下

public class Test {
    public static void main(String[] args) {
		WildDuck wildDuck = new WildDuck(new LoudQuackBehavior());
        wildDuck.display(); // 野鸭
        wildDuck.executeQuack(); // 大声叫
    }
}

在这里插入图片描述
假如现在还有一个需求:加一种玩具鸭(可以叫不会飞),那我们又该如何做呢?

只要添加一个名为 ToyDuck 的类即可

public class ToyDuck {
    private QuackBehavior quackBehavior;

    public ToyDuck(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    // 显示鸭子信息
    public void display() {
        System.out.println("玩具鸭");
    }

    public void executeQuack() {
        quackBehavior.quack();
    }
}

测试类的写法如下:

public class Test {
    public static void main(String[] args) {
    
        ToyDuck toyDuck = new ToyDuck(new LoudQuackBehavior());
        toyDuck.display(); // 玩具鸭
        toyDuck.executeQuack(); // 大声叫
    }
}

在这里插入图片描述
总结

  1. 策略模式的关键在于意识到策略是变的,因此我们需要为每种策略定义一个接口,让属于该策略的各种不同的具体的策略都去实现这个接口
  2. 策略模式的核心思想是:多用聚合/组合,少用继承;用行为类组合,而不是行为的继承
  3. 策略模式体现了对修改关闭,对扩展开放的原则,客户端增加行为不用修改原有代码,只要添加一种策略(行为)即可
  4. 提供了可以替换继承关系的办法:策略模式将算法封装在独立的策略类中,使得你可以独立于 Context 改变它,使得它易于切换、扩展和理解
  5. 需要注意的是:每增加一种策略,就要多增加一些类,当策略过多会导致类数目庞大。一般来说,如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值