设计模式——策略模式
策略,指可以实现目标的方案集合。在后期维护程序时,常常会被要去给当前已经设计好的继承结构增加新的功能,这时,对基层类的微小修改常常会使导出类发生意想不到的错误。有效应对后期维护时需求的扩展,这是策略模式产生的契机。
1.情景举例
在一个古老的手机销售网站里,出售着各种各样的手机。这个网站在2000年就有了,当时的手机,功能非常的局限,设计师设计手机的基类时,只提供了一个打电话的方法。
public abstract class Phone {
public abstract void ringUp(String phoneNumber);
}
在当时的社会中,很多手机确实也只有一个打电话的功能,于是产生了各种各样的导出类。比如:
public class NormalPhone extends Phone{
@Override
public void ringUp(String phoneNumber) {
System.out.println("I made a phone call to " + phoneNumber);
}
}
但在手机快速的发展下,手机最基本的功能已经不再只有打电话了。所以该网站维护人员想要对Phone类进行一个扩展,让它具备一个指纹解锁功能。
2.通往策略模式的道路
从以下几个方面进行思考:
Q:在基类里加上一个指纹解锁的方法
A:这样确实所有导出类都有指纹解锁功能了,但是那些旧型号的手机和一些面容解锁的手机确实没有指纹解锁功能啊,这样的修改让那些手机很难受啊。而且针对不同的实现类需要重写该方法,这样就会产生大量的重复代码。
Q:定义一个拥有指纹解锁方法的接口,让每个有指纹解锁的手机继承它
A:接口里的方法是不能有实现的,每一个实现这个接口的手机类都要重新编写该指纹解锁方法。这样做的话我们就无法通过基类Phone去多态使用这个方法了,并且在实现的过程中也有大量的重复代码,就算是复制粘贴也会累死人的啊。
3.使用策略模式
用策略模式尝试一下吧!
怎么做呢?
首先需要定义一个拥有指纹解锁方法的接口
public interface UnLock {
void fingerprintUnlock();
}
接着把指纹解锁方法当做一个方案,想象一下,指纹解锁的方法可能有几种实现(几个方案)。
有“按压屏幕下方识别区解锁”,“按压手机背面识别器解锁”,“无指纹解锁”等等
然后根据实现去创建相应的实现类。
public class PressScreenBottomUnlock implements UnLock{
//按压手机屏幕下方识别区解锁
@Override
public void fingerprintUnlock() {
System.out.println("Press the bottom of the screen to unlock the phone");
}
}
public class NoFingerprintUnlock implements UnLock{
//没有指纹解锁功能
@Override
public void fingerprintUnlock() {
System.out.println("Sorry....I can not do");
}
}
然后让基类Phone拥有一个 Unlock类型的变量和一个指纹解锁的功能方法,在方法中调用Unlock类型变量的指纹解锁方法。这样我们就把指纹解锁这个功能委派给Unlock类型变量去实现,基类中原有的部分没有发生改变,而且每一个指纹解锁的具体实现只用编写一个实现类就ok,避免了很多的重复代码。最关键的一点是我们可以通过基类Phone进行多态调用指纹解锁功能。
public abstract class Phone {
UnLock fingerprintUnlock;
public abstract void ringUp(String phoneNumber);
public void fpUnlock() {
//调用Unlock接口的方法
fingerprintUnlock.fingerprintUnlock();
}
}
最后只需要在新的手机实现类的构造方法中设置相应的指纹解锁方案即可
public class Iphone6 extends Phone {
public Iphone6() {
fingerprintUnlock = new PressScreenBottomUnlock();
}
@Override
public void ringUp(String phoneNumber) {
System.out.println("I made a phone call to " + phoneNumber);
}
}
测试
public class MainTest {
public static void main(String[] args) {
Phone phone = new Iphone6();
phone.fpUnlock();
}
}
这样就大工告成了。
4.策略模式小结
策略模式的大体步骤就是
1.提取想要增加的功能封装为接口
2.按照需求为接口制作多个实现类
3.在基类中增加接口类型的变量和增加相应功能方法去调用接口方法
4.导出类初始化时根据自身需求给基类中的接口类型变量赋值。
在使用了策略模式之后,避免了大量的重复代码,并且在没有破坏继承体系的前提下优雅地插入新的功能,可以说是完美的解决了问题。而且Unlock类型是作为变量存在于Phone类中的,所以完全可以为它编写相应的setter方法,这样在运行中我们就可以动态的改变指纹解锁的方案了。
策略模式是一个思想,它把算法和实体类分离封装,然后通过组合的方式动态给实体添加(更换)算法,这既减小了耦合度,又增加了可扩展性,是一个非常好的模式。
5.关于策略模式的问题
Q:我在基类中增加了新的变量和方法,但是在原有的导出类中并没有给Unlock类型变量赋值,这样我在之前的导出类中使用新方法时会出现空指针错误,这怎么办啊?
A:之前的代码都是在没有新变量,新方法的基础上编写的,所以基类修改后原有代码并不会出错。以后再使用旧的导出类时可以通过setter方法手动给Unlock类型变量赋值,这样就在调用方法时不会出现空指针错误了。或者在为基类增加接口类型变量时直接赋一个初始值,也可以解决问题。
Q:策略模式仅仅是在后期维护的时候才能用吗?
A:后期维护时因为继承体系已经形成,在庞大的继承体系中修改部分内容并且把对整个体系的影响减少到最小是特别困难的,策略模式是相对解决这个问题的一个较好的方案。由于已经存在的导出类中并没有对新变量进行设置,所以要手动增加策略,这是策略模式一个不太优雅的地方。如果能在项目设计初期就多考虑以后维护时增加的需求,尽可能的多提前加入策略模式,就能在后期项目维护时获得更好的扩展性和伸缩性,因此,策略模式在项目设计时加入其实是更好的选择。