设计模式--策略模式(Strategy Pattern)

粗俗的理解

  一个好的程序能根据不同的情况容易的改变一些行为,而不用大面积的修改代码。并且具有良好的可扩展性

一个你可能使用过的例子

在Java或者是Android中的LayoutManager。

View.setLayoutManager(gridLayout);

  如果我们在以后突然有了新的需求,要改变布局样式,是不是只需要修改setLayoutManager的参数即可,而不用大面积的修改我们的程序。

问题引入

  这里引入一个非常经典的Duck(鸭子)问题。完整篇可以参考《Head First Design Patterns》。
  有一个鸭子游戏,其中很多种不同的鸭子,比如,绿头鸭,橡皮鸭,木头鸭等等。他们之间有很多共同的方法,所以要实现这些鸭子,我们可以定义一个父类,分别让这些不同种类的鸭子继承,来减少代码量。
  比如我们可以在父类中定义飞行,嘎嘎叫,浮水等。
  看起来好像很完美,这的确是我们的正常思路,但是在实际实现的时候好像出现了一些问题,木头鸭也能飞吗?橡皮鸭也会嘎嘎叫吗?橡皮鸭叫起来是吱吱~的。好像重写父类的方法就能解决问题,但如果有更多不同种类的鸭子加入进来,那么重复代码将会变得非常多,系统的逻辑也会变得非常乱。这在面向对象的实现中是不合理的。
  上面我们分析得出飞行和叫的方法会随着不同的鸭子种类改变而改变,所以我们把它抽离出来可不可以呢?
分析:
  建立一个鸭子的抽象父类
  因为飞行和叫的行为需要多种变化,所以建立他们的接口,然后针对每一种行为具体实现对应的算法。

动手实现

按照我们的思路,建立一个抽象父类Duck.java

public abstract class Duck {
	public Duck() {};//构造方法
	
	/*
	 *  因为每种类型的鸭子都需要显示,但是显示效果都不一样,所以定义成了抽象方法,每一个继承于这个类的子类,都需要强制实现该方法。 
	 *  */
	public abstract void display();
	
	/*
	 *  每一种鸭子都能浮水,所以直接实现了该方法。
	 *  */
	public void swim() {
		System.out.println("All ducks float,even decoys(所有的鸭子都能浮在水面上,包括诱饵鸭)");
	}
}

下面我们来设计飞行和叫的功能。
建立飞行接口,FlyBehavior.java

public interface FlyBehavior {
	public void fly();
}

建立叫的接口,QuackBehavior.java

public interface QuackBehavior {
	public void quack();
}

实现会飞的飞行类。
建立FlyWithWings 类,实现FlyBehavior 接口

public class FlyWithWings implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I'm flying!!");
	}
}

因为有的鸭子不会飞,例如木头鸭,所以再实现一个不会飞的飞行类。
建立FlyNoWay 类,实现FlyBehavior 接口

public class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I can't fly……");
	}
}

实现嘎嘎叫的接口。
建立Quack类,实现QuackBehavior接口

public class Quack implements QuackBehavior {

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("Quack");
	}
}

实现吱吱~叫的接口,给橡皮鸭使用。
建立Squeak,实现QuackBehavior接口。

public class Squeak implements QuackBehavior {

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("Squeak~");
	}
}

接口都建立好了之后,我们再回到Duck.java,这个抽象父类。
之前知识实现了几个所有类型的鸭子共有的方法,现在我们利用多态来实现飞行和叫的动作。

  1. 首先声明两个接口分别声明两个引用变量。
  2. 然后声明两个setter方法,接收鸭子类传过来的行为。然后赋给两个接口变量。
  3. 定义两个方法,利用多台展现相应的行为。
public abstract class Duck {
/* 1. 首先声明两个接口分别声明两个引用变量。 */
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;
	
	public Duck() {};//构造方法
	
	/*
	 *  因为每种类型的鸭子都需要显示,但是显示效果都不一样,所以定义成了抽象方法,每一个继承于这个类的子类,都需要强制实现该方法。 
	 *  */
	public abstract void display();
	
	/*
	 *  每一种鸭子都能浮水,所以直接实现了该方法。
	 *  */
	public void swim() {
		System.out.println("All ducks float,even decoys(所有的鸭子都能浮在水面上,包括诱饵鸭)");
	}
/*3. 定义两个方法,利用多台展现相应的行为。 */
	public void performFly() {
		flyBehavior.fly();
	}
	public void performQuack() {
		quackBehavior.quack();
	}
/* 2. 然后声明两个setter方法,接收鸭子类传过来的行为。然后赋给两个接口变量。 */
	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}
	public void setQuackBehavior(QuackBehavior qb) {
		quackBehavior = qb;
	}
}

实现一下普通鸭子的,继承抽象父类

public class NormalDuck extends Duck {
	/* 在构造方法里实现父类引用接口变量的具体行为类 */
	public NormalDuck() {
		flyBehavior = new FlyWithWings();
		quackBehavior = new Quack();
	}

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("I'm a normal duck");
	}
}

再来一个橡皮鸭类,继承一下抽象类

public class RubberDuck extends Duck {
	public RubberDuck() {
		flyBehavior = new FlyNoWay();//因为橡皮鸭不会飞,所以生成的是FlyNoWay不会飞实例。
		quackBehavior = new Squeak();//因为橡皮鸭不会飞,所以生成的是Squeak吱吱叫的实例。
	}

	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("I'm a rubber duck");
	}
}

整个算法的设计已经完成了,接下来我们建立测试类看一下效果。

public class MiniDuckSimulator {

	public static void main(String[] args) {
		
		/* 生成一个抽象父类的普通鸭对象,用具体的橡皮鸭类实现 */
		Duck normalDuck = new NormalDuck();
		normalDuck.display();
		normalDuck.performQuack();
		normalDuck.performFly();
		/* 生成一个抽象父类的橡皮鸭对象,用具体的橡皮鸭类实现 */
		Duck rubberDuck = new RubberDuck();
		rubberDuck.display();
		rubberDuck.performQuack();
		rubberDuck.performFly();
	}
}

运行输出结果:

I'm a normal duck
Quack
I'm flying!!
I'm a rubber duck
Squeak~
I can't fly……

  我们已经利用多台的原理实现了策略模式,当我们生成鸭子对象时,只需要根据不同的鸭子类型选择不同的实现算法即可。
  我们好像发现了,Duck这个抽象父类里面还有两个方法没有使用。

	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}
	public void setQuackBehavior(QuackBehavior qb) {
		quackBehavior = qb;
	}

要怎么使用呢?如果我们想要橡皮鸭也实现嘎嘎叫要怎么做呢?
修改测试类的代码:

public class MiniDuckSimulator {

	public static void main(String[] args) {
		
		/* 生成一个抽象父类的普通鸭对象,用具体的橡皮鸭类实现 */
		Duck normalDuck = new NormalDuck();
		normalDuck.display();
		normalDuck.performQuack();
		normalDuck.performFly();
		/* 生成一个抽象父类的橡皮鸭对象,用具体的橡皮鸭类实现 */
		Duck rubberDuck = new RubberDuck();
		rubberDuck.display();
		/* 添加这一句 */
		rubberDuck.setQuackBehavior(new Quack());
		rubberDuck.performQuack();
		rubberDuck.performFly();
	}
}

输出结果:

I'm a normal duck
Quack
I'm flying!!
I'm a rubber duck
Quack
I can't fly……

这样就改变了橡皮鸭的吱吱叫行为,变成了嘎嘎叫。
在这个过程中我们并没有大面积的修改代码,知识添加了一行,选择了另外的算法而已。
这像不像我们在Java或者是Android中为LayoutManager设置布局的操作。
在这个例子的基础上,可以自己试着扩展一下其他类型的鸭子。

定义

通过这个例子后,给出比较官方的定义:

策略模式属于对象行为设计模式,大大降低了代码的耦合度。主要定义一组算法,将每个算法都分别封装起来,并且他们直接可以互换,互不影响。此模式让算法的变化独立于使用算法的用户。
(这里的算法并不是我们数据结构与算法之中的算法,可以理解为不同业务的处理方法)

优缺点

  • 优点
    1. 可以在各个算法之间自由切换。
    2. 避免了条件判断
      如果没有策略模式,需要在程序中频繁的切换各个算法,那么需要条件判断在什么时候该用哪种算法,有了策略模式之后我们就不用了。
    3. 扩展性良好
      可以随意的扩展算法,不用大面积的修改程序。
  • 缺点
    1. 策略类的数量增多
      每一个算法,都对应这一个策略类。因此如果当算法超过4个的时候,我们需要重新思考怎样改良,和其他的设计模式混用,或者是否继续使用策略模式。
    2. 所有的策略都暴露在外

什么时候该用呢?

  • 多个类只有在算法或行为上稍有不同的场景
  • 算法需要自由切换的场景
  • 需要屏蔽算法规则的场景

  当然定义说的再规范也决定不了在实际开发中的具体使用,所以要做到理解各种设计模式,才能在编码中做到游刃有余。

你可能感兴趣的:(Design,Patterns)