状态(State)模式--------用类表示状态

》》在 State 模式中,我们用类来表示状态。

        以类来表示状态后,我们就能通过切换类来

   方便地改变对象的状态。当需要增加新的状态时,

   如何修改代码这个问题也会很明确。

------------------下面的示例程序:金库报警系统

                    (警戒状态每小时会改变一次的警报系统)


       在我们即将要写的程序中,并不会真正呼叫警报中心,只是在页面上显示呼叫状态。此外,如果以现实世界中

  的时间来测试程序就太慢了,所以我们假设程序中的 1 秒对应现实世界中的一个小时。

---------------------------金库警报系统的结构图:

          

扫描二维码关注公众号,回复: 2214667 查看本文章

--------------------------------《示例程序的运行结果》

             

------------------------------------------------------------------

     《不使用  State 模式的伪代码 》

       警报系统的类{

               使用金库时被调用的方法(){

                           if (白天){

                                 向警报中心报告使用记录

                           }else if (晚上){

                                 向警报中心报告紧急事态

                          }

               }


             警铃响起时被调用的方法(){

                     向警报中心报告紧急事态

             }


             正常通话时被调用的方法(){

                     if(白天){

                             呼叫警报中心

                     }else if(晚上){

                             呼叫警报中心的留言电话

                     }

              }

       }

 ---------------------------------------------------------------------

       《使用了 State 模式的伪代码》

        表示白天的状态的类{

                 使用金库时被调用的方法(){

                           向警报中心报告使用记录

                 }

                 警铃响起时被调用的方法(){

                           向警报中心报告紧急事态

                }

                正常通话时被调用的方法(){

                          呼叫警报中心

                }

        }


       >>>>>>

        表示晚上的状态的类{

               使用金库时被调用的方法(){

                        向警报中心报告紧急事态

               }

               警铃响起时被调用的方法(){

                         向警报中心报告紧急事态

              }

               正常通话时被调用的方法(){

                         呼叫警报中心的留言电话

              }

        }

    备注:上面两种伪代码:

                 》》不使用 State 模式的伪代码:会先在各个方法里面使用 if 语句判断

                                 现在是白天还是晚上,然后再进行相应的处理(用方法来表示

                                状态)

                》》使用 State 模式的伪代码:用类来表示白天和晚上。

                                (用类来表示状态)

--------------------------下面的内容是使用了  State 模式的

》》示例程序的类图:

          

》》State 接口:

        package state;
/*
 * 该接口表示金库状态的接口。
 * 在接口中定义了以下事件对应的接口:
 *    设置时间
 *    使用金库
 *    按下警铃
 *    正常通话
 */


public interface State {
public abstract void doClock(Context context , int hour);   //设置时间
public abstract void doUse(Context context);                //使用金库
public abstract void doAlarm(Context context);             //按下警铃
public abstract void doPhone(Context context);              //正常通话


}

》》DayState 类:

     package state;
/*
 * 该类表示白天的状态,实现了 State 接口,实现了State 接口中声明的所有方法
 * 
 * 对于每个表示状态的类,我们都只能生成一个实例。这里用了  单例模式
 */


public class DayState implements State {
private static DayState singleton = new  DayState();
//构造函数的可见性是  private 
private DayState(){                                 

}

//获取唯一实例
public static State getInstance(){
return singleton;
}


//设置时间
/*
* 如果接收到的参数是表示晚上的时间,就会切换到夜间状态,即发生了状态变化(状态迁移)
*/
public void doClock(Context context, int hour) {
if(hour < 9 || hour >= 17){
context.changeState(NightState.getInstance());
}


}


    // 使用金库
public void doUse(Context context) {
context.recordLog("使用金库(白天)");


}


//按下警铃
public void doAlarm(Context context) {
context.callSecurityCenter("按下警铃(白天)");


}


// 正常通话
public void doPhone(Context context) {
   context.callSecurityCenter("正常通话(白天)");


}

//显示表示类的文字
public String toString(){
return "[白天]";
}


}

》》NightState 类:

           package state;
 /*
  * 该类的结构与  DayState类的结构 完全一样
  */


public class NightState implements State {
private static NightState singleton = new NightState();
//构造函数的可见性是 private 
private NightState(){

}
//获取唯一实例
public static State  getInstance(){
return singleton;
}


    //设置时间
public void doClock(Context context, int hour) {
if( 9 <= hour && hour < 17){
context.changeState(DayState.getInstance());
}


}


//使用金库
public void doUse(Context context) {
context.callSecurityCenter("紧急:晚上使用金库!");


}


//按下警铃
public void doAlarm(Context context) {
context.callSecurityCenter("按下警铃(晚上)");


}


//正常通话
public void doPhone(Context context) {
context.recordLog("晚上的通话录音");


}

//显示表示类的文字
public String toString(){
return "[晚上]";
}


}

》》Context 接口:

        package state;
/*
 * 该接口是负责管理状态和联系警报中心的接口
 */
public interface Context {
public abstract void setClock(int hour);       //设置时间
public abstract void changeState(State state); //改变状态
public abstract void callSecurityCenter(String msg); //联系警报中心
public abstract void recordLog(String msg);     //在警报中心留下记录


}

》》SafeFrame 类:

        package state;
/*
 * 该类使用 GUI 实现了警报系统界面的类,它实现了 Context 接口。
 * 该类中有表示文本输入框(TextField)、多行文本输入框(TextArea)和按钮(Button)
 *  等各种控件的字段。
 *  该类的构造函数进行了以下处理:(1)、设置背景色
 *                      (2)、设置布局管理器
 *                      (3)、设置控件
 *                      (4)、设置监听器
 *  监听器的设置非常重要。我们通过调用各个按钮的 addActionListener() 方法来设置监听器,
 *  addActionListener() 方法接收的参数是“当按钮被按下时会被调用的实例”,该实例必须
 *  是实现了 ActionListener 接口的实例。
 */


import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class SafeFrame extends Frame implements Context, ActionListener {
//显示当前时间
    private TextField textClock = new TextField(60);
//显示警报中心的记录
private TextArea textScreen = new TextArea(20 , 60);
//使用金库按钮
private Button  buttonUse = new Button("use");
//按下警铃按钮
private Button buttonAlarm = new Button("alarm");
//正常通话按钮
private Button buttonPhone = new Button("phone");
//结束按钮
private Button buttonExit = new Button("exit");


//当前的状态
private State state = DayState.getInstance();

//构造函数
public SafeFrame(String title){
//设置标题
super(title);
//设置背景色
setBackground(Color.lightGray);
   //设置布局
setLayout(new BorderLayout());
//在上面所设置的局部中  配置textClock
add(textClock,BorderLayout.NORTH);
//设置该文本组件不能被编辑
textClock.setEditable(false);
//配置 textScreen
add(textScreen,BorderLayout.CENTER);
textScreen.setEditable(false);
//为界面添加按钮
Panel panel = new Panel();
panel.add(buttonUse);
panel.add(buttonAlarm);
panel.add(buttonPhone);
panel.add(buttonExit);
//配置界面
add(panel , BorderLayout.SOUTH);
pack();
   setVisible(true);
   //设置监听器
   buttonUse.addActionListener(this);
   buttonAlarm.addActionListener(this);
   buttonPhone.addActionListener(this);
   buttonExit.addActionListener(this);

}


//按钮被按下后下面的方法会被调用
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
if(e.getSource() == buttonUse){      //金库使用按钮
state.doUse(this);
}else if (e.getSource() == buttonAlarm){ //按下警铃按钮
state.doAlarm(this);
}else if(e.getSource() == buttonPhone){   //正常通话按钮
state.doPhone(this);
}else if(e.getSource() == buttonExit){  //结束按钮
System.exit(0);
}else{
System.out.println("?");
}


}


//设置时间
public void setClock(int hour) {
String clockstring = "现在的时间是:";
if( hour < 10){
clockstring += "0"+hour+":00";
}else{
clockstring += hour + ":00";
}
System.out.println(clockstring);
//在文本组件中设置指定文本
textClock.setText(clockstring);
state.doClock(this, hour);


}




public void changeState(State state) {
System.out.println("从"+ this.state + "状态变为了"+state+"状态");
/*
* 给代表状态的字段赋予表示当前状态的类的实例,就相当于进行了状态迁移
*/
this.state = state;


}


    //联系警报中心
public void callSecurityCenter(String msg) {
textScreen.append("call!"+msg+"\n");


}


//在警报中心留下记录
public void recordLog(String msg) {
textScreen.append("record..."+msg + "\n");


}


}

》》Main 类   -----》测试程序

       package state;
/*
 * 该类是测试程序,生成了SafeFrame 的实例并每秒调用一次 setClock 方法对该实例
 * 设置一次时间。
 * 该类中使用了  线程的知识
 */


public class Main {
public static void main(String[] args){
SafeFrame frame = new SafeFrame("State Sample");
while(true){
for(int hour = 0 ; hour < 24 ;hour++){
frame.setClock(hour);              //设置时间
try{
Thread.sleep(1000);
}catch(InterruptedException e){

}
}

}
}


}

---------------------------------------------------------------------

        《State  模式中的登场角色》

         **** State (状态)

                 State 角色表示状态,定义了根据不同状态进行不同处理的接口。该接口是那些处理

            内容依赖于状态的方法的集合。在示例程序中,State 接口扮演此举色。

         **** ConcreteState (具体的状态)

                  ConcreteState 角色表示各个具体的状态,它实现了 State 接口。在示例程序中,

            由 DayState 类和 NightState 类扮演此角色。

         **** Context (状况、前后关系、上下文)

                  Context 角色持有表示当前状态的 ConcreteState 角色。此外,它还定义了供外部

           调用者使用 State 模式的接口。在示例程序中,由Context 和 SafeFrame  类扮演此角色。

---------------------------------------------------------------------

         《扩展思路的要点》

           1.分而治之

                     **分而治之,简单而言就是将一个复杂的大问题分解为多个小问题然后逐个解决。

                     **在 State 模式中用类来表示系统的“状态”,并以此将复杂的程序分解出来。

           2.依赖于状态的处理

                     ** 在 State 接口中声明的所有方法都是“依赖于状态的处理”,都是“状态不同处理也不同”。

                     ** 在State 模式中,我们应该如何编程,以实现“依赖于状态的处理”呢?总结起来如下

                          两点:

                          (1)、定义接口,声明抽象方法

                          (2)、定义多个类,实现具体方法

           3.应当是谁来管理状态迁移

                     ** 在 State 模式中,需要注意的是:应当是谁来管理状态迁移

                     ** 除了上面示例程序中,处理状态迁移的方法外,还可以使用状态迁移表来设计程序。

                         所谓状态迁移表是可以根据“输入和内部状态”得到“输出和下一个状态”的一览表。

                         当状态迁移遵循一定的规则时,使用状态迁移表非常有效。

                     ** 此外,当状态数过多时,可以用程序来生成代码而不是手写代码。

           4.不会自相矛盾

                     ** 如果不使用 State  模式,我们需要使用多个变量的值的集合来表示系统的状态。这时,

                          必须十分小心,注意不要让变量的值之间互相矛盾。

                     ** 在 State 模式中,是用类来表示状态的。这样,我们就只需要一个表示系统状态的变量

                        即可。在示例程序中,SafeFrame 类的 state 字段就是这个变量,它决定了系统的状态。

                        因此,不会存在自相矛盾的状态。

           5.易于增加新的状态

                    ** 在 State 模式中,增加新的状态是非常简单的。

                    **  但是,在State 模式中增加其他“依赖于状态的处理”是很困难的。

                          (依赖于状态的处理-----》 即 方法)

           6.实例的多面性

-------------------------------------------------------------------

          《相关的设计模式》

            *** Singleton 模式

            *** Flyweight 模式

                 

                  

猜你喜欢

转载自blog.csdn.net/lierming__/article/details/79698989