设计模式之策略模式

策略模式(Strategy Pattern)

我们先通过一个例子来引出策略模式:

猴子问题:有各种各样的猴子,婴猴,长臂猿,玩具猴等,猴子有各种行为,比如叫爬树、叫、直立行走等;现在我们需要对各种猴子进行一个设计,使得每种猴子都可以显示自己的种类信息。

编码如下:

//抽象的猴子,包含猴子普遍拥有的行为
public abstract class Monkey {

	public abstract void show();
	public void quack(){
		System.out.println("猴子会叫~");
	}
	public void walk(){
		System.out.println("猴子会直立行走~");
	}
	public void climb(){
		System.out.println("猴子会爬树~");
	}
}
//婴猴,会爬树会叫不会直立行走
public class ConcreteMonkey1 extends Monkey{

	@Override
	public void show() {
		System.out.println("这是婴猴……");
	}
	//由于婴猴不会直立行走,所以必须重写父类的walk方法
	@Override
	public void walk(){
		System.out.println("婴猴不会直立行走~");
	}
}
//长臂猿,会爬树会叫会直立行走
public class ConcreteMonkey2 extends Monkey{

	@Override
	public void show() {
		System.out.println("这是长臂猿……");
	}
}
//玩具猴,不会爬树不会叫不会直立行走
public class ConcreteMonkey3 extends Monkey{

	@Override
	public void show() {
		System.out.println("这是婴猴……");
	}
	//由于玩具猴不会爬树不会叫不会直立行走,所以必须重写父类的所有方法
	@Override
	public void walk(){
		System.out.println("玩具猴不会直立行走~");
	}
	@Override
	public void quack() {
		System.out.println("玩具猴不会叫~");
	}
	@Override
	public void climb() {
		System.out.println("玩具猴不会爬树~");
	}
}

分析以上代码出现的问题:上面的代码中是对猴子类做了一层抽象,定义了猴子类普遍拥有的行为,再让具体的猴子去实现它即可,我们知道,子类继承了父类,就拥有了父类的所有方法,那么就存在这样一个问题,对于婴猴是不会直立行走的,而它继承了父类就变成会直立行走了,显然是不科学的,因此我们需要在子类中覆盖父类的walk方法,也就是重写了父类的方法,这其实已经违反了里式替换原则;更糟糕的一种情况是对于玩具猴来说,它需要重写父类的所有方法。

进而分析问题的本质,在这个例子中,对于猴子的一些行为,不同种类的猴子可能有不同的表现,例如对于爬树这个行为,有的猴子不会爬树、有的猴子爬的一般、还有的猴子爬树非常好,也就是说对于同样的一个行为,有不同的表现。由此引出我们的策略模式,策略模式可以非常巧妙的解决我们这个问题。

什么是策略模式?

在阎宏博士的《JAVA与模式》一书中开头是这样描述策略(Strategy)模式的:

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

策略模式的类图:

在这里插入图片描述

这个模式涉及到三个角色:

  • 环境(Context)角色:持有一个Strategy的引用,至于需要使用到哪个策略,我们可以在构造器中指定。

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

  • 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

使用策略模式解决上面的猴子问题(为了减少代码篇幅,这里仅描述爬树这个行为)

//策略接口:定义爬树行为
public interface ClimbBehavior {
	void climb();
}
//具体的策略
public class GoodClimbBehavior implements ClimbBehavior{
	@Override
	public void climb() {
		System.out.println("爬树技术高超~");
	}
}
//具体的策略
public class NoClimbBehavior implements ClimbBehavior{
	@Override
	public void climb() {
		System.out.println("不会爬树~");
	}
}
public abstract class Monkey {
	public ClimbBehavior climbBehavior;//聚合策略接口
	public abstract void show();
	public void climb(){
		climbBehavior.climb();
	}
}
public class ConcreteMonkey1 extends Monkey{
	public ConcreteMonkey1() {
		climbBehavior=new GoodClimbBehavior();
	}
	@Override
	public void show() {
		System.out.println("这是长臂猿,会爬树……");
	}
}
public class ConcreteMonkey2 extends Monkey{
	public ConcreteMonkey2() {
		climbBehavior=new NoClimbBehavior();
	}
	@Override
	public void show() {
		System.out.println("这是玩具猴,不会爬树……");
	}
}
public class Client {
	public static void main(String[] args) {
		Monkey monkey1=new ConcreteMonkey1();
		monkey1.show();
		monkey1.climb();
		System.out.println("============================");
		Monkey monkey2=new ConcreteMonkey2();
		monkey2.show();
		monkey2.climb();
	}
}

显然策略模式巧妙的解决了猴子问题,可能有人有这样想,既然对于猴子的行为,不同种类的猴子具有不同的表现,那么为何不直接将这些行为定义成抽象的方法,让每个子类分别去实现即可。的确,这样做确实是可行的,但是我们回想一下,为什么我们要面向抽象编程,一方面是为了满足ocp原则,还有一方面是为了实现代码的一个复用,我们希望将一些子类普遍拥有的行为定义在父类(抽象类/接口)中,这样子类就可以直接拥有父类的代码,而如果解决猴子问题使用的方法是将这些行为定义成抽象的方法,让每个子类分别去实现即可,那么每一个猴子类都要去实现所有的行为方法,岂不是得不偿失了!

策略模式的优缺点

优点:
  1. 策略模式的核心思想是:多用组合/聚合,少用继承;用行为类组合,而不是行为的继承。更有弹性

  2. 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else)

  3. 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改
    变它,使它易于切换、易于理解、易于扩展

缺点:
  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况
  2. 由于策略模式把每个具体的策略实现都单独封装成为类,每添加一个策略就要增加一个类,当策略过多是会导致类数目庞(类爆炸)

关于策略模式在jdk源码中的应用,可以参考我另一篇博客:
https://blog.youkuaiyun.com/can_chen/article/details/106749912

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值