Skip to content

设计模式之——策略模式

一、定义

策略模式(Strategy Pattern)是针对一组算法或逻辑,将每一个算法或逻辑封装到具有共同接口的独立的类中,从而使得它们之间可以相互替换。策略模式使得算法或逻辑可以在不影响到客户端的情况下发生变化。

二、结构

策略模式涉及三个角色:

1、环境(Context)角色:持有一个Strategy的引用。

2、抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需实现的接口。

3、具体策略(Concrete Strategy)角色:包装了相关的算法或行为。

三、实现

假设现在我们要实现一个鸭子游戏的应用,游戏中可以产生各种鸭子,一边游泳,一边戏水。这个要怎么实现?

1、方案一

设计一个鸭子超类,所有的鸭子都会继承这个超类,并各自重写自己的方法。

/**
 * 鸭子超类
 * @author miner
 */
public class Duck {
	
	public Duck() {
		
	}
	
	//飞行
	public void performFly() {
		System.out.println("我正在飞。。。");
	}
	
	//鸣叫
	public void performQuack() {
		System.out.println("呱呱呱。。。");
	}
	
	//游泳
	public void swim() {
		System.out.println("所有的鸭子都会游泳。。。");
	}
	
	//鸭子的外观
	public void display(){
		System.out.println("我的特征。。。");
	}
}

/**
 * 绿头鸭
 * 会飞,呱呱叫
 * @author miner
 */
public class MallardDuck extends Duck{
	
	public void display() {
		System.out.println("我是一只绿头鸭。。。");
	}

}
/**
 * 橡皮鸭
 * 不会飞,吱吱叫
 * @author miner
 */
public class RubberDuck extends Duck{
	
	public void fly() {
		//什么也不做,不会飞
	}
	
	public void quack() {
		System.out.println("吱吱叫。。。");
	}
}
/**
 * 模型鸭
 * 不会飞,呱呱叫
 * @author miner
 */
public class ModelDuck extends Duck{
	
	public void fly() {
		//什么也不做,不会飞
	}

	public void display() {
		System.out.println("我是一只模型鸭。。。");
	}
}

缺点:

这样做确实可以实现整个应用,但有些鸭子是不会飞的,例如模型鸭,有些鸭子也不是呱呱叫,而是吱吱叫,例如橡皮鸭,还有的鸭子甚至不会叫,例如木头鸭,若实现超类,则每个子类都必须实现fly()和quack()方法。这样会造成代码冗余,不利于代码复用,且不容易知道每个子类的所有行为(因为有些不会飞的鸭子,也有fly方法,有些不会叫的鸭子,也有quack方法)。

2、方案二

既然继承不好使,那不如用接口,将fly()方法封装到FlyBehavior接口中,将quack()方法封装到QuackBehavior接口中,会飞的鸭子实现FlyBehavior接口,会叫的鸭子实现QuackBehavior接口并重写quack()方法。

缺点:

这样还是可以实现整个应用,但缺点是,只要新增加一个子类,就必须实现FlyBehavior接口或QuackBehavior接口,且必须重写接口中的方法,因为接口中不提供方法的实现,这样若子类巨多,要写的代码就巨多,同样会造成代码冗余,不利于代码维护。

3、方案三

这下轮到策略模式出场了

I、首先构造飞行行为接口和鸣叫行为接口

/**
 * 飞行
 * @author miner
 *
 */
public interface FlyBehavior {
	
	void fly();

}
/**
 * 鸣叫
 * @author miner
 *
 */
public interface QuackBehavior {

	void quack();
}

II、再构造鸭子超类,并持有飞行和鸣叫的引用

/**
 * 鸭子超类
 * @author miner
 *
 */
public abstract class Duck {
	
	protected FlyBehavior flyBehavior;  //持有飞行行为的引用
	protected QuackBehavior quackBehavior;    //持有鸣叫行为的引用
	
	//默认构造方法
	public Duck() {
		
	}
	
	//飞行
	public void performFly() {
		flyBehavior.fly();
	}
	
	//鸣叫
	public void performQuack() {
		quackBehavior.quack();
	}
	
	//游泳
	public void swim() {
		System.out.println("所有的鸭子都会游泳");
	}
	
	//鸭子的外观,抽象方法,由具体子类实现
	public abstract void display();
	
	//为了动态设置飞行行为和鸣叫行为,我们给strategy引用加上set方法
	//设置飞行行为
	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	//设置鸣叫行为
	public void setQuackBehavior(QuackBehavior quackBehavior) {
		this.quackBehavior = quackBehavior;
	}
}

III、构造飞行具体实现类

/**
 * 有翅膀的鸭子的飞行动作
 * @author miner
 *
 */
public class FlyWithWings implements FlyBehavior{
	
	public void fly() {
		System.out.println("用翅膀飞。。。");
	}

}
/**
 * 不会飞的鸭子的飞行动作
 * @author miner
 *
 */
public class FlyNoWay implements FlyBehavior{
	
	public void fly() {
		//什么也不做,不会飞
	}
}
/**
 * 以火箭为动力的飞行行为
 * @author miner
 *
 */
public class FlyPoweredByRocket implements FlyBehavior{

	@Override
	public void fly() {
		System.out.println("乘火箭飞行!");
	}
	
}

IV、构造鸣叫具体实现类

/**
 * 呱呱叫
 * @author miner
 *
 */
public class Quack implements QuackBehavior{
	
	public void quack() {
		System.out.println("呱呱叫...");
	}

}
/**
 * 吱吱叫
 * @author miner
 *
 */
public class Squeak implements QuackBehavior{
	
	public void quack() {
		System.out.println("吱吱叫...");
	}

}
/**
 * 不会叫
 * @author miner
 *
 */
public class MuteQuack {
	
	public void quack() {
		//什么也不做,不会叫
	}

}

V、编写具体子类

/**
 * 绿头鸭
 * 会呱呱叫,会飞行
 * @author miner
 *
 */
public class MallardDuck extends Duck{
	
	public MallardDuck() {
		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();
	}
	
	public void display() {
		System.out.println("我是一只绿头鸭");
	}

}
/**
 * 模型鸭
 * 不会飞,会呱呱叫
 * @author miner
 *
 */
public class ModelDuck extends Duck{
	
	public ModelDuck() {
		flyBehavior = new FlyNoWay();
		quackBehavior = new Quack();
	}

	@Override
	public void display() {
		System.out.println("我是一只模型鸭");
	}
	
}

最后是测试代码:

public class StrategyPatternTest {

	public static void main(String[] args) {

		//绿头鸭会飞行也会呱呱叫
		Duck mallardDuck = new MallardDuck();
		mallardDuck.performFly();
		mallardDuck.performQuack();
		
		//模型鸭
		Duck modelDuck = new ModelDuck();
		//一开始我们的modelDuck不会飞
		modelDuck.performFly();
		//现在让它以火箭为动力飞行
		modelDuck.setFlyBehavior(new FlyPoweredByRocket());
		//现在模型鸭会飞了
		modelDuck.performFly();
	}

}

利用策略模式可以发现,我们不再使用继承而是使用组合来实现飞行和鸣叫,通过委托给具体的FlyBehavior和QuackBehavior实现飞行和鸣叫,体现了“多用组合,少用继承”的设计原则。

四、策略模式的优点

1、策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。

2、使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

五、应用场景

1、 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
2、 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
3、 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
具体的应用场景有:
1、游戏中切换武器,角色类只需持有武器接口的一个引用,就可在游戏过程中动态地通过set方法切换武器;
2、应用切换主题或配色,同上;
3、电子商务网站中的折扣系统,对于不同的会员有不同的折扣,计算价格的类只需持有一个折扣的引用,则可动态地根据会员等级高低计算价格。
以折扣系统为例,代码如下:
/**
 * 会员超类
 * @author miner
 *
 */
public interface BaseMember {
	
	double calPrice(double price);

}
/**
 * 非会员
 * 无折扣
 * @author miner
 *
 */
public class NonMember implements BaseMember{
	
	public double calPrice(double price) {
		return price;
	}

}
/**
 * 初级会员
 * 九五折
 * @author miner
 *
 */
public class PrimaryMember implements BaseMember{
	
	public double calPrice(double price) {
		return price*0.95;
	}

}
/**
 * 高级会员
 * 九折
 * @author miner
 *
 */
public class SeniorMember implements BaseMember{
	
	public double calPrice(double price) {
		return price*0.9;
	}

}
/**
 * 至尊会员
 * 八折
 * @author miner
 *
 */
public class SupremeMember implements BaseMember{

	public double calPrice(double price) {
		return price*0.8;
	}

}
/**
 * 价格类
 * @author miner
 *
 */
public class Price {
	
	protected BaseMember member;
	
	public Price() {
		member = new NonMember();    //默认为非会员
	}
	
	//折后价格
	public double discountPrice(double price) {
		return member.calPrice(price);
	}
	
	public void setMember(BaseMember member) {
		this.member = member;
	}

}

测试类:

public class Test {
	
	public static void main(String[] args) {
		Price price = new Price();
		double p1 = price.discountPrice(300);
		System.out.println("非会员价格为:" + p1);
		price.setMember(new SeniorMember());
		double p2 = price.discountPrice(300);
		System.out.println("高级会员价格为:" + p2);
	}

}

输出:

非会员价格为:300.0
高级会员价格为:270.0

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注