图解设计模式【3】
State模式
State模式中,用类来表示状态。以类来表示状态就可以通过切换类来方便地改变对象的状态。当需要增加新的状态时,如何修改代码这个问题也会很明确。
示例
设计一个简单的报警系统,每秒会改变一次状态。
金库报警系统 |
---|
- 有一个金库,金库与报警中心相连,金库里有警铃和正常通话用的电话。金库里有时钟,监视着现在的时间。 |
- 白天的时间范围时9:00-16:59,晚上的时间范围是17:00-23:59和0:00-8:59。 |
- 金库只能在白天使用。白天使用金库的话,会在报警中心留下记录;晚上使用金库的话,会向报警中心发送紧急事态通知。 |
- 任何时候都可以使用警铃。使用警铃的话,会向警报中心发送紧急事态通知。 |
- 任何时候都可以使用警铃。使用警铃的话,会向警报中心发送紧急事态通知。 |
- 任何时候都可以使用电话(晚上只有留言电话)。白天使用电话的话,会呼叫报警中心。晚上使用电话的话,会呼叫报警中心的留言电话。 |
如果不使用State模式,我们可以使用如下的伪码逻辑
警报系统的类{使用金库时被调用的方法(){if (白天) {向警报中心报告使用记录} else if (晚上){向警报中心报告紧急事态}警铃响起时被调用的方法(){向警报中心报告紧急事态}正常通话时被调用的方法(){if (白天){呼叫报警中心} else if (晚上){呼叫警报中心的留言电话}}
}
使用了State模式的伪代码
表示白天的状态的类{使用金库时被调用的方法(){向警报中心报告使用记录}警铃响起时被调用的方法(){向警报中心报告紧急事态}正常通话时被调用的方法(){呼叫警报中心}
}
表示晚上的状态的类{使用金库时被调用的方法(){向警报中心报告紧急事态}警铃响起时被调用的方法(){向警报中心报告紧急事态}正常通话时被调用的方法(){呼叫警报中心的留言电话}
}
两者的区别在于,使用State模式,不需要用if语句判断是白天还是晚上。
State接口时表示金库状态的接口。其中包括设置时间、使用金库、按下警铃、正常通话等API
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类表示白天的状态。该类实现了State接口,因此还实现了State接口中声明的所有方法。
public class DayState implements State {private static DayState singleton = new DayState();private DayState() { // 构造函数的可见性是private}public static State getInstance() { // 获取唯一实例return singleton;}public void doClock(Context context, int hour) { // 设置时间if (hour < 9 || 17 <= hour) {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类表示晚上的状态。它与DateState类一样,也是用了Singleton模式。
public class NightState implements State {private static NightState singleton = new NightState();private NightState() { // 构造函数的可见性是private}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接口是负责管理状态和联系警报中心的接口。
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类是使用GUI实现警报系统界面的类,它实现了Context接口。
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;public class SafeFrame extends Frame implements ActionListener, Context {private TextField textClock = new TextField(60); // 显示当前时间private TextArea textScreen = new TextArea(10, 60); // 显示警报中心的记录private Button buttonUse = new Button("使用金库"); // 金库使用按钮private Button buttonAlarm = new Button("按下警铃"); // 按下警铃按钮private Button buttonPhone = new Button("正常通话"); // 正常通话按钮private Button buttonExit = new Button("结束"); // 结束按钮private State state = DayState.getInstance(); // 当前的状态// 构造函数public SafeFrame(String title) {super(title);setBackground(Color.lightGray);setLayout(new BorderLayout());// 配置textClockadd(textClock, BorderLayout.NORTH);textClock.setEditable(false);// 配置textScreenadd(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();show();// 设置监听器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类生成了一个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表示状态,定义了根据不同状态进行不同处理的API。该API是那些处理内容依赖于状态的方法的集和。
-
ConcreteState
ConcreteState表示各个具体的状态,实现了State接口。
-
Context
Context持有表示当前状态的ConcreteState。它还定义了提供外部调用者使用State模式的API。
编程时,我们经常使用分而治之的方针。这种方针非常适用于大规模的复杂处理。当遇到庞大且复杂的问题,不能用一般的方法解决时,我们会将该问题分解为多个小问题。在State模式中,我们用类来表示状态,并为每一种具体的状态都定义一个相应的类。
State模式易于增加新的状态,但是在State模式中增加其他“依赖于状态的处理”是很困难的。
Flyweight模式
Flyweight是轻量级的意思。关于Flyweight模式,一言以蔽之就是“通过尽量共享实例来避免new出实例“。
示例
BigChar表示大型字符类。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;public class BigChar {// 字符名字private char charname;// 大型字符对应的字符串(由'#' '.' '\n'组成)private String fontdata;// 构造函数public BigChar(char charname) {this.charname = charname;try {BufferedReader reader = new BufferedReader(new FileReader("big" + charname + ".txt"));String line;StringBuffer buf = new StringBuffer();while ((line = reader.readLine()) != null) {buf.append(line);buf.append("\n");}reader.close();this.fontdata = buf.toString();} catch (IOException e) {this.fontdata = charname + "?";}}// 显示大型字符public void print() {System.out.print(fontdata);}
}
BigCharFactory类是生成BigChar类的实例的工厂。它实现了共享实例的功能。
import java.util.HashMap;public class BigCharFactory {// 管理已经生成的BigChar的实例private HashMap pool = new HashMap();// Singleton模式private static BigCharFactory singleton = new BigCharFactory();// 构造函数private BigCharFactory() {}// 获取唯一的实例public static BigCharFactory getInstance() {return singleton;}// 生成(共享)BigChar类的实例public synchronized BigChar getBigChar(char charname) {BigChar bc = (BigChar)pool.get("" + charname);if (bc == null) {bc = new BigChar(charname); // 生成BigChar的实例pool.put("" + charname, bc);}return bc;}
}
BigString类是由BigChar组成的大型字符串的类。
public class BigString {// “大型字符”的数组private BigChar[] bigchars;// 构造函数public BigString(String string) {bigchars = new BigChar[string.length()];BigCharFactory factory = BigCharFactory.getInstance();for (int i = 0; i < bigchars.length; i++) {bigchars[i] = factory.getBigChar(string.charAt(i));}}// 显示public void print() {for (int i = 0; i < bigchars.length; i++) {bigchars[i].print();}}
}
Main类
public class Main {public static void main(String[] args) {if (args.length == 0) {System.out.println("Usage: java Main digits");System.out.println("Example: java Main 1212123");System.exit(0);}BigString bs = new BigString(args[0]);bs.print();}
}
解析
-
Flyweight
按照通常方式编写程序会导致程序变重,所以如果能够共享实例会比较好,而Flyweight表示的就是那些实例会被共享的类。
-
FlyweightFactory
FlyweightFactory是生成Flyweight的工厂。在工厂中生成Flyweight可以实现共享实例。
-
Client
Client使用FlyweightFactory来生成Flyweight。
Flyweight模式的主题是共享。在共享实例时,要想到“如果要改变被共享的对象,就会对多个地方产生影响”。在决定Flyweight中的字段时,需要精挑细选。只将那些真正应该在多个地方共享的字段定义在Flyweight中即可。应当共享的信息叫做Intrinsic信息,不应当共享的信息被称作Extrinsic信息。
—————————————未完待续———————————————————
Reference
图解设计模式 【日】结成浩 著 杨文轩 译