参考资料:
概念解析
策略模式strategy,对象行为型
实现一个功能有多种算法或者策略,可以依据环境或者条件的不同选择不同的算法或者策略来完成此功能。
比如查找、排序等。
常用的编码方式:
1. 创建一个类,在类中提供多个方法,每个方法对应一个具体的查找方法。一个简单的例子,DateUtils工具类,获取时间的方式有很多种,对外提供很多获取时间的方法。
2. 或者是将查找,排序的算法封装在一个统一的方法中,通过if else的方法进行选择,比如if(冒泡){冒泡的方法}else if(倒序){倒序的方法}
以上这2中都是硬编码,如果需要新增一个新的查找或者排序算法,那么就要修改封装的算法类的源代码DateUtils;如果是更换了算法,比如DateUtils.getYear()更改名称成DateUtils.loadYear(),那么客户端也要修改调用的代码。这样在这个类中就会封装大量的查找算法,越来越复杂,维护起来也会变得很困难。
headfirst书上的例子拿来用一下:
一只鸭子,它的行为模式:可以飞fly()、叫duack()、走walk()、游泳swim()。
但是鸭子是有很多种的:真的鸭子,洗澡的玩具鸭,小黄鸭,墙上的涂鸦等,这些鸭子有的可以飞,有的不能遇水,有的不能叫。
而且就duack而言,也有很多种叫法,有的嘎嘎嘎,有的呀呀呀。。。
那么如何设计这个鸭子对象,让其产生的子鸭子可以飞,可以叫,而且发出不同的叫声呢?这里就可以用到策略模式来设计。
整体思路就是抽取鸭子的行为(算法),也就是算法(具体行为)和对象(鸭)分离。策略模式把对象本身和运算规则区分开来,定义一系列的算法,把每个算法封装起来,并且使其可以相互替换。也就是一个鸭子可以设定为嘎嘎叫,也可以替换为呀呀叫。
应用场景
直接上应用的场景吧,大部分人应该关注的是这点,场景也不是绝对的。此部分参考百度百科
1. 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为(鸭叫分为很多种)。
2. 需要在不同的情况下使用不同的策略(算法),或者策略还可能在未来用其他方式来实现(克隆出现了一只咩咩叫的鸭子)。
3. 对客户隐藏具体的策略(算法)的实现细节,彼此完全独立(可以不需要知道我怎么实现的叫声,只需要知道我提供了多少种叫法,调用就好了)。
模式的组成
例子
上图中定义了一个person,它要去旅游(旅游是一个行为动作),选择出行方式可以有很多种,飞机,火车,自行车。但是我们去实现这个person的时候并不要在这个类中实现所有的出行方式,而是通过一个接口方法。person中维护一个接口的调用。至于具体的出行(strategy.travel())的具体实现,就是具体的行为实现。
重点注意UML中的setStrategy(TravelStrategy strategy),通过改变Strategy来改变行为。TrainStrategy等都实现了TravelStrategy接口。
标准点的图:
—抽象策略角色(Strategy): 策略类,通常由一个接口或者抽象类实现。定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
—具体策略角色(ContentStrategy):以Strategy接口实现某具体算法。
—环境角色(Content):持有一个策略类的引用,最终给客户端调用。用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
代码实现
先来个题目做一下,来自headfirst的拷问:
一句话概括:不同的游戏角色可以使用不同的武器进行战斗,如何实现?
代码部分
首先观测到行为,使用武器战斗,那么就要抽象出一个使用武器的接口
public interface WeaponBehavior {
/**
* 攻击
*/
void useWeapon();
}
使用什么武器呢(具体行为实现)?假设为使用宝剑战斗
public class SwordBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("使用宝剑攻击.");
}
}
好了,行为有了,该设定角色Character了,这个角色有使用武器的行为,并且有战斗方法:
public abstract class Character {
public Character() {
}
/**
* 使用武器
*/
WeaponBehavior weapon;
public void setWeapon(WeaponBehavior weapon) {
this.weapon = weapon;
}
/**
* 战斗
*/
public void fight() {
weapon.useWeapon();
}
}
角色有了,该创建具体的人物了,来个king试试,他喜欢用宝剑。
public class King extends Character {
public King(){
weapon = new SwordBehavior();
}
}
注意这里的weapon,是继承自Character的,而且在king创建的时候,也创建了接口的实现类为SwordBehavior,那么他就有了具体的行为实现。
再来一个queen,她喜欢用弓箭。
public class Queen extends Character {
public Queen(){
weapon = new BowAndArrowBehavior();
}
}
是时候开始战斗了。
@Test
public void testFight(){
// 创建一个king
Character k = new King();
Character q = new Queen();
k.fight();
System.out.println("queen血量 -1");
q.fight();
System.out.println("king血量 -1");
System.out.println("king的宝剑掉了...");
System.out.println("king捡起了弓箭...");
k.setWeapon(new BowAndArrowBehavior());
k.fight();
System.out.println("queen血量 -1");
System.out.println("king获得了胜利");
}
使用宝剑攻击.
queen血量 -1
使用弓箭攻击.
king血量 -1
king的宝剑掉了...
king捡起了弓箭...
使用弓箭攻击.
queen血量 -1
king获得了胜利
看到效果了,king的宝剑被queen打掉了,但是king迅速的捡起了地上的弓箭,反败为胜。
小伙伴可以去试着实现鸭子发出不同叫声
优缺点
优点:
1. 策略模式提供了管理相关的算法族的办法(算法族就是在strategy接口中定义的一系列方法,代码举例中只有useWeapon(),但是你还可以加入其它的方法,比如useShield(),或者使用魔法等等)。Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
2. 策略模式提供了可以替换继承关系的办法。
3. 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4. 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。
缺点
1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
2. 策略模式造成很多的策略类,每个具体策略类都会产生一个新类(实现了接口的类)。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
总结
摘抄自blog1
1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
headfirst读书分享
1. 找出应用中可能需要变化之处,将他们独立出来,不要和不需要变化的代码混在一起。
2. 针对接口编程,而不是针对实现编程。
3. 多用组合,少用继承。
设计模式让你和其他开发人员之间有共享的词汇,一旦懂得这些词汇,和其他开发人员之间的沟通就会很容易。
设计模式也可以帮助你提升思考架构的层次号模式层面,而不是停留在琐碎的对象上。
设计模式不会直接进入你的代码中,而是必须先进入你的『脑袋』中。一旦你先在脑海中装入许多 模式的知识,就能够开始在新设计中采用它们,以及当你的旧代码变得如同意大利面一样搅和成一 团没有弹性时,可用它们重做旧代码。
连接:https://blog.csdn.net/hguisu/article/details/7558249 ↩︎