一、定义
定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。
二、抛砖引玉
这个通俗易懂的小栗子,原版出自《设计模式之禅(第二版)》。
三国情景再现:
诸葛亮在刘备去东吴招亲之前,特授予伴郎赵云三个锦囊,说是按天机拆开解决棘手问题。
这三个妙计分别是:找乔国老帮忙(也就是走后门了),求吴国太放行(开绿灯)以及孙夫人断后。
接下来我们模拟这三个妙计的使用场景。
//妙计接口:
public interface Strategy {
void operate();
}
//妙计具体实现(一)——乔国老开后门
public class BackDoor implements Strategy {
@Override
public void operate() {
System.out.println("找乔国老帮忙,让吴国太给孙权施加压力");
}
}
//妙计具体实现(二)——求吴国太放行(开绿灯)
public class GivenGreenLight implements Strategy {
@Override
public void operate() {
System.out.println("求吴国太开绿灯,放行");
}
}
//妙计具体实现(三)——孙夫人断后
public class BlockEnemy implements Strategy {
@Override
public void operate() {
System.out.println("孙夫人断后,挡住追兵");
}
}
//装锦囊的袋子
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
//使用计谋
public void operate(){
this.strategy.operate();
}
}
//赵云
public class ZhaoYun {
public static void main(String[] args) {
Context context = null;
System.out.println("---刚刚到吴国的时候拆第一个锦囊");
context = new Context(new BackDoor());
context.operate();
System.out.println("---刘备乐不思蜀,拆开第二个");
context = new Context(new GivenGreenLight());
context.operate();
System.out.println("---孙权的小兵追了,拆第三个");
context = new Context(new BlockEnemy());
context.operate();
}
}
//最后运行结果
---刚刚到吴国的时候拆第一个锦囊
找乔国老帮忙,让吴国太给孙权施加压力
---刘备乐不思蜀,拆开第二个
求吴国太开绿灯,放行
---孙权的小兵追了,拆第三个
孙夫人断后,挡住追兵
栗子很简单,也很明了。不再做过多解释,倘若看不太懂,说明你需要补充一下java基础。
策略模式属于行为型模式,在实际开发中,我们要分析一个事物哪些行为是不可变的,哪些行为是可变的。可变行为的开发,日后开发要做到易维护、易扩展。
前人为我们总结出了策略模式,将可变行为抽象,接口化(符合迪米特原则)行为分类定义接口(符合接口分离原则),之后对应实现具体的行为,日后需要扩展时就新建一个类实现行为接口(符合开闭原则)。
三、第二个栗子:
接下来再看一个鸭子的栗子,鸭子的种类很多,有的会飞,飞得高飞得久(所谓飞得好),反之就是飞得差,有的甚至不会飞,就不需要这个行为;有的叫声是咕咕,有的叫声嘎嘎。
首先我们把飞和叫声分别抽象
飞行为抽象
public interface FlyBehavior {
void fly();
}
//飞得好
public class GoodFlyBehavior implements FlyBehavior {
private GoodFlyBehavior(){
}
public static GoodFlyBehavior getInstantiation(){
return new GoodFlyBehavior();
}
@Override
public void fly() {
System.out.println("good-----fly");
}
}
//飞得差
public class BadFlyBehavior implements FlyBehavior{
private BadFlyBehavior(){
}
//忽略我这里用了反射
public static BadFlyBehavior getInstantiation(){
return new BadFlyBehavior();
}
@Override
public void fly() {
System.out.println("bad----fly");
}
}
叫声行为抽象:
public interface QuackBehavior {
void quack();
}
public class GaGaQuackBehavior implements QuackBehavior {
@Override
public void quack() {
System.out.println("gaga---quack");
}
}
public class GuGuQuackBehavior implements QuackBehavior {
@Override
public void quack() {
System.out.println("gugu---quack");
}
}
接下来我们开始造鸭子了
//首先造一只抽象的鸭子
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public void fly(){
if (flyBehavior!=null){
flyBehavior.fly();
}
}
public void quack(){
if (quackBehavior!=null){
quackBehavior.quack();
}
}
public abstract void eat();
public void swim(){
System.out.println("小鸭子swim--");
}
}
//绿头鸭飞得好,嘎嘎叫
public class GreenHeadDuck extends Duck {
public GreenHeadDuck(){
flyBehavior = GoodFlyBehavior.getInstantiation();
quackBehavior = new GaGaQuackBehavior();
}
@Override
public void eat() {
System.out.println("吃鱼");
}
}
//红头鸭飞得差,咕咕叫
public class RedHeadDuck extends Duck {
public RedHeadDuck(){
flyBehavior = BadFlyBehavior.getInstantiation();
quackBehavior = new GuGuQuackBehavior();
}
@Override
public void eat() {
System.out.println("吃鱼---");
}
}
//蓝头鸦,不会飞,咕咕叫
public class BlueHeadDuck extends Duck {
public BlueHeadDuck(){
quackBehavior = new GuGuQuackBehavior();
}
@Override
public void eat() {
System.out.println("坐着吃");
}
}
//测试:
public class test1 {
public static void main(String[] args) {
Duck green = new GreenHeadDuck();
green.fly();
green.quack();
System.out.println("---------------------------");
Duck red = new RedHeadDuck();
red.fly();
red.quack();
System.out.println("---------------------------");
Duck blue = new BlueHeadDuck();
blue.fly();// 虽然调了fly,但是没打印出什么,看方法逻辑上此方法是空的。
blue.quack();
}
}
--------------绿头鸭-------------
good-----fly
gaga---quack
--------------红头鸭-------------
bad----fly
gugu---quack
--------------蓝头鸭-------------
gugu---quack
可以看得出策略模式使用的就是面向对象的继承和多态机制。
策略模式的优点:
- 策略可以自由切换
- 避免使用多重条件判断
- 扩展性好
缺点:
- 策略类数量过多
- 所有的策略类都需要对外暴露。上层模板必须知道有哪些策略,然后才能决定使用哪个策略,这与迪米特法则是相违背的,
我只是想使用个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?
这是原装策略模式的一个缺点,幸运的是我们可以使用其他模式来修正这个缺陷,如工厂模式,代理模式或享元模式。
使用场景:
- 多个类只有在算法或者行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
四、再来举个计算加减的栗子:
同样出自《设计模式之禅(第二版)》的改编。
一个计算器,有两个功能,两个数相加,两个数相减。
public interface Calculator {
int exec(int a,int b);
}
public class Add implements Calculator {
@Override
public int exec(int a, int b) {
return a+b;
}
}
public class Sub implements Calculator {
@Override
public int exec(int a, int b) {
return a-b;
}
}
public class Context {
private Calculator cal = null;
public Context(Calculator cal){
this.cal = cal;
}
public int exec(int a,int b){
return this.cal.exec(a,b);
}
}
public class test {
public static void main(String[] args) {
Context context = new Context(new Add());
int result = context.exec(10,10);
System.out.println(result);
Context context1 = new Context(new Sub());
int result1 = context1.exec(20,10);
System.out.println(result1);
}
}
来个高端的操作:
public enum Calculator {
//+
ADD ("+"){
public int exec(int a,int b){
return a+b;
}
},
SUB("-"){
public int exec(int a,int b){
return a-b;
}
};
String value = "";
//定义成员值类型
private Calculator(String _value){
this.value = _value;
}
//获得枚举成员的值
public String getValue(){
return this.value;
}
//声明一个抽象函数
public abstract int exec(int a,int b);
}
public class Client {
public static void main(String[] args) {
int add_result = Calculator.ADD.exec(10,20);
int sub_result = Calculator.SUB.exec(20,10);
System.out.println(add_result);
System.out.println(sub_result);
}
}
是不是感觉很清爽,这就叫策略枚举。
策略枚举,是一个非常优秀和方便的模式,但是它受枚举类型的限制,每个枚举项都是public、final、static的,扩展性受了一定的约束,所以在系统开发中,策略枚举一般担当不经常发生变化的角色。
需要注意:
如果系统中的一个策略家族的具体策略数量超过了4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会成为一个烫手山芋。
策略模式的精华远不止这些,还待实际开发中运用和体会。