策略模式

0

title

引言

要分析设计模式中的“策略模式”,我们可以以《希伯来圣经》中的一个故事为例。

《希伯来圣经》中有这样一个故事,说的是人类产生不同语言的起源。在这个故事中,一群只说一种语言的人在“大洪水”之后从东方来到了示拿地区,并决定在这修建一座城市和一座“能够通天的”高塔;上帝见此情形就把他们的语言打乱,让他们再也不能明白对方的意思,并把他们分散到了世界各地。

这里我们可以将God视作一个程序员。
在他的设计中,所有的人类(Human),虽然有很多个个体,但一开始都是一样的,说的是一样的语言。

所以我们可以将Human类作为父类,然后所有的人类个体对象都继承这个Human父类。父类中有一个speak()方法,所有继承Human类的子类都将继承这个方法,于是都拥有了用语言交流的能力。

//Human父类
public class Human {
    public void speak(){
        System.out.println("只要是人类的子类都speak the same language");
    }
}

可以有很多的人类种类继承这个Human父类,比如来自东方的的人:

public class PeopleFromEast extends {
    //PeopleFromEast继承了Human,
    //所以也就继承了Human父类中的speak()方法
    //所以PeopleFromEast类中甚至都不需要写任何方法
}

在demo测试中,我们可以看到,PeopleFromEast类的对象,可以调用从父类继承来的speak()方法:

public class RunDemo {
    public static void main (String[] args){
    //创建一个PeopleFromEast对象
        PeopleFromEast peopleFromEast = new PeopleFromEast();
        //让这个对象调用从父类继承来的方法speak()
        peopleFromEast.speak();
    }
}

demo的输出结果

根据God最初的设想,这样的设计是没有问题的。

“土方法”的缺陷

可是后来由于语言一致,人类同心协力修建巴比塔,(程序猿)God感到受到了威胁。于是决定做出改变:让来自不同地方的人speak不同的language——比如PeopleFromEast说EastLanguage,PeopleFromWest说WestLanguage,等等。

God要实现这样的改变,最直观的方法自然是让每个继承Human父类的人类子类重写父类的speak()方法。

clipboard.png

//新的PeopleFromEast类
//这一次PeopleFromEast重写了speak()方法
public class PeopleFromEast extends Human {
    @Override
    public void speak() {
        System.out.println("PeopleFromEast从今以后只能speak EastLanguage");
    }
}

demo的运行结果:

让子类重写speak()方法

如果人类的子类种类不多,那么(程序猿God)还可以每次都重写Human子类的speak()方法。可是如果人类的子类有很多种呢?又或者不止speak()方法有这种情况呢?比如,也许God以后希望让每种人类子类的行走方式都不一样。那是不是应该在Human父类中加入一个walk()方法,然后让每个子类都重写呢?

也就是说,如果人类子类的“规格”常常改变,每当有新的人类子类出现,(程序猿)God都要被迫检查并可能需要覆盖speak()walk(),这绝对是God所不愿意看见的。


策略模式原理及益处

这个策略依然有一个Human父类(相当于一个模板),所有的人类子类都继承这个父类(根据这个模板来创建)。

所不同的是,这次God决定在每个人类的子类中都附上自己的一份意识。让这份意识来控制这个子类应该说的语言种类。为了方便叙述,我们可以将God的这一意识称为LanguageController

然后,God为人类的子类可以说的Language种类提供了一个选择范围
比如,中国人(ChineseMan--因为Chinese既可以表示“中文”又可以表示“中国人”,为了区分,我们称“中国人”为"ChineseMan")说中文(Chinese),美国人(American)就说英文(English)。

这样一来,在每个人类子类创建时,God附在其上的意识LanguageController将发挥作用,然后按照God的意愿,从language的可选范围中选择一种语言来指定给该人类子类。

这一策略在程序设计中可以这样实现:
创建一个Human父类:

public  class Human {
    //LanguageController是接口类型
    //languageController作为Human类的field,
    //代表God将自己的意识附在人类父类之上,
    //也就代表God的意识将附在所有的人类个体之上
    LanguageController languageController ;
      
    //根据God的设计,人类的模板中只需要作出规定:人会说某种语言
    //而不需要作出具体的安排
    public void performSpeaking (){
    //人说话的时候,还得上帝的意识起作用,
    //决定这个“具体的人”该说何种语言
        languageController.speak();
    }
}

创建LanguageController接口:

//创造God的意识,也就是LanguageController:
public interface LanguageController {
    //接口中只定义了一个speak()方法
    public void speak();
}

现在God要创造中国人和美国人:

中国人和美国人的类

ChineseMan类:

public class ChineseMan extends Human {
    //中国人对象的构造器
    ChineseMan() {
        //构造ChineseMan对象时,God的意识发挥作用
        //languageController是从Human父类继承来的
        //根据God的安排,要创建ChineseMan对象,就应该告诉languageController现在要创造的这个对象说中文
        languageController = new speakChinese();
        //在这里,由于多态的缘故,表现为具体子类--也就是speakChinese对象
        skinColorController = new ColorIsLikeAsian();
    }
}

American类:

public class American extends Human {
    //American的构造器
    public American (){
        languageController = new SpeakEnglish();
    }
}

那么这个speakChinese类speakEnglish类有什么玄机呢?

speakChinese类:

//speakChinese类实现了LanguageController接口
public class speakChinese implements LanguageController{

    //实现LanguageController接口中规定的speak()方法
    @Override
    public void speak() {
        System.out.println("我说中文!");
    }
}

speakChinese类可能不太好理解,因为speak Chinese(说中文)更多算是一种行为(action),这里却定义为类。其实只需要换一种思维,把speakChinese类视作一种标签,把这个标签赋给God的意识(languageContronller),也就实现了根据不同的human子类来确定不同的语言。
speakEnglish类:

//SpeakEnglish类实现了LanguageController接口
public class SpeakEnglish implements LanguageController {
    @Override
    public void speak() {
        System.out.println("I speak English!");

    }
}

下面运行一个demo--StrategyModeDemo.java:

public class StrategyModeDemo {

    public static void main(String[] args){
    
        //新建一个ChineseMan对象
        Human chineseMan = new ChineseMan();
        //ChineseMan说的语言:
        chineseMan.performSpeaking();

        //American对象:
        Human american = new American();
        //American说的语言:
        american.performSpeaking();

    }
}

StrategyModeDemo.java运行结果:

运行结果

执行逻辑图示

而整个执行流程的逻辑则如图所示:

执行逻辑


更上一层楼

当然,以上所有的Human子类在被创建时,他们说什么话就已经注定了。
这是因为他们的God的意识起作用的环节在他们的构造器中。

那么God有没有办法让已经被造出来的ChineseMan对象改说English呢?
答案是肯定的!

我们只需要在Human.java中加入一个修改语言的方法即可:

public  class Human {
      LanguageController languageController ;
      SkinColorController skinColorController;

    //控制Human的子类说的语言,比如中国人说中文
    public void performSpeaking (){
        languageController.speak();
    }
    
    //随时改变能说的语言
    public void setLanguage (LanguageController languageController){
        languageController.speak();
    }
}

再次运行StrategyModeDemo.java:

public class StrategyModeDemo {

    public static void main(String[] args){
        //新建一个ChineseMan对象
        Human chineseMan = new ChineseMan();
        //ChineseMan说的语言:
        chineseMan.performSpeaking();
        System.out.println("God决定让ChineseMan也说英文");
        chineseMan.setLanguage(new SpeakEnglish());
    }
}

运行结果


个人浅见,如有不足,欢迎批评指正:)

你可能感兴趣的

载入中...