java设计模式之——策略模式
1,什么是策略模式?
策略模式,又叫算法簇模式,就是定义了不同的算法族,并且之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
2,策略模式有什么好处?
策略模式的好处在于你可以动态的改变对象的行为。
3,设计原则
设计原则是把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口(c++z中可以用虚类),然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为。下面是一个例子。
策略模式属于对象行为型模式,主要针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响 到客户端的情况下发生变化。通常,策略模式适用于当一个应用程序需要实现一种特定的服务或者功能,而且该程序有多种实现方式时使用。
4 ,策略模式中有三个对象:
(1) 环境对象:该类中实现了对抽象策略中定义的接口或者抽象类的引用。
(2) 抽象策略对象:它可由接口或抽象类来实现。
(3) 具体策略对象:它封装了实现同不功能的不同算法。
利用策略模式构建应用程序,可以根据用户配置等内容,选择不同有算法来实现应用程序的功能。具体的选择有环境对象来完成。采用这种方式可以避免由于使用条件语句而带来的代码混乱,提高应用程序的灵活性与条理性。
5,应用场景举例:
刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开能解决棘手问题,嘿,还别说,真解决了大问题,搞到最后是周瑜陪了夫人又折兵,那咱们先看看这个场景是什么样子的。
先说说这个场景中的要素:三个妙计,一个锦囊,一个赵云,妙计是亮哥给的,妙计放在锦囊里,俗称就是锦囊妙计嘛,那赵云就是一个干活的人,从锦囊取出妙计,执行,然后获胜。用java程序怎么表现这些呢?
那我们先来看看图?
三个妙计是同一类型的东西,那咱就写个接口:
- package com.yangguangfu.strategy;
- /**
- *
- * @author trygf521@126.com:阿福
- * 首先定义一个策略接口,这是诸葛亮老人家给赵云的三个锦囊妙计的接口。
- */
- public interface IStrategy {
- //每个锦囊妙计都是一个可执行的算法。
- public void operate();
- }
然后再写三个实现类,有三个妙计嘛:
妙计一:初到吴国:
- package com.yangguangfu.strategy;
- /**
- *
- * @author trygf521@126.com:阿福
- * 找乔国老帮忙,使孙权不能杀刘备。
- */
- public class BackDoor implements IStrategy {
- @Override
- public void operate() {
- System.out.println("找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备...");
- }
- }
妙计二:求吴国太开个绿灯,放行:
- package com.yangguangfu.strategy;
- /**
- *
- * @author trygf521@126.com:阿福
- * 求吴国太开个绿灯。
- */
- public class GivenGreenLight implements IStrategy {
- @Override
- public void operate() {
- System.out.println("求吴国太开个绿灯,放行!");
- }
- }
妙计三:孙夫人断后,挡住追兵:
- package com.yangguangfu.strategy;
- /**
- *
- * @author trygf521@126.com:阿福
- * 孙夫人断后,挡住追兵。
- */
- public class BlackEnemy implements IStrategy {
- @Override
- public void operate() {
- System.out.println("孙夫人断后,挡住追兵...");
- }
- }
好了,大家看看,三个妙计是有了,那需要有个地方放妙计啊,放锦囊里:
- package com.yangguangfu.strategy;
- /**
- *
- * @author trygf521@126.com:阿福
- *
- */
- public class Context {
- private IStrategy strategy;
- //构造函数,要你使用哪个妙计
- public Context(IStrategy strategy){
- this.strategy = strategy;
- }
- public void operate(){
- this.strategy.operate();
- }
- }
然后就是赵云雄赳赳的揣着三个锦囊,拉着已步入老年行列,还想着娶纯情少女的,色咪咪的刘备老爷子去入赘了,嗨,还别说,亮哥的三个妙计还真不错,瞧瞧:
- package com.yangguangfu.strategy;
- public class ZhaoYun {
- /**
- * 赵云出场了,他根据诸葛亮给他的交代,依次拆开妙计
- */
- public static void main(String[] args) {
- Context context;
- //刚到吴国的时候拆开第一个
- System.out.println("----------刚刚到吴国的时候拆开第一个---------------");
- context = new Context(new BackDoor());
- context.operate();//拆开执行
- System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n");
- //当刘备乐不思蜀时,拆开第二个
- System.out.println("----------刘备乐不思蜀,拆第二个了---------------");
- context = new Context(new GivenGreenLight());
- context.operate();//拆开执行
- System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n");
- //孙权的小追兵了,咋办?拆开第三个锦囊
- System.out.println("----------孙权的小追兵了,咋办?拆开第三个锦囊---------------");
- context = new Context(new BlackEnemy());
- context.operate();//拆开执行
- System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n");
- }
- }
后话:就这三招,搞得的周郎是“赔了夫人又折兵”呀!这就是策略模式,高内聚低耦合的特点也表现出来了,还有一个就是扩展性,也就是OCP原则,策略类可以继续添加下去气,只是修改Context.java就可以了,这个不多说了,自己领会吧。
以上转自:http://yangguangfu.iteye.com/blog/815107?page=2#comments
以下转自:http://www.cnblogs.com/wenjiang/p/3352041.html
人的机缘是神奇的,认识一个人就相当于打开了一个圈子,不管这个人是否在圈子中心,而这点,会在不经意间带给我们意想不到的作用。
如果我们在编写代码的时候,遇到大量的条件判断的时候,可能会采用策略模式来优化结构,因为这时涉及到策略的选择,但有时候仔细查看下,就会发现,这些所谓的策略其实是对象的不同状态,更加明显的是,对象的某种状态也成为判断的条件。
我们还是以一个例子入手。
假设现在我们有一个饮水机,它有以下两个状态: 满桶,空桶。初始状态是满桶,容量是20。饮水机只有一个动作:press,每次press后都会使容量减1,一旦为0,则将状态设置为空桶,这时press没有水流出。
要使用状态模式,我们必须明确两个东西:状态和每个状态下执行的动作。就像是饮水机,最基本的状态就是满桶和空桶,而这两个状态下,都可能要执行倒水这个动作,也就是press。如果饮水机的容量为0,则会进入空桶的状态。
在状态模式中,因为所有的状态都要执行相应的动作,所以我们可以考虑将状态抽象出来。
状态的抽象一般有两种形式:接口和抽象类。如果所有的状态都有共同的数据域,可以使用抽象类,但如果只是单纯的执行动作,就可以使用接口。
这里我们就用接口。
public interface DispenserState {
void press();
}
然后我们再定义满桶和空桶两个状态:
public class FullState implements DispenserState {
@Override
public void press() {
System.out.println("Water is pouring!");
}
}
public class NullState implements DispenserState {
@Override
public void press() {
System.out.println("There is not water poured!");
}
}
接着我们再实现饮水机:
public class WaterDispenser {
private static int capacity = 20;
private static DispenserState dispenserState;
public WaterDispenser(DispenserState state) {
dispenserState = state;
}
private static void setState(DispenserState state) {
dispenserState = state;
}
public DispenserState getState() {
return dispenserState;
}
public void press() {
capacity--;
if (capacity <= 0) {
setState(new NullState());
}
dispenserState.press();
}
}
接着我们再进行测试:
public class Test {
public static void main(String[] args) {
WaterDispenser dispenser = new WaterDispenser(new FullState());
for (int i = 0; i < 100; ++i) {
dispenser.press();
}
}
}
这是一个非常简单的应用场景:我们不断的press,饮水机里的水会越来越少,从满桶状态变成空桶状态。
如果我们不使用状态模式,也可以解决这个问题:
public class WaterDispenser {
private static int capacity = 20;
public void press() {
capacity--;
if (capacity <= 0) {
System.out.println("There is not water poured!");
} else {
System.out.println("Water is pouring!");
}
}
}
这样确实是更加简单,不需要有多余的接口和一系列的类,但这里的情况只有两种,如果是三种,五种,甚至更多,几十种,那我们到底需要多么可怕的if...else子句啊!而且,要是条件中再嵌套条件,简直就是难以想象!!
状态模式的好处就是将我们从这个复杂的嵌套条件中脱离出来,但状态模式的坏处也是非常明显:需要管理一系列的状态类。
状态模式的意图就是让每一个状态对修改关闭,允许对象在内部状态改变时改变它的行为,使对象看起来好像修改了它的类。
让每一个状态对修改关闭,也就是让状态类来改变状态,即封装,我们只能通过向状态类发送消息来改变状态,至于怎么改变,则完全隐藏起来。
允许对象在内部状态改变时改变它的行为,使对象看起来好像修改了它的类。其实,对象本身拥有一个状态类的对象集合,只要符合状态改变的条件,就可以将表示状态的状态类改变成下个状态类。从具体的代码来看,所谓的状态类的对象集合,其实就是我们的对象拥有一个状态类的引用,该引用可以指向任何状态类的对象集合的具体引用。对象本身一般都会有一个setState()方法,可以将状态修改成另一个状态。
至于这个对象,我们可以给它一个专门的名词:Context,也就是上下文类。
上下文是一个难以理解的词眼,放在具体的应用场景中更加直白点。
这里我们的饮水机就是一个Context,它将自己的行为委托给状态对象执行,像是press()方法,就交给具体的状态对象state的press()方法执行。
上下文类的明显特点就是拥有一个引用,然后通过该引用调用相应的方法。
我们是如何区分不同的状态?就是通过每个状态不同的行为来区分,所以不同的状态都有相同的动作,但是这个动作的执行结果是不同的,而且执行结果还有可能会修改当前的状态。为了体现多态,摆脱对具体状态类的依赖,使用Context可以动态的替换状态类,就像策略模式一样。
状态的转换不一定是放在Context中,有时候状态类本身也会自动切换状态。当状态的转换是固定的,像是这里的容量为0,就变成空桶状态,我们可以在Context中完成转换,但如果转换是动态的,也就是没有固定的判断条件,像是一完成就自动切换,我们可以放在状态类中。
但将状态的切换放在状态类中,会让状态类间产生依赖,而且状态类还需要拥有Context类的引用才能切换状态,也就是采用观察者模式的方法来通知Context类更新状态。所以,是否要这样做,就看具体的编程环境以及我们的经验了。
程序员的经验是非常重要的,它决定我们是否可以达到更高的境界,新手是在积累经验,等累积到一定的程度,编程的时候就基本靠经验的驱动了,什么是安全的代码,什么样的代码扩展性好,都会在不知不觉间体现出来,不需要特意从头脑中挖出来。
状态对象也是可以共享的,但前提就是状态对象不能持有它们自己的内部状态,否则无法保证另一个线程得到的对象是正确的状态。所以,我们如果想要共享状态,需要把每个状态都指定到静态的实例变量中。
状态模式的意图其实非常简单:将与状态有关的处理逻辑分散到代表对象状态的各个类中,但我们的Context类必须拥有这些状态对象集合的引用,这也就引出一个问题:实例化Context类对象会导致状态类对象集合初始化方面的问题,也就是对象的依赖问题。
为了尽可能减少这方面的依赖,我们的Context类通常拥有的只是一个状态类的抽象引用,然后设置一个setter以便动态的更改状态类对象。
如果不是使用对象,而是采用常量的方法:
private static final int FULL_STATE = 0;
private static final int NULL_STATE = 1;
也可以使用enum将它们封装起来:
enum STATE{ NULL_STATE, FULL_STATE};
采用这种做法是因为我们需要根据当前的状态自动跳转到下一个状态,比如说,饮水机可以在空桶的状态自动加水:
if(state == NULL_STATE){
capacity++;
if(capacity == 20){
state = FULL_STATE;
}
}
这种做法非茶馆常见,因为我们只是想要有一个状态的判断和切换的简单动作,并不需要特意创建一个对象,因为这些状态只是单纯的标识,没有任何的职责。但程序中出现过多的静态变量总是让人觉得这个程序的设计不具有良好的弹性,如果可以从这些状态中提取出抽象,可以考虑使用状态模式来优化我们的代码结构。
之所以说状态模式是策略模式的孪生兄弟,是因为它们的UML图是一样的,但意图却完全不一样,策略模式是让用户指定更换的策略算法,而状态模式是状态在满足一定条件下的自动更换,用户无法指定状态,最多只能设置初始状态。