一、Observer 模式--发送状态变化通知
1. 场景
观察者会观察一个会生成数值的对象,将生成的数字结果展示,不同的观察者展示的效果不同
2. UML
3. Code
3.1 Observer 观察者
/**
* 观察者,将被观察对象加载进来
* @author CSZ
*/
public interface Observer {
public abstract void update(NumberGenerator generator);
}
3.2 NumberGenerator 被观察者
/**
* 观察对象
* @author CSZ
*/
public abstract class NumberGenerator {
// 观察者容器
private ArrayList<Observer> observers = new ArrayList<>();
// 注册观察者
public void addObserver(Observer observer){
observers.add(observer);
}
// 删除观察者
public void deleteObserver(Observer observer){
observers.remove(observer);
}
// 向 Observer 发通知
public void notifyObservers(){
Iterator<Observer> iterator = observers.iterator();
while (iterator.hasNext()){
Observer next = iterator.next();
next.update(this);
}
}
// 获取数值
public abstract int getNumber();
// 生成数值
public abstract void execute();
}
3.3 RandomNumberGenerator
/**
* 具体观察对象
* @author CSZ
*/
public class RandomNumberGenerator extends NumberGenerator{
private Random random = new Random();
private int number;
@Override
public int getNumber() {
return number;
}
// 当这个方法被执行后,会调用所有具体观察者的 update 方法
@Override
public void execute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
3.4 GraphObserver
/**
* 具体观察者1 有一个身为观察者该有的方法
* @author CSZ
*/
public class GraphObserver implements Observer{
@Override
public void update(NumberGenerator generator) {
System.out.println("GraphObserver:");
int count = generator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println();
try {
Thread.sleep(100);
}catch (InterruptedException e){}
}
}
3.5 DigitObserver
/**
* 具体观察者2 有一个身为观察者该有的方法
* @author CSZ
*/
public class DigitObserver implements Observer{
@Override
public void update(NumberGenerator generator) {
System.out.println("DigiObserver:" + generator.getNumber());
try {
Thread.sleep(100);
}catch (InterruptedException e){}
}
}
3.6 测试类
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
4. 总结与思考
- 注意调用的顺序:我们不同的观察者之间不能产生因为顺序而导致的影响
- 除此之外,有些情况,我们的观察者可能会对 subject 进行操作,但是操作带来的是 subject 的改变,改变又导致观察者需要观察。所以可能会有死循环的危险。
- 在程序中,我们是直接将 public abstract void update(NumberGenerator generator); 被观察者本身丢了进去,也可以根据需要,将我们关心的参数丢进去,等等
- Java 中,java.util.Observer 就是使用的该模式,工具的预期作用也是如此,除此之外,spring 中定义了许多监听器,Listener 其实也是这种设计模式。
- 现在我们常称这种模式叫做,发布-订阅,Publish-Subscribe。因为,其实我们的观察并非主动,而是被动的。
二、Memento 模式--保存对象状态
1. 场景:
这种感觉很像我们打游戏时候的存档,但是书中还提到了一些细节:如果我们像恢复示例,需要一个可以自由访问实例内部结构的权限。但是,如果稍不注意,又可能会加你个依赖于实例内部结构的额代码分散的编写在程序中的各个地方,导致程序变得难以维护,这就是“破坏了封装性”。
2. UML
3. Code
4.1 Memento 【Memento角色】用于将 【Originator】的内部信息整合保存
/**
* @author CSZ
*/
public class Memento {
int money;
ArrayList<String> fruits;
public int getMoney(){
return money;
}
public Memento(int money) {
this.money = money;
this.fruits = new ArrayList<>();
}
void addFruit(String fruit){
fruits.add(fruit);
}
List<String> getFruits(){
return (List<String>)fruits.clone();
}
}
4.2 Gamer 【Originator角色】
/**
* 他有两种操作,更新自己的 memento 并返回,或者将之前的 memento 重新获取,并覆盖现在状态。
* @author CSZ
*/
public class Gamer {
private int money;
private List<String> fruits = new ArrayList<>();
private Random random = new Random();
private static String[] fruitsname = {
"苹果","葡萄","香蕉","橘子"
};
public Gamer(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void bet(){
int dice = random.nextInt(6) + 1;
if (dice == 1){
money += 100;
System.out.println("金币增加");
} else if (dice == 2){
money /= 2;
System.out.println("金币减半");
} else if (dice == 6){
String f = getFruit();
System.out.println("获得水果" + f);
fruits.add(f);
} else{
System.out.println("什么也没发生");
}
}
public Memento createMemento(){
Memento memento = new Memento(money);
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.startsWith("好吃的")){
memento.addFruit(next);
}
}
return memento;
}
public void restoreMemento(Memento memento){
this.money = money;
this.fruits = memento.fruits;
}
@Override
public String toString() {
return "Gamer{" +
"money=" + money +
", fruits=" + fruits +
'}';
}
private String getFruit(){
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
4.3 测试类
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
Gamer gamer = new Gamer(100);
// 创建全局存档点
Memento memento = gamer.createMemento();
for (int i = 0; i < 100; i++) {
System.out.println("======" + i);
System.out.println("当前状态:" + gamer);
gamer.bet();
System.out.println("所持金币为:" + gamer.getMoney() + "元");
if (gamer.getMoney() > memento.getMoney()){
System.out.println("所持金币增加,保存状态");
// 如果有收益,创建新的存档点,并赋给全局存档点
memento = gamer.createMemento();
}else if (gamer.getMoney() < memento.getMoney()){
System.out.println("所持金币减少,恢复至以前的状态");
// 如果当前操作没有收益,直接撤回到全局存档点。
gamer.restoreMemento(memento);
}
}
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
三、State 模式--用类表示状态
1. 场景
2.UML
3. Code
3.1 Context
/**
* @author CSZ
*/
public interface Context {
// 设置时间
void doClock(Context context,int hour);
// 使用金库
void doUse(Context context);
// 按下警铃
void doAlarm(Context context);
// 正常通话
void doPhone(Context context);
}
3.2 State
/**
* @author CSZ
*/
public interface State {
// 设置时间
void doClock(Context context,int hour);
// 使用金库
void doUse(Context context);
// 按下警铃
void doAlarm(Context context);
// 正常通话
void doPhone(Context context);
}
3.3 DayState
/**
* 对于 白天状态(真正的某种状态),我们只需要一个实例就够了,所以用的是 singleton 模式
* @author CSZ
*/
public class DayState implements State{
private static DayState singleton = new DayState();
private DayState() {
}
public static State getInstance(){
return singleton;
}
/**
* 这就是核心关键了,我们称如下方法的作用是 状态转移。
* 当我们判定时间是白天,不做处理,如果是 晚上 就会进入 if()
* if 中 会传入全局 context 对象,(我个人喜欢把 context叫做 全局环境)并且参数指定为 夜晚状态
*/
@Override
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour){
context.changeState(NightState.getInstance());
}
}
@Override
public void doUse(Context context) {
// 因为这个类就是为了 白天这种状态准备的,所以没有 if else 的判断
// 而以下的方法都是通过调用 context 中的 方法来实现的,还记得这种双重绑定的形式么
context.recordLog("白天:金库");
}
@Override
public void doAlarm(Context context) {
context.callSecurityCenter("白天:按下警铃");
}
@Override
public void doPhone(Context context) {
context.callSecurityCenter("白天:正常通话");
}
@Override
public String toString() {
return "[ 白天 ]";
}
}
3.4 NightState
/**
* @author CSZ
*/
public class NightState implements State{
private static NightState singleton = new NightState();
private NightState() {
}
public static State getInstance(){
return singleton;
}
@Override
public void doClock(Context context, int hour) {
if ( 9 <= hour && hour <17 ){
context.changeState(DayState.getInstance());
}
}
@Override
public void doUse(Context context) {
context.callSecurityCenter("紧急:晚上使用金库");
}
@Override
public void doAlarm(Context context) {
context.callSecurityCenter("晚上:警铃");
}
@Override
public void doPhone(Context context) {
context.recordLog("晚上通过录音");
}
@Override
public String toString() {
return "[ 晚上 ]";
}
}
3.5 SafeFrame
/**
* context 的实现类,他来总控管理状态
* @author CSZ
*/
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("use golden");
private Button buttonAlarm = new Button("push it");
private Button buttonPhone = new Button("on phone");
private Button buttonExit = new Button("over");
// 设定了 初始状态
private State state = DayState.getInstance();
// 为了展示 配置了一些信息
public SafeFrame(String title) throws HeadlessException {
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();
show();
// 配置监听器
buttonUse.addActionListener(this);
buttonAlarm.addActionListener(this);
buttonPhone.addActionListener(this);
buttonExit.addActionListener(this);
}
// 响应监听器事件
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
// 这里的 if-else 只是为了针对绑定事件的处理,跟设计模式无关
if (e.getSource() == buttonUse){
// 但是下面就是最有意思的时刻了:我们没有关注到底是 白天还是黑夜,直接用 state 调用了他的方法
// state 调用 类中的 doUse 方法,而在 doUse 方法中,又使用了 context 的方法
// 最终 在 context 中定义的由 具体state 指定参数的方法 的效果被执行。
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("?");
}
}
// 时间的变动影响状态的变化
@Override
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);
}
// 状态转移的方法
@Override
public void changeState(State state) {
System.out.println("从" + this.state + "状态变为了" + state + "状态。");
this.state = state;
}
@Override
public void callSecurityCenter(String msg) {
textScreen.append("call!" + msg + "\n");
}
@Override
public void recordLog(String msg) {
textScreen.append("record..." + msg + "\n");
}
}
3.6 测试类
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
SafeFrame state_sample = new SafeFrame("state sample");
while (true){
for(int hour = 0; hour < 24 ; hour++){
state_sample.setClock(hour);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
4. 总结于思考:
- 跟我们的传统实现有什么不同呢?传统实现,当我们调用 方法的时候,我们要判断 当前是什么时间?根据 if-else 的条件走不同的分支。
- 感受到差异了么,简单来说,传统方式是用方法来判断状态,后者是通过类来表示状态。
- 使用面向对象的思考角度,将状态转化为不同的类型,不同的类型实现的效果不同,这样的好处就是,分而治之,如果有 10 种状态呢,再用 if -else 考虑起来是不是非常复杂,但是使用 state 只需要告诉我,这种状态下的操作是什么即可。