管理状态

一、Observer 模式--发送状态变化通知

1. 场景

观察者会观察一个会生成数值的对象,将生成的数字结果展示,不同的观察者展示的效果不同

2. UML

image.png

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. 总结与思考

image.png
  1. 注意调用的顺序:我们不同的观察者之间不能产生因为顺序而导致的影响
  2. 除此之外,有些情况,我们的观察者可能会对 subject 进行操作,但是操作带来的是 subject 的改变,改变又导致观察者需要观察。所以可能会有死循环的危险。
  3. 在程序中,我们是直接将 public abstract void update(NumberGenerator generator); 被观察者本身丢了进去,也可以根据需要,将我们关心的参数丢进去,等等
  4. Java 中,java.util.Observer 就是使用的该模式,工具的预期作用也是如此,除此之外,spring 中定义了许多监听器,Listener 其实也是这种设计模式。
  5. 现在我们常称这种模式叫做,发布-订阅,Publish-Subscribe。因为,其实我们的观察并非主动,而是被动的。


二、Memento 模式--保存对象状态

1. 场景:

image.png

这种感觉很像我们打游戏时候的存档,但是书中还提到了一些细节:如果我们像恢复示例,需要一个可以自由访问实例内部结构的权限。但是,如果稍不注意,又可能会加你个依赖于实例内部结构的额代码分散的编写在程序中的各个地方,导致程序变得难以维护,这就是“破坏了封装性”。

2. UML

image.png

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. 场景

image.png

2.UML

image.png

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();
                }
            }
        }
    }
}
image.png

4. 总结于思考:

  1. 跟我们的传统实现有什么不同呢?传统实现,当我们调用 方法的时候,我们要判断 当前是什么时间?根据 if-else 的条件走不同的分支。
  2. 感受到差异了么,简单来说,传统方式是用方法来判断状态,后者是通过类来表示状态。
  3. 使用面向对象的思考角度,将状态转化为不同的类型,不同的类型实现的效果不同,这样的好处就是,分而治之,如果有 10 种状态呢,再用 if -else 考虑起来是不是非常复杂,但是使用 state 只需要告诉我,这种状态下的操作是什么即可。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容