Java设计模式----------策略模式

注:本篇博客内容出自Head First设计模式,仅对文章进行整理与总结。


1.引出问题

现在我们有一个鸭子的基类:

public class Duck {
	public void quack() {
		System.out.println("Duck quack");
	}
	
	public void apperence() {
		System.out.println("Duck apperence");
	}
}

假设我们需要为每个鸭子的子类添加一个fly,让我们的鸭子会飞,并修改每个子类,实现不同的quack功能,毕竟鸭子也不都是一样叫的。在此,我们看一下两种解决方案:

方案一 :

我们可以像quack功能一样,在Duck基类添加一个fly方法。

public class Duck {
	public void quack() {
		System.out.println("Duck quack");
	}
	
	public void apperence() {
		System.out.println("Duck apperence");
	}
	
	public void fly() {
		System.out.println("Duck fly");
	}
}

上述的方法实际是通过java的继承特性实现了想要的需求,如果仅仅从需求的角度来看,我们的确实现了所有鸭子的子类均有fly的功能,但实际情况是,某些鸭子是不会飞的,比如浴缸里放着的橡皮鸭,小孩子的玩具鸭,用于当作诱饵的诱饵鸭等。

你可能想说,我在每个子类中覆盖相应的fly方法,实现特定的飞行方式,不会飞的情况什么也不做就行了,那么以后每写一个子类就要覆盖相应的方法,如此一来不仅增加了前期开发工作量,也加大后期添加/修改功能的工作量,并且还得注意别改出bug,这样的话就使得代码的维护成本大幅提高,以上代码只是添加了一个fly功能就如此麻烦,那如果需要添加更多功能呢,你怕不是要天天加班了,过上12+6的幸福生活了。

可见,对代码的局部修改,影响的成面可不是局部的。


方案二:

我们设计一个Flyable并把quack方法拿出来单独放到Quackable的接口:

public interface Flyable {
	public void fly();
}

public interface Quackable {
    public void quack();
}
让有飞行功能的子类实现Flyable接口,类似下面子类:
public class MallardDuck implements Flyable ,Quackable{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("Fly with wings");
	}

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


这种解决方案相较于方案一,大大减少了我们的工作量,但是还是存在一个问题。我们知道鸭子种类千千万,但飞行的方式不可能千千万,所以有些飞行方式是一样的,这就导致同一个功能我们可以会去写很多遍。

从开发者的角度来说,这无疑造成了代码无法复用。我们程序员的宗旨就是不做重复且毫无意义的编程。


2. 解决问题

有没有即降低工作量,方便后期开发维护,又能实现代码高度复用的方法呢,答案是肯定的,那就是本篇文章即将要介绍的一种设计模式---------策略模式。

先看一下策略模式的定义:

策略模式:定义了算法族,分别封装起来,让他们之间可以相互替换。此模式让算法的变化独立于使用算法的客户。

现在先不对定义做什么解释,稍后再做说明,我们继续往下看。


2.1 问题分析

从上文我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里会不断的改变。Flyable和Quackable接口虽然解决了问题,但是java接口不具有实现代码,所以继承接口无法达到代码服用的目的,这就意味着:无论何时你要修改修改某个行为,你就必须得往下追踪并在每一个定义此行为的类中修改他,同时还要避免修改出新的错误。

从上面的分析,我们引出一个新的设计原则,那就是:

              找出应用中需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。

另一种更通俗的说法就是:把会变化的部分取出并封装起来,一变以后可以轻易的改动或扩充此部分代码,而不影响不需要变化的其他部分。


首先,我们知道Duck类内部的fly和quack会随着鸭子的不同而改变,为了把这两个行为从Duck类里分开,我们把它们从Duck类中单独拿出来,建立一组新的类来表示不同的行为。比如,建立一个新的类表示“呱呱叫”,另一个新的类表示“嘎嘎叫”等等。


其次,如何设计新的类呢?我们希望一切都能够有弹性,毕竟之前的设计太过于死板,导致工作量居高不下,维护等一系列问题。我们希望能够指定具体的行为到鸭子的实例。比如,设计一个新的鸭子子类,并指定特定的行为给他。最好我还能在程序运行的动态的改变子类的行为,在不同情况下指定不同的行为给指定的子类。

由此,我们引出第二个设计原则,那就是:

            针对接口编程,而不是针对实现编程。

 针对接口编程真正的意思是针对某个超类型编程。关键就是在java的多态技术,利用多态,程序可以针对超类型编程,执行时会根据实际状态执行到真正的行为,不会被绑死在超类型的行为上。


2.2 代码实现

现在联系代码做进一步说明。

结合上面的分析,我们把变化的部分提取出来,封装在接口里面,并实现特定行为类组,具体如下:

public interface FlyBehavior {
	public void fly();
}

public interface QuackBehavior {
	public void quack();
}

首先我们将quack和fly方法从Duck中取出,分别封装在QuackBehavior和FlyBehavior中,然后我们实现特性的行为类组:

以下是QuackBehavior的行为类组:

//叫法一
public class Quack implements QuackBehavior {

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

}

//叫法二
public class MuteQuack implements QuackBehavior {

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

}

//叫法三
public class Squeak implements QuackBehavior {

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

}
以下是FlyBehavior的行为类组:
// 飞行方法一
public class FlyWithWings implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("Fly with wings");
	}

}

//飞行方法二
public class FlyNoWay implements FlyBehavior {

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

}

现在,“工具类”我们都已经准备好了,就差整合一个Duck的行为了。关键在于,Duck现在应该将fly和quack的动作“委托”给“工具类”来处理,而不是自己处理。

下面是经过整合之后的Duck基类:

public abstract class Duck {
	
	private FlyBehavior flyBehavior;
	private QuackBehavior quackBehavior;
	
	public Duck() {
	}
	
	public void performFly() {
		flyBehavior.fly();
	}
	
	public void performQuack() {
		quackBehavior.quack();
	}
	
	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}
	
	public void setQuackBehavior(QuackBehavior quackBehavior) {
		this.quackBehavior = quackBehavior;
	}
	
	public abstract void apperence();
}

现在对以上代码进行说明:

1. 我们将Duck有具体类变为抽象类,并将appernece方法改为abstract抽象方法,这是因为Duck类是一个基类,我们希望所有的鸭子子类都能继承这个基类,并让子类在实例化的时候通过apperence做一些解释说明。

2. 我们添加了FlyBehavior,QuackBehavior成员变量和setFlyBehavior,setQuackBehavior方法。添加成员变量的根据就是上面提到的针对接口变成,添加方法是为了指定具体的行为到具体的Duck子类。


接下来我们写一些测试代码,为此我们先创建一个鸭子子类:

//绿头鸭
public class MallardDuck extends Duck{

	public  MallardDuck(FlyBehavior flyBehavior ,QuackBehavior quackBehavior) {
		// TODO Auto-generated constructor stub
		this.flyBehavior = flyBehavior;
		this.quackBehavior = quackBehavior;
	}
	
	@Override
	public void apperence() {
		// TODO Auto-generated method stub
		System.out.println("Mallard duck");
	}
}
测试代码如下:
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FlyBehavior flyBehavior = new FlyWithWings();
		QuackBehavior quackBehavior = new Squeak();
		
		Duck duck = new MallardDuck(flyBehavior, quackBehavior);
		
		duck.performFly();
		duck.performQuack();
	}

//运行结果:
Fly with wings
Squeak Squeak Squeak

首先,变量的命名都是以接口或者是抽象类的方式,而实际实例化的则是具体的子类对象,这就是针对接口编程而不是针对具体实现编程。其次,调用的fly以及quack方法是通过基类中的函数实现的,没有将具体的实现绑定在子类里,这样做增加了代码的灵活性以及降低了代码之间的耦合性,我们只需要调用基类的perform***方法,之后就会执行到具体的行为。


现在我们尝试在代码运行的过程中改变子类的行为,我们来做一个机器鸭,并设计一种新的飞行方式:

public class RobotDuck  extends Duck{

	public RobotDuck(FlyBehavior flyBehavior ,QuackBehavior quackBehavior) {
		// TODO Auto-generated constructor stub
		this.flyBehavior = flyBehavior;
		this.quackBehavior = quackBehavior;
	}
	
	@Override
	public void apperence() {
		// TODO Auto-generated method stub
		System.out.println("Robot Duck");
	}
public class FlyWithRocket implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("Ooooooh......, fly with rocket so cool");
	}

}

测试类如下:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FlyBehavior flyBehavior = new FlyNoWay();
		QuackBehavior quackBehavior = new MuteQuack();
		FlyBehavior newFlyBehavior = new FlyWithRocket();
		
		Duck duck = new RobotDuck(flyBehavior, quackBehavior);
		duck.performFly();
		duck.performQuack();
		
		System.out.println("\n now we change to use rocket to fly\n");
		duck.setFlyBehavior(newFlyBehavior);
		duck.performFly();
		duck.performQuack();
		
	}

//运行结果:
I can't fly
<< Be Slience>>

 now we change to use rocket to fly

Ooooooh......, fly with rocket so cool
<< Be Slience>>


首先机器鸭不能叫也不能飞,然后我们通过setFlyBehavior的基类方法在程序运行过程中改变了子类的飞行方式,使它使用火箭推进器进行飞行。


现在让我们回头看一下策略模式的定义:

策略模式:定义了算法族,分别封装起来,让他们之间可以相互替换。此模式让算法的变化独立于使用算法的客户。
结合以上例子,这里的算法族就是指实现了fly和quack的接口的各种具体的行为,他们之间可以相互替换且不会影响客户端代码,提高了代码的复用性以及灵活度,降低了代码之间的耦合性以及开发维护的成本,这就是策略模式。


以上例子只是用来简述策略模式的例子,实际开发中遇到的情景不可能完全相同,但是万变不离其宗,打好基础才是最主要的。本篇文章到此结束,谢谢。



猜你喜欢

转载自blog.csdn.net/purple7826/article/details/80289282