设计模式讲解与代码实践(二十一)——状态

本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!

1 目的

状态(State)模式用于描述某事物所具有的一组可枚举状态。在不同的状态下该事物对相同的操作表现出不同的行为,并可能在操作后发生状态的转化。
从状态模式的描述来看,包含两个要素:

  1. 各状态下具有相同的操作。这很容易让我们联想到通过抽象方法或接口来声明这些操作;
  2. 状态转化。对于状态模式,状态间的转化既可以在状态外部实现也可在其内部实现。

2 基本形态

状态的基本形态如类图2-1所示。
设计模式讲解与代码实践(二十一)——状态_第1张图片
图2-1 状态类图

3 参与者

结合图2-1,下面介绍各类在状态设计模式中扮演的角色。
3.1 State
State是状态接口,定义了与状态相关的各接口方法。
3.2 ConcreteState
ConcreteState是具体状态,实现了接口State。
3.3 Context
Context是环境(上下文)。Context对象中包含State类型对象以表示当前环境的状态。初始时由外部(或默认)指定Context所处状态。当Context对外提供的方法被调用时(如Request),Context将调用其包含的State对象的对应方法(如Handle),并根据调用结果更新State实例。

4 代码实践

下面我们用一个业务场景实例来进一步讲解状态的使用。
4.1 场景介绍
某单词记忆应用的背单词功能将单词分为待测试、学习中和已掌握三种状态。应用以循环播放单词让用户选择是否认识该单词的形式帮助用户记忆、测试单词。单词各状态间的转换关系如图4-1所示。
设计模式讲解与代码实践(二十一)——状态_第2张图片
图4-1 单词状态图

以下各节将介绍该场景各类的具体实现及其在状态设计模式中所对应的参与者角色。

4.2 ILearningState
ILearningState是学习状态接口,定义了在不同状态下单词学习的各相关操作。对应于状态模式的参与者,ILearningState是状态接口State。下面的代码给出了ILearningState的声明。

package demo.designpattern.state;

/**
 * 学习状态接口
 * Created by LiMingzi on 2017/11/10.
 */
public interface ILearningState {
    /**
     * 显示问题
     *
     * @param word 单词
     */
    void showQuestion(Word word);

    /**
     * 认识
     *
     * @return 新状态
     */
    ILearningState know();

    /**
     * 不认识
     *
     * @return 新状态
     */
    ILearningState unKnow();

    /**
     * 是否需要学习
     *
     * @return 是否需要学习
     */
    boolean needStudy();
}

上述代码中,13行,声明了显示问题方法showQuestion,为了使各状态实例具有上下文无关性以便重用(可参见第5章扩展),将状态所描述的环境(上下文)Word类型实例作为该方法参数。20行声明的方法know与27行声明的方法unKnow涉及到状态的转化,返回ILearningState类型的实例。
4.3 ToBeTested
ToBeTested是待测验状态类,实现了学习状态接口ILearningState。对应于状态模式的参与者,ToBeTested是具体状态ConcreteState。下面的代码给出了ToBeTested的声明。

package demo.designpattern.state;

/**
 * 待测验状态
 * Created by LiMingzi on 2017/11/10.
 */
public class ToBeTested implements ILearningState {
    /**
     * 显示问题
     *
     * @param word 单词
     */
    @Override
    public void showQuestion(Word word){
        System.out.println("测试模式:");
        System.out.println(word.getSpell());
    }

    /**
     * 认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState know(){
        return new Known();
    }

    /**
     * 不认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState unKnow() {
        return new Unknown();
    }

    /**
     * 是否需要学习
     *
     * @return 是否需要学习
     */
    @Override
    public boolean needStudy() {
        return true;
    }
}

上述代码中,14行,对于待测试单词提问时显示“测试模式”,只给出单词拼写;26行,对于认识的单词将转化为已掌握状态;36行,对于不认识的单词将转化为学习中状态。
4.4 Known
Known是已掌握状态类,实现了学习状态接口ILearningState。对应于状态模式的参与者,Known是具体状态ConcreteState。下面的代码给出了Known的声明。

package demo.designpattern.state;

/**
 * 已掌握状态
 * Created by LiMingzi on 2017/11/10.
 */
public class Known implements ILearningState {
    /**
     * 显示问题
     *
     * @param word 单词
     */
    @Override
    public void showQuestion(Word word) {
        System.out.println("复习模式:");
        System.out.println(word.getSpell());
    }

    /**
     * 认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState know() {
        return this;
    }

    /**
     * 不认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState unKnow() {
        return new Unknown();
    }

    /**
     * 是否需要学习
     *
     * @return 是否需要学习
     */
    @Override
    public boolean needStudy() {
        return false;
    }
}

上述代码中,14行,对于已掌握单词提问时显示“复习模式”,只给出单词拼写;26行,对于认识的单词状态不变;36行,对于不认识的单词将转化为学习中状态。
4.5 Unknown
Unknown是学习中状态类,实现了学习状态接口ILearningState。对应于状态模式的参与者,Unknown是具体状态ConcreteState。下面的代码给出了Unknown的声明。

package demo.designpattern.state;

/**
 * 学习中状态
 * Created by LiMingzi on 2017/11/10.
 */
public class Unknown implements ILearningState {
    /**
     * 显示问题
     * @param word 单词
     *
     */
    @Override
    public void showQuestion(Word word) {
        System.out.println("学习模式:");
        System.out.println(word.getSpell());
        System.out.println(word.getExample());
    }

    /**
     * 认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState know() {
        return new ToBeTested();
    }

    /**
     * 不认识
     *
     * @return 新状态
     */
    @Override
    public ILearningState unKnow() {
        return this;
    }

    /**
     * 是否需要学习
     *
     * @return 是否需要学习
     */
    @Override
    public boolean needStudy() {
        return true;
    }
}

上述代码中,14行,对于已掌握单词提问时显示“学习模式”,给出单词拼写和例句;27行,对于认识的单词转化为待测验状态;37行,对于不认识的单词状态不变。
4.6 Word
Word是单词类,维护了单词各元素及单词的学习状态,提供单词学习相关操作。对应于状态模式的参与者,Word是环境(上下文)Context。下面的代码给出了Word的声明。

package demo.designpattern.state;

/**
 * 单词类
 * Created by LiMingzi on 2017/11/25.
 */
public class Word {
    /**
     * 拼写
     */
    private String spell;
    /**
     * 释义
     */
    private String paraphrase;
    /**
     * 例句
     */
    private String example;
    /**
     * 学习状态
     */
    private ILearningState state;

    /**
     * 获取拼写
     * @return 拼写
     */
    public String getSpell() {
        return spell;
    }

    /**
     * 获取释义
     * @return 释义
     */
    public String getParaphrase() {
        return paraphrase;
    }

    /**
     * 获取例句
     * @return 例句
     */
    public String getExample() {
        return example;
    }

    /**
     * 构造方法
     * @param spell 拼写
     * @param paraphrase 释义
     * @param example 例句
     * @param state 状态
     */
    public Word(String spell, String paraphrase, String example, ILearningState state) {
        this.spell = spell;
        this.paraphrase = paraphrase;
        this.example = example;
        this.state = state;
    }
    /**
     * 显示问题
     */
    public void showQuestion() {
      state.showQuestion(this);
    }

    /**
     * 认识
     */
    public void know() {
        state = state.know();
    }

    /**
     * 不认识
     */
    public void unKnow() {
        state = state.unKnow();
        System.out.println("释义:"+paraphrase);
    }

    /**
     * 是否需要学习
     */
    public boolean needStudy() {
        return state.needStudy();
    }
}

上述代码中,23行,成员变量state维护了当前单词的学习状态。65行,显示问题方法showQuestion的行为与单词所处状态有关,内部实现直接调用了状态实例的相应处理方法,并将自己作为实参传递。know、unknow、needStudy等方法类似,恕不赘述。
4.7 WordLearning
WordLearning是单词学习类,提供了单词学习相关方法。对应于状态模式的参与者,WordLearning是客户Client,未出现在图2-1中。下面的代码给出了WordLearning的声明。

package demo.designpattern.state;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 单词学习类
 * Created by LiMingzi on 2017/11/10.
 */
public class WordLearning {
    /**
     * 学习
     */
    public void learn(){
        // 待学习单词集合
        List words = loadWords();
        // 扫描器对象
        Scanner scanner = new Scanner(System.in);
        System.out.println("学习开始");
        while (true){
            for (int i = 0; i < words.size(); i++) {
                // 当前单词
                Word word = words.get(i);
                if(!word.needStudy()){
                    continue;
                }
                word.showQuestion();
                // 是否认识
                String isKnownStr = scanner.nextLine();
                if("y".equals(isKnownStr)){
                    word.know();
                }else{
                    word.unKnow();
                }
            }
            // 有未完成学习的单词
            boolean hasLearningWord = false;
            for (Word word : words) {
                if(word.needStudy()){
                    hasLearningWord = true;
                    break;
                }
            }
            if(!hasLearningWord){
                break;
            }
        }
        System.out.println("学习结束");
    }

    /**
     * 载入单词(demo,实际应从持久层获取)
     * @return 单词集合
     */
    private ListloadWords(){
        // 单词集合
        List words = new ArrayList();
        words.add(new Word("hello","你好","Hello world!",new ToBeTested()));
        words.add(new Word("world","世界","Hello world!",new ToBeTested()));
        return words;
    }
}

上述代码中,17行,载入待学习的单词;22行,遍历各单词,26行,跳过不需要再学习的单词;28行根据单词学习状态向用户提问该单词;30行读取用户输入(y表示认识该单词,n表示不认识该单词)。39行,遍历各单词,确定是否仍有单词需要继续学习。
4.8 测试代码
为了测试本文中的代码,我们可以编写如下测试代码。测试代码中我们对hello和world两个单词进行学习,根据输入的不同,单词的学习状态发生了转化,最终完成了这两个单词的学习。

 /**
     * 状态测试
     */
    public static void stateTest(){
        // 单词学习类对象
        WordLearning wordLearning = new WordLearning();
        wordLearning.learn();
    }

编译运行后,得到如下测试结果:
学习开始
测试模式:
hello
n
释义:你好
测试模式:
world
n
释义:世界
学习模式:
hello
Hello world!
y
学习模式:
world
Hello world!
n
释义:世界
测试模式:
hello
y
学习模式:
world
Hello world!
y
测试模式:
world
y
学习结束

5 扩展

在实际应用中,如果状态的宿主(Context)数量庞大且状态转换频繁则应该有效控制状态类实例的数量。比如,本文中的示例每个单词都需要一个“学习状态”,而学习状态在学习过程中会频繁转化。这种情况下我们可以将每种状态的实例实现为单例。当然,这有一个前提,即将业务属性与状态剥离,上下文信息完全由宿主而非状态类维护。

你可能感兴趣的:(算法与程序设计,设计模式,java,架构设计,设计模式讲解与代码实践)