策略模式是什么?
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。
为什么使用策略模式?
优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地-增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在
许多
行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的
保密性
与安全性。
使用示例:
该示例取自一个动作冒险游戏,我们将看到代表游戏角色的类和角色可以使用的武器行为的类。每个角色一次只能
使用一种武器。
但是可以在游戏的过程中切换武器。
1.画UML图:
2.UML图分析:
Character(角色)是抽象类,由具体的角色来继承。而Weapon(武器)是接口,由具体的武器来继承。所有实际
的角色和武器都是
具体类。
任何角色如果想换武器,可以调用setWeapon()方法,此方法定义在Character超类中。可用getWeapon()方法来
获取所使用的武器,
来攻击其他角色。
3.代码示例:
Base包:
/** * 角色基类 */ public abstract class Character { private WeaponBehavior weapon; public abstract void fight(); public void setWeapon(WeaponBehavior weapon) { this.weapon = weapon; } public void getWeapon() { weapon.useWeapon(); } }
/** * 武器接口 */ public interface WeaponBehavior { void useWeapon(); }
derive包:
角色role包:
/** * 国王 */ public class King extends Character { @Override public void fight() { System.out.println("I'm the King.My attract force is 95."); } }
/** * 皇后 */ public class Queen extends Character { @Override public void fight() { System.out.println("I'm the Queen.My attract force is 90."); } }
/** * 骑士 */ public class Knight extends Character { @Override public void fight() { System.out.println("I'm the Knight.My attract force is 92.5."); } }
/** * 妖怪 */ public class Troll extends Character { @Override public void fight() { System.out.println("I'm the troll.My attract force is 100."); } }
behavior包:
/** * 实现用宝剑挥舞 */ public class SwordBehavior implements WeaponBehavior{ @Override public void useWeapon() { System.out.println("I use the sword to attract."); } }
/** * 实现用匕首刺杀 */ public class KnifeBehavior implements WeaponBehavior { @Override public void useWeapon() { System.out.println("I use the knife to attract."); } }
/** * 实现用弓箭射击 */ public class BowAndArrowBehavior implements WeaponBehavior { @Override public void useWeapon() { System.out.println("I use the bow and arrow to attract."); } }
/** * 实现用斧头劈砍 */ public class AxeBehavior implements WeaponBehavior { @Override public void useWeapon() { System.out.println("I use the axe to attract."); } }
测试类:
public class TestTroll { public static void main(String[] args) { Character character = new Troll(); character.setWeapon(new AxeBehavior()); character.fight(); character.getWeapon(); } }
测试结果:
I'm the troll.My attract force is 100. I use the axe to attract.总结:
1.我们在实例化Troll(妖怪)对象的时候,没有采用Troll troll = new Troll();这样的方式,这是因为我们用到了一个
设计原则
:
针对接口编程,而不是针对实现编程。
“针对接口编程”的关键在于多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,
不会被绑死再超类型的行为上。“针对超类性编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,
通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。
这也意味着,声明类时不用理会以后执行时的真正对象类型!”
例如:
Dog d = new Dog();
d.bark();
就是明显的“针对实现编程”,声明变量“d”为Dog类型(是Animal的具体实现),会造成
我们必须针对具体实现编码。
但是“针对接口/超类型编程”做法会如下:
Animal animal = new Dog();
animal.makeSound();
我们知道该对象是狗,但是我们现在利用animal进行多态的调用。
更棒的是,子类实例化的动作不再需要在代码中硬编码,例如 new Dog(),而是“在运行时才指定具体实现的对象”。
我们知道该对象是狗,但是我们现在利用animal进行多态的调用。
更棒的是,子类实例化的动作不再需要在代码中硬编码,例如 new Dog(),而是“在运行时才指定具体实现的对象”。
animal = getAnimal();
animal.makeSound();
我们不知道实际的子类型是“什么”... 我们只关心它知道如何正确地进行makeSound()的动作就够了。
Character基类中的武器类型声明为接口类型WeaponBehavior也是基于这个设计原则设计的。
2.第二个
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码
混在一起。
在这个小程序demo中,因为每个角色都会“战斗”,所以fight相关的是写在基类中待继承的,它是不会变化的,
每个角色所使用的“武器”都是不同的,故武器是会变化的,所以为了分开“变化和不会变化的部分”,我们把“武器”
从Character类中取出来,建立一组新类来代表每个行为。
而我们以WeaponBehavior为接口,然后使用SwordBehavior等实现类来实现这个父接口,这样的设计,可以让
“武器”的动作被其他的对象复用,因为这些行为已经与角色类无关了。这样一来,有了继承的“复用”的好处,却没
有继承所带来的包袱(比如高耦合),而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到
武器行为的角色类。
3.第三个
设计原则:多用组合,少用继承。
我们描述事情的方式也稍有改变。不再把角色的行为说成是“一组行为”,我们开始把行为想成是“一族算法”。
想象下,我们后期有一个需求变更,需要给每个角色增加一个“宠物”,可以用来帮助战斗,每个人的宠物自然也会
可能是不同的,我们可以说每个角色“有一个”武器和宠物。“有一个”关系相当有趣:每一个角色都有一个
WeaponBehavior和PetBehavior,好将武器和宠物委托给它们代为处理。
当我们将两个类结合起来使用,如同本例一样,这就是组合(composition)。
这种做法和“继承”不同的地方在于,角色的行为不是继承来的,而是和适当的行为对象“组合”来的。这使我们所
建立的系统具有很大的弹性,不仅可将算法族封装成类,还可以“在运行时动态地改变行为”,只要组合的行为对象
符合正确的接口标准即可。
良好的OO设计必须具备可复用、可扩充、可维护三个特性。
ps:第一篇博客介绍策略模式显的有点啰嗦和长篇大论了点,但细读下来相信你会有所收获的~感谢阅读。