策略模式
- 定义
策略模式是定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化可以独立于使用算法的客户。
先不用着急理解定义,先看下面的例子
- 栗子
假设我们有一个Car类(代表所有的汽车),我们知道汽车都可以发动、加速、刹车等,而现在的汽车种类有非常非常多,不同的品牌,使用不同的燃料等等,于是,为了提高代码的复用性,我们可以将所有汽车都具有的共性封装到Car类中,不同类型的车有各自的类,他们的个性就在各自的类中实现即可。代码如下:
Car类
public class Car{
/**
* 发动
*/
public void start(){
System.out.println("发动");
}
/**
* 加速
*/
public void accele(){
System.out.println("加速");
}
/**
* 刹车
*/
public void brake(){
System.out.println("刹车");
}
}
宝马汽车类
/**
* 奔驰 继承Car类
*/
public class BWMCar extends Car{
/**
* 查看汽车品牌
*/
public void getBrand(){
System.out.println("宝马");
}
/**
* 补充燃料
*/
public void refuel(){
System.out.println("加油");
}
}
特斯拉汽车类
/**
* 特斯拉 继承Car
*/
public class TeslaCar extends Car{
/**
* 查看汽车品牌
*/
public void getBrand(){
System.out.println("特斯拉");
}
/**
* 补充燃料
*/
public void refuel(){
System.out.println("充电");
}
}
测试运行类
public class Test {
public static void main(String[] args) {
BWMCar bwm = new BWMCar();
TeslaCar tesla = new TeslaCar();
System.out.println("=======宝马=======");
bwm.start();
bwm.accele();
bwm.brake();
bwm.getBrand();
bwm.refuel();
System.out.println("========特斯拉========");
tesla.start();
tesla.accele();
tesla.brake();
tesla.getBrand();
tesla.refuel();
}
}
运行结果:
=======宝马=======
发动
加速
刹车
宝马
加油
========特斯拉========
发动
加速
刹车
特斯拉
充电
类图
对于现在来讲这样做已经可以了,但是,开发过程中从来不缺新需求的提出。假设此时比亚迪的电动车也要添加上,如果我们继续按照上卖弄继承的方式,可以得到下面比亚迪电动车类。
public class BYDCar extends Car{
/**
* 查看汽车品牌
*/
public void getBrand(){
System.out.println("比亚迪");
}
/**
* 补充燃料
*/
public void refuel(){
System.out.println("充电");
}
}
我们可以看到,BYDCar 类和TeslaCar类除了getBrand() 的方法不一样外,refuel() 方法是一模一样的,那么我们是不是可以将这个共有的方法提取出来呢?
提取到Car中显然是不可以的,因为BWMCar 同时也继承的Car类,如果将refuel()【充电】方法提取到Car中,那么所有补充燃料方式不是充电的Car的子类都需要重写refuel()方法,当子类特别多的时候,将会是非常麻烦的事情,后期如果有变动,修改起来也会令人疯狂!
这时候你可能会有这样的想法,我们可以再设计出两个子类,一个作为汽油车的父类,一个作为电动车的父类,由这两个类继承Car,得到的类图如下所示
看似这样是可以的,但是我们知道比亚迪也有传统的汽油车,那用继承该如何实现呢?每个品牌下都有很多型号的汽车,用继承全部实现可以吗?
不论是如何实现,我们从上面两次实现中都发现了继承的缺陷,显然,继承很难满足快速变化的需求,或者说继承不是最佳的实现方式!
分析上面类图可以发现,子类很多的方法都是相同的,只是具体的实现不同。是不是非常熟悉的感觉,没错,就是接口!我们接下来的解决方法就是使用接口来实现。
首先先抽离获取品牌的方法,我们知道,汽车的品牌有很多,同时每个品牌又有非常多的型号的汽车,所以品牌非常有必要抽离!
我们抽离出一个接口Brand,所有的汽车品牌都需要实现这个接口,而具体的实现都是由具体的品牌自己确定。
同理,可以抽离出燃料补充接口:
同时我们再Car类中添加这两种行为的属性,同时抽离这两种行为,并将其委托给具体的行为接口实现类去执行
完整的类图如下:
再这里使用的是组合来整合所有的行为,而不是继承,下面用代码来实现吧
Brand接口
/**
* 品牌接口
*/
public interface Brand {
void getBrand();
}
品牌实现类
/**
* 宝马品牌实现类 继承品牌接口
*/
public class BWM implements Brand{
public void getBrand(){
System.out.println("宝马");
}
}
public class Tesla implements Brand{
public void getBrand(){
System.out.println("特斯拉");
}
}
public class BYD implements Brand{
public void getBrand(){
System.out.println("比亚迪");
}
}
补充燃料接口
/**
* 补充燃料接口
*/
public interface RefuelWay {
void refuel();
}
补充燃料实现接口
/**
* 加油补充燃料实现类
*/
public class Oil implements RefuelWay{
public void refuel(){
System.out.println("加油");
}
}
public class Electricity implements RefuelWay{
public void refuel(){
System.out.println("充电");
}
}
Car实现类
public class Car{
/**
* 展示品牌行为
*/
private Brand brand;
/**
* 补充燃料方式行为
*/
private RefuelWay refuelWay;
/**
* 构造方法中设置两种行为
*/
public Car(Brand brand,RefuelWay refuelWay){
this.brand = brand;
this.refuelWay = refuelWay;
}
/**
* 展示品牌 委托给品牌具体实现类
*/
public void getBrand(){
brand.getBrand();
}
/**
* 补充燃料 委托给补充燃料的具体实现类
*/
public void refuel(){
refuelWay.refuel();
}
/**
* 发动
*/
public void start(){
System.out.println("发动");
}
/**
* 加速
*/
public void accele(){
System.out.println("加速");
}
/**
* 刹车
*/
public void brake(){
System.out.println("刹车");
}
/**
* 展示方法 方便我们测试
*/
public void display(){
this.start();
this.accele();
this.brake();
this.getBrand();
this.refuel();
}
}
测试类
public class Test {
public static void main(String[] args) {
// 创建不同的品牌
Brand bwm = new BWM();
Brand tesla = new Tesla();
Brand byd = new BYD();
// 创建不同的燃料补充方式
RefuelWay oil = new Oil();
RefuelWay electricity = new Electricity();
// 构造 宝马的汽油车
System.out.println("------------构造 宝马的汽油车--------------");
Car car1 = new Car(bwm,oil);
car1.display();
// 构造 宝马电动车
System.out.println("------------构造 宝马电动车--------------");
Car car2 = new Car(bwm, electricity);
car2.display();
// 构造 特斯拉电动车
System.out.println("------------构造 特斯拉电动车--------------");
Car car3 = new Car(tesla,electricity);
car3.display();
// 构造 比亚迪电动车
System.out.println("------------构造 比亚迪电动车--------------");
Car car4 = new Car(byd,electricity);
car4.display();
// 构造 比亚迪汽油车
System.out.println("------------构造 比亚迪汽油车--------------");
Car car5 = new Car(byd,oil);
car5.display();
}
}
输出:
------------构造 宝马的汽油车--------------
发动
加速
刹车
宝马
加油
------------构造 宝马电动车--------------
发动
加速
刹车
宝马
充电
------------构造 特斯拉电动车--------------
发动
加速
刹车
特斯拉
充电
------------构造 比亚迪电动车--------------
发动
加速
刹车
比亚迪
充电
------------构造 比亚迪汽油车--------------
发动
加速
刹车
比亚
对于一开始使用的继承,如果父类后期发生变化,那么对于子类的影响是非常大的,同时继承也非常的不灵活。而使用组合时,如果后期行为发生了变化,我们只需要切换这个行为的实现类即可,而Car中使用的时实现这个行为的接口,对其子类不会产生影响,如果需要添加新的行为,只需要添加一个新的行为接口,并将其委托给具体的实现类即可。
由此我们可以得出:实际开发中,我们应当多用组合,少用继承。
这里对展示品牌和补充燃料方式行为的封装就是使用的策略模式,再次阅读策略模式的定义,应当容易理解了。