设计模式-观察者模式

1. OOA and OOD 1.1 模拟程序

  • 小孩在睡觉
  • 醒来后要求吃东西
    • 思路1
      一个睡着的小孩(Kid), 一个人(Parent)专门看着这个小孩醒没醒(thread)。
    • 代码实现
package com.alan.planA; class Child implements Runnable { private boolean flag; public boolean isWakenUp() { return flag; }public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; } }class Dad implements Runnable { private Child c; public Dad(Child c) { this.c = c; }public void run() { while (!c.isWakenUp()) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } feed(c); }private void feed(Child c2) { System.out.println("feed child."); } }public class FeedChildSimulator { public static void main(String[] args) { Child c = new Child(); Dad d = new Dad(c); Thread thread1 = new Thread(d); Thread thread2 = new Thread(c); thread1.start(); thread2.start(); } }

  • 思路1的方式可行,但会无端的耗费太多的CPU资源,如果是一个人看孩子的话,那基本上没有时间干其他的事情了。所以,引申出思路2。
  • 思路2
    监听孩子醒了这件事情。主动监听会消耗CPU资源。最好的设计就是在小孩胳膊上绑一绳,他一醒过来,自动拉一下那个绳子就可以了,这时候Parent过来喂他就可以了。反客为主。
  • 代码实现
package com.alan.planB; class Child implements Runnable { private boolean flag; private Dad d; public Child(Dad d) { this.d = d; }public void isWakenUp() { flag = true; d.feed(this); }public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } isWakenUp(); } }class Dad { public void feed(Child c2) { System.out.println("feed child."); } }public class FeedChildSimulator { public static void main(String[] args) { Dad d = new Dad(); Child c = new Child(d); Thread thread = new Thread(c); thread.start(); } }

  • 思路三
    不同醒了的方式,他爸爸处理的方式也应该是不同的。事情的发生是包含着一些具体情况的。当孩子醒了的时候,应该把事情的具体情况(事件<事情的发生时间,地点等>)告诉他爸爸。该怎么做,用什么样的设计方法。
  • 代码实现
package com.alan.planC; class WakenUpEvent { private long time; private String location; private Child source; public WakenUpEvent(long time, String location, Child source) { super(); this.time = time; this.location = location; this.source = source; }public long getTime() { return time; }public void setTime(long time) { this.time = time; }public String getLocation() { return location; }public void setLocation(String location) { this.location = location; }public Child getSource() { return source; }public void setSource(Child source) { this.source = source; }}class Child implements Runnable { private Dad d; public Child(Dad d) { this.d = d; }public void isWakenUp() { d.actionToWakenUp(new WakenUpEvent(System.currentTimeMillis(), "bed", this)); }public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } isWakenUp(); } }class Dad { public void actionToWakenUp(WakenUpEvent wakenUpEvent) { // 根据时间,地点,事件源来具体做出处理 System.out.println("feed child."); } }public class FeedChildSimulator { public static void main(String[] args) { Dad d = new Dad(); Child c = new Child(d); Thread thread = new Thread(c); thread.start(); } }

  • 如果此时,当小孩醒了,除了他爸爸要做出响应之外,他爷爷,他奶奶...也要做出响应,应该如何是好,按照思路3的方式,小孩手上的线会越来越多。这说明思路3的这种设计也是不好的,接下来就引出思路4。
  • 思路4
    将变化的事件进行抽象,将绳子都按顺序绑在一个棍子上,将监听所有孩子醒了的这件事情的所有监听者做成一个集合。
  • 代码实现
package com.alan.planD; import java.util.ArrayList; import java.util.List; class WakenUpEvent { private long time; private String location; private Child source; public WakenUpEvent(long time, String location, Child source) { super(); this.time = time; this.location = location; this.source = source; }public long getTime() { return time; }public void setTime(long time) { this.time = time; }public String getLocation() { return location; }public void setLocation(String location) { this.location = location; }public Child getSource() { return source; }public void setSource(Child source) { this.source = source; }}class Child implements Runnable { private List wakenUpListeners = new ArrayList(); public void addWakenUpListener(WakenUpListener wul) { wakenUpListeners.add(wul); }public void isWakenUp() { for (WakenUpListener l : wakenUpListeners) { l.actionToWakenUp(new WakenUpEvent(System.currentTimeMillis(), "bed", this)); } }public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } isWakenUp(); } }class Dad implements WakenUpListener { public void actionToWakenUp(WakenUpEvent wakenUpEvent) { // 根据时间,地点,事件源来具体做出处理 System.out.println("feed child."); } }class GrandFather implements WakenUpListener { public void actionToWakenUp(WakenUpEvent wakenUpEvent) { // 根据时间,地点,事件源来具体做出处理 System.out.println("hug child."); } }/** * 实现这个接口的类,一定会监听WakenUp醒过来的这件事 */ interface WakenUpListener { // 对这样的事件进行响应,事件本身和事件源脱离 ,灵活度最高 public void actionToWakenUp(WakenUpEvent wakenUpEvent); }public class FeedChildSimulator { public static void main(String[] args) { Dad d = new Dad(); GrandFather gf = new GrandFather(); Child c = new Child(); c.addWakenUpListener(d); c.addWakenUpListener(gf); Thread thread = new Thread(c); thread.start(); } }

  • 基于JDK中Observer接口和Observable类实现观察者模式
package design.pattern.behavior.observer; import java.util.Observable; public class ConcreteSubject extends Observable { private int state; public int getState() { return state; }public void setState(int state) { this.state = state; this.setChanged(); this.notifyObservers(); } }package design.pattern.behavior.observer; import java.util.Observable; import java.util.Observer; public class MyObserver implements Observer {private int state; public int getState() { return state; }public void setState(int state) { this.state = state; }public void update(Observable o, Object arg) { state = ((ConcreteSubject)o).getState(); } }package design.pattern.behavior.observer; public class Client { public static void main(String[] args) { MyObserver o1 = new MyObserver(); MyObserver o2 = new MyObserver(); MyObserver o3 = new MyObserver(); System.out.println(o1.getState()); System.out.println(o2.getState()); System.out.println(o3.getState()); ConcreteSubject s = new ConcreteSubject(); s.addObserver(o1); s.addObserver(o2); s.addObserver(o3); s.setState(3000); System.out.println("state changed..."); System.out.println(o1.getState()); System.out.println(o2.getState()); System.out.println(o3.getState()); } }

1.2 弄清楚用户到底要什么, 具体什么要求。
  • 甲方: 我说:“我要一房子”。
  • 乙方: 上去直接把房子盖了出来。正正方方的。
  • 甲方: 我说我要的是一碉堡。
  • 乙方: 此时要哭的心都有了。
    此时的设计一定会完蛋,因为这是你以为的房子,不是客户想要的。在真正设计一个软件系统的时候,最重要的事情是什么啊?
    • 弄明白客户的具体需求到底是什么样子的!
      你要的房子要建成一个什么样子的呢?
    • 注意:越是钻到技术中拔不出来的人,这方面越是欠缺。当你的代码量有一定量的时候,你会注意到,要做好一个软件系统,首先你要保证第一点,就是你在开头上不能错。开头错了就是南辕北辙了。所以最重要的是弄明白客户的要求到底是什么。弄明白别人要什么的这个过程叫做分析。
    • 分析(要做什么)是弄明白客户要干什么,要弄明白你的软件系统要实现的功能是什么
    • 设计(这个功能怎么实现): 要达成同一功能,会有不同的方法。
    • 确定功能 > 用啥样的结构(合适的,具体问题具体分析)去实现这个功能。
    • 灵活一点,再灵活一点。
    • 设计模式刚开始可以往死里学,熟悉招式,知道其使用场景;下一个层次是想办法把招式穿起来,灵活运用;最高境界就是设计模式的名字忘了,让我设计,我依然可以设计的很到位。心中无招,手中也无招。但是以上都是建立在每一招你都非常熟悉的基础之上。
    • 以后在遇到问题时候,首先要想清楚问题的本身你有没有搞清楚。如果没有弄清楚,首先不要一下子就钻到技术层面去。确定需求的时候用什么语言其实已经不重要了。
    • 所以,一个真正的软件项目,首先第一步要做的是确定这个需求。怎么确定需求?客户不一定明白自己想要什么?婚姻的不幸就是由小小需求的不确定慢慢积累起来的。
    • 面向对象设计:
      怎样从需求中探索出我所需要的类,需不需要加各种各样的辅助类,到底哪些是实体类,哪些是管理的类,哪些是工具类。需不需要由各种的线程,需要什么样的架构等等。
      实体类(属性):找需求文档中的名词。
      方法:动词。
      一个类的属性进行只让自己读写。
    • 在软件的设计过程中,如果仅考虑到当前,这个设计会没有弹性,不具备可扩展性。预想到会有变化可以设计得出来,否则设计不出来。透彻的理解需求,想想需求未来会发生什么样子的变化。所以预想不到未来的需求,你的设计就做不到位,这是很重要的一点。假如说,预想的需求过了头了,也不行,叫过度设计(over design)。在一段时间内预想到一定时间的变化就够了,不要想太多了。设计功能的时候要想到你将来的扩展点在什么地方。
    • 将变化的事件进行抽象。
    • 做到Main方法不变,读配置文件的形式。
    • AWT
    • 用设计模式,是有成本的
    • 企业开发中实现需求,在有限时间完成功能最重要。
    • 先将功能实现,然后重构(refactor)。

    推荐阅读