最近看完了《HeadFirst设计模式》这本书,想趁着这个机会写点东西总结总结,因此准备开始从今天开始将各种设计模式捋一遍,记录下来为有需要的同学当做参考。
闲话少叙,今天首先来说一下策略模式。首先给出策略模式的定义:
**策略模式定义了算法蔟,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。**
假设你想开发一个模拟鸭子的游戏,其中有各式各样的鸭子,鸭子拥有各种行为,因此你设计一个鸭子的超类,然后各种鸭子继承此超类。
public abstract class Duck {
abstract public void quak(); //这些方法为鸭子的各种行为,包括游泳、呱呱叫等
abstract public void swim();
abstract public void display();
}
class MallardDuck extends Duck{
void display(){ //....实现 该鸭子为绿头鸭,展示起来头为绿色的
}
}
class RedHeadDuck extends Duck{
void display(){ //....实现 该鸭子是红头的,展示起来为红色
}
}
现在你的鸭子功能模拟器的功能增加了,现在需要鸭子会飞,因此你可能会想到在抽象基类加一个fly()方法。
public abstract class Duck {
abstract public void quak(); //这些方法为鸭子的各种行为,包括游泳、呱呱叫等
abstract public void swim();
abstract public void display();
abstract public void fly(); //新增飞翔行为
}
此时抽象基类继承的问题就暴露出来了,因为各个鸭子都继承了基类的抽象方法,假设现在有一只橡皮鸭子也继承了该基类,如下所示。
/**
*各个子类都要实现抽象基类中的抽象方法,表示为自己本身的实现。
*/
class RubberDuck extends Duck{
void display(){};
void swim(){};
void quak(){};
void fly(){// 可以对其进行空实现,什么也不做
};
}
很明显橡皮鸭子是不会飞的,但是它仍然要实现你定义在抽象基类中的方法。当然你可以在fly()方法中对其进行空实现,什么也不做。
但是如果以后加入其他的鸭子,比如说诱饵鸭,这是一种假鸭子不回飞也不会叫,你就需要对其中的多个方法进行空实现。
class DecoyDuck extends Duck{
void display(){
};
void swim(){};
void quak(){
// 覆盖,但是空实现
};
void fly(){// 可以对其进行空实现,什么也不做
// 覆盖,但是空实现
};
}
但是如果我们以后要开发出越来越多种类的鸭子怎么办呢,这些新鸭子可能会有一些新的行为,如果我们每次向基类里添加一个新的行为,那我们都需要去所有这个基类的继承子类中去实现新的行为方法,而有的行为并不是所有鸭子共有的,这种每次新增行为都大量去更改已有代码显然是不能接受的。
那如果把飞行行为和叫声行为抽离出来成单独的接口是否可行呢?
public abstract class Duck {
abstract public void swim();
abstract public void display();
}
public interface Quackable{
void quack();
}
public interface Flyable{
void fly();
}
class RedHeadDuck extends Duck implements Quackable,Flyable{
void fly(){};
void quack(){};
void display(){};
void swim(){};
}
虽然这可以解决不再有会飞的鸭子这种现象,但是其中很多的方法比如说fly()方法,需要重复写很多代码,因为鸭子的飞行差不多都一样,如果我们有几十个实现子类,那么需要写几十次飞行方法,造成大量的重复代码。
我们的业务可能随时应对变化,因此要尽量将自己的代码能够应对变化性,在变化的时候能够尽量修改较少的代码。此时就对应到了设计模式的一个***设计原则,***我称为此原则为动静分离:**找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要辩护的代码混在一起。**也就是说将变化的代码抽离出来和不经常变化的代码分离开,这样就能在变化的时候尽量少的影响其他代码。
应用到鸭子类,我们知道duck类内的fly和quack方法会随着鸭子的不同而改变,因此将飞行和呱呱叫行为抽离出来,切每一组类将实现各自的动作。
将Quackable和Flyable抽离出来也体现出了针对接口编程,而非针对实现编程的原则。之前我们是由具体的鸭子子类来实现相应的行为,被实现绑的死死的,无法更改行为,现在我们可以依赖于行为接口的实现子类,将相应行为指定给鸭子子类,从而可以更改行为,而不在与实现绑死了。
比如说Flyable可以有两个相应的实现
interface FlyBehavior{
void fly();
}
class FlyWithWings implements FlyBehavior{
void fly(){
//实现鸭子的飞行动作。
}
}
class FlyNoWay implements FlyBehavior{
void fly(){
//什么都不做。
}
}
//QuackBehavior有三个实现
interface QuackBehavior{
void quack();
}
class Quack implements QuackBehavior{
void quack(){
//嘎嘎叫
}
}
class Squeak implements QuackBehavior{
void quack(){
//吱吱叫
}
}
class MuteQuack implements QuackBehavior{
void quack(){
//不叫
}
}
通过这样设计,就可以让呱呱叫的动作 和飞行的动作被其他对象服用了,这些行为已经和鸭子类无关了
现在我们将鸭子基类中加入两个实例变量。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
abstract public void swim();
abstract public void performQuack();
abstract public void display();
abstract public void performFly();
}
现在在鸭子的实现类里我们想让鸭子拥有什么样的行为,我们就直接委托给鸭子子类
class MallardDuck extends Duck{
public MallardDuck (){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
//将该鸭子相对应的行为委托给该鸭子子类
public MallardDuck (QuackBehavior quackBehavior,FlyBehavior flyBehavior ){
this.quackBehavior = quackBehavior ;
this.flyBehavior = flyBehavior ;
}
public void setFlyBehavior(FlyBehavior flyBehavior ){
this.flyBehavior = flyBehavior ;
}
public void setQuackBehavior(QuackBehavior quackBehavior){
this.quackBehavior = quackBehavior ;
}
void performQuack(){
quackBehavior.quack(); 表现出委托的叫行为的叫声
}
void performFly(){
flyBehavior .fly(); 表现出委托的飞行行为的飞行方法
}
}
上面的方法是不是就可以解决我们的问题了,再举个例子,比如说我们现在想生产一种飞起来利用火箭动力飞行的模型鸭子。
class ModelDuck extends Duck{
public ModelDuck (){
quackBehavior = new Quack();
flyBehavior = new FlyNoWay();//一开始我们的模型鸭是不会飞的
}
//将该鸭子相对应的行为委托给该鸭子子类
public ModelDuck (QuackBehavior quackBehavior,FlyBehavior flyBehavior ){
this.quackBehavior = quackBehavior ;
this.flyBehavior = flyBehavior ;
}
public void setFlyBehavior(FlyBehavior flyBehavior ){
this.flyBehavior = flyBehavior ;
}
public void setQuackBehavior(QuackBehavior quackBehavior){
this.quackBehavior = quackBehavior ;
}
void performQuack(){
quackBehavior.quack(); 表现出委托的叫行为的叫声
}
void performFly(){
flyBehavior .fly(); 表现出委托的飞行行为的飞行方法
}
}
//定义一种利用火箭飞行行为
public class FlyRocketPowered implements FlyBehavior{
public void fly(){
System.out.println("I'm flying with a rocket");
}
}
现在让我们新建一直模型鸭
Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyRocketPowered ());
modelDuck.performFly();
执行结果:
模型鸭不会飞
I'm flying with a rocket"
以上的这些就是策略模式了,在本例中是将飞行行为和叫声行为抽离出来,来进行众多实现。在以组合的方式来实现多种行为的替换,这样就能在运行时更改鸭子的飞行和叫声行式,不再将鸭子子类和飞行叫声绑死,实现灵活替换,这些行为的实现子类就可以看成是算法蔟。今天的分享就到这里啦。