不管你在何处工作,构建些什么,用何种编程语言,在软件开发上,一直有条颠扑不破的真理:成长与改变。
不管软件设计的多好,一段时间之后,总是需要成长与改变,否则软件就会死亡。对于此,首先有第一条设计原则:
封装变化
即将应用中需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。除此在外,我们还希望我们的代码易于维护和扩展,更加富有弹性,对于此,有第二条设计原则:
面向接口编程,而不是针对实现编程
基本所有的设计模式都符合上述两条设计原则,现在,我们将要在此结合例子讲述策略模式
策略模式
定义:策略模式定义了算法族,分别封装起来,让它们可以相互替换,此模式让算法的变化独立于使用算法的客户。
即定义一个算法接口,让具体的算法类实现该接口,在客户角色中仅依赖该接口来使用算法,并不关心算法的具体实现。
UML类图
其中可以看到我们对定义的理解:
- 算法接口:通常为一个抽象类或者接口
- 算法实现类:实现了算法接口的具体算法
- 环境(客户):依赖于算法接口
例子
现在有名为鸭子的父类,有叫,游泳以及外观这三个方法,红头鸭和绿头鸭继承了父类,并重写了外观方法。
package strategy;
public class Duck {
public void quack(){}
public void swim(){}
public void display(){}
}
package strategy;
public class MallardDuck extends Duck{
public void display(){
//外观是绿头鸭
}
}
package strategy;
public class RedheadDuck extends Duck {
public void display(){
//外观是红头鸭
}
}
如果现在我们想让鸭子飞,那么在父类中再实现飞的方法就行,但是,如果增加了子类橡皮鸭,橡皮鸭也会飞了(实际上这种玩具可不会飞)。那么我们可以通过覆盖掉子类中的这个方法来让橡皮鸭不会飞,那要是我们再增加了木工鸭,既不会飞也不会叫,是不是又要重新覆盖这些代码呢?再增加新类呢?这么看来的话,代码的复用性就很差了。
我们或许可以为这些行为提供一个接口,如为叫提供一个叫行为接口,游泳提供一个游泳行为接口,让鸭子们实现这些接口就行了。然而这么做的后果是,随着鸭子种类的增多,对接口中方法的实现将使我们疲于奔命。
这时候策略模式就显示出它的威力了。
首先,我们定义接口,以飞接口和叫接口为例。
package strategy;
/**
* Strategy接口
*/
public interface FlyBehavior {
void fly();
}
package strategy;
/**
* Strategy接口
*/
public interface QuackBehaviour {
void quack();
}
之后根据鸭子具体叫/飞的行为,定义具体实现类,如会飞,或者不会飞。
package strategy;
/**
* 具体的会飞行为
*/
public class FlyWithWings implements FlyBehavior {
@Override
public void fly(){
System.out.println("I believe I can fly!");
}
}
package strategy;
/**
* 具体的不会飞行为
*/
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly!");
}
}
整合鸭子行为
package strategy;
/**
* 将行为实现类加入到父类中
* 将具体实现委托给行为实现
*/
public class Duck {
QuackBehaviour quackBehaviour;
FlyBehavior flyBehavior;
public void performQuack(){
quackBehaviour.quack();
}
public void performFly(){
flyBehavior.fly();
}
// public void quack(){}
public void swim(){}
public void display(){}
}
package strategy;
/**
* 绿头鸭
*/
public class MallardDuck extends Duck{
public MallardDuck(){
quackBehaviour = new Quack();
flyBehavior = new FlyWithWings();
}
public void display(){
//外观是绿头鸭
System.out.println("I'm a real mallard duck");
}
}
我们看到我们讲具体行为/算法 交给了算法的持有者,由持有者来运行,但是它并不关心算法的具体实现,即将实现委托给了具体的行为/算法实现。
动态的设定行为
实现set方法即可
public void setFlyBehavior(FlyBehavior fb){
flybehavior = fb;
}
第三条设计原则
在上面这个例子中,我们发现,继承并不是万能的,它有时候反而给代码的复用,维护造成了负面的作用。
那么该如何扩展我们的行为呢?
多用组合,少用继承
使用组合建立系统将具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态的改变行为,只要组合的行为对象符合正确的接口标准即可,同时,组合也用在了许许多多的设计模式之中,在接下来的设计模式中,我们讲体会到它的诸多优点与缺点。
源码在这里:我的github地址