设计模式之Observer
来源:互联网 发布:剑灵可爱召唤捏脸数据 编辑:程序博客网 时间:2024/06/05 14:11
Observer
tags: design pattern,Observer
要求:
模拟以下情景:
小孩在睡觉
醒了之后要吃东西
第一种设计方法
(说实话这是我第一反应想到的方法,我果然还是图样图森破。。。)
有一个Dad类, 有一个Child类, Dad类持有Child类的引用, Dad监测着Child, 如果Child醒了, Dad就调用feed方法去喂小孩。
package simulation;class Child implements Runnable { private boolean wakeUp = false; public void wakeUp() { this.wakeUp = true; } public boolean isWakeUp() { return this.wakeUp; } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); }}class Dad implements Runnable { private Child child; public Dad(Child c) { this.child = c; } @Override public void run() { while (!child.isWakeUp()) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } this.feed(this.child); } public void feed(Child c) { System.out.println("feed child"); }}public class Test { public static void main(String[] args) { Child c = new Child(); new Thread(new Dad(c)).start(); }}
分析:
程序可行是可行, 但是有极其不合理的地方:Dad每隔一秒钟看一下Child, 完全干不了别的事。–> CPU的资源被无端消耗, 上面的代码在效率和资源消耗上都有很大问题!!!
那么应该如何改进呢?
第二种设计方法:
化主动为被动!
把Dad主动监测Child变为被动监测, 就是说, 反过来, 让Child监测Dad。换句话说, 在Child睡觉的时候Dad可以干别的事, 但Child一醒过来,Dad马上过来喂他吃东西。
这时候把上面的代码修改成下面的:
package simulation;class Child implements Runnable { private Dad dad; // private boolean wakeUp = false; public Child(Dad d) { this.dad = d; } public void wakeUp() { // this.wakeUp = true; this.dad.feed(this); } // public boolean isWakeUp() { // return this.wakeUp; // } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); }}class Dad { public void feed(Child c) { System.out.println("feed child"); }}public class Test { public static void main(String[] args) { Dad d = new Dad(); Child c = new Child(d); new Thread(c).start(); }}
这时候Dad完全可以不用作为一个线程类, Dad这个类里也可以只保留feed这个方法, Child中持有Dad的引用, Child一醒过来, Dad就调用feed方法。
这样修改之后, 明显比第一种方法更具有效率。
但是作为设计来讲, 在一个程序当中, 如果只考虑当前而没有预料到将来一定时间内将会发生的变化, 那么程序不具有可扩展性,弹性很差。
比如上面这个情景, Child醒过来这件事,包含了许多信息:几点醒过来?是在早上还是在晚上?
睡了多久?在哪里醒过来?等等。 针对不同的事件信息,作为监测者的Dad应该有不同的处理方式, 不能说一醒过来就喂, 如果是晚上Child刚吃完饭睡了一下, 醒过来, Dad又喂他吃东西, 那么Child就撑死了。
所以第二种方法仅仅是把程序写通,可扩展性很差。
对于事件的处理
Child醒过来这件事的发生包含了许多具体情况(具体信息),应该把这些情况告诉监测者, 也就是Dad, Dad根据这件事情的具体情况, 来做出具体的处理方式。
因此把事件抽象出来,封装成另外一个类。
package simulation;class WakeUpEvent { private long time; private String location; private Object source; public WakeUpEvent(long time, String location, Object source) { 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 Object getSource() { return source; } public void setSource(Object source) { this.source = source; }}class Child implements Runnable { private Dad dad; // private boolean wakeUp = false; public Child(Dad d) { this.dad = d; } public void wakeUp() { // this.wakeUp = true; this.dad.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(), "bed", this)); } // public boolean isWakeUp() { // return this.wakeUp; // } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); }}class Dad { public void actionToWakeUp(WakeUpEvent event) { // do something according to the event }}public class Test { public static void main(String[] args) { Dad d = new Dad(); Child c = new Child(d); new Thread(c).start(); }}
注意上面WakeUpEvent
这个类里面, 属性除了有time
和location
之外还有一个source
, 而且类型是Object
(其实也可以写成Child
类型, 但为了更像AWT, 所以写成Object
),这里表示一个事件源对象,就是发生这件事的对象。比如说Child醒了这个事件, Child就是事件源。
然后再思考:Child一醒过来Dad就要喂他, 那么喂这个动作就已经被固定下来了。假如Child醒来之后不想让Dad喂他, 而是想让Dad抱他出去玩,那么明显feed这个方法已经不合适了。更灵活的方法是:Child一醒过来, 发生了这么一件事, Dad便对这件事做出反应,至于是喂他还是抱他出去玩都可以。
所以把Dad
这个类中原来的feed方法修改了,方法名改成了actionToWakeUp
,参数改成了WakeUpEvent event
。 在方法体内, 便可以增加判断,根据事件的不同具体信息做出不同的反应。这样写明显比使用feed方法灵活得多。
继续思考:如果现在不只有Dad, Child醒过来之后,他的Grandpa也要做出反应,那么应该怎么办呢?按照之前的思路, 增加一个Grandpa类, 里面也有一个actionToWakeUp(WakeUpEvent event)
方法,另外在Child这个类里增加一个Grandpa的引用。那么如果现在不仅是爸爸、爷爷对小孩醒过来做出反应,小孩的妈妈、奶奶、外公、外婆甚至是家里的狗都要做出反应呢?那岂不是要不断的修改Child这个类的源代码?!在OO里面有一个极其重要的核心原则————OCP, open close principle, 开闭原则, 对扩展开放, 对修改关闭。上面的方法要不断修改Child的源代码,显然是不符合这个原则的, 说明了设计还不到位!
第三种设计方法 Observer
现在有好多好多监测小孩醒过来这件事的人,而且每个人对这件事的响应各不相同,但是有一个共同点,可以把响应的方法都叫actionToWakeUp(WakeUpEvent event)
,参数都是小孩醒过来这件事。这时候可以考虑使用接口把变化的这部分给抽象出来,因为接口抽象出来的是一系列的类所具有的共同特点。
因此增加一个WakeUpListener
的接口,里面有actionToWakeUp(WakeUpEvent event)
这个方法。 然后让Dad和Grandpa这两个类实现这个接口。
这时候再思考一下,为什么这个方法里传的参数是WakeUpEvent而不是Child? 假如写成Child, 那么这个方法就只能用在小孩身上了,但是如果是WakeUpEvent,那么小狗醒了也可以用这个方法,小猫醒了也可以用这个方法,也就是说, 事件本身也是和事件源是脱离的。这个时候灵活性最高!
把上面的代码修改:
package simulation;import java.util.*;class WakeUpEvent { private long time; private String location; private Object source; public WakeUpEvent(long time, String location, Object source) { 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 Object getSource() { return source; } public void setSource(Object source) { this.source = source; }}class Child implements Runnable { private List<WakeUpListener> listeners = new ArrayList<WakeUpListener>(); public void addWakeUpListener(WakeUpListener listener) { this.listeners.add(listener); } public void wakeUp() { for (Iterator<WakeUpListener> it = this.listeners.iterator(); it .hasNext();) { WakeUpListener l = it.next(); l.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(), "bed", this)); } } // public boolean isWakeUp() { // return this.wakeUp; // } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); }}interface WakeUpListener { public void actionToWakeUp(WakeUpEvent event);}class Dad implements WakeUpListener { public void actionToWakeUp(WakeUpEvent event) { System.out.println("feed child"); }}class Grandpa implements WakeUpListener { public void actionToWakeUp(WakeUpEvent event) { System.out.println("hug child"); }}public class Test { public static void main(String[] args) { Dad d = new Dad(); Child c = new Child(); c.addWakeUpListener(d); new Thread(c).start(); }}
注意:这时候Child这个类里没有Dad或者Grandpa的引用, 而是改成了一个List<WakeUpListener>
, 并且多了一个addWakeUpListener(WakeUpListener listener)
的方法,当需要添加监听器的时候,添加一个实现了WakeUpListener
这个接口的类, 在main方法里new一个新的监听器对象,然后直接调用这个方法添加便可以,完全无需修改Child的代码。扩展程序而无需修改Child的源代码, 这才符合OCP原则!
比如我还想添加一个小狗, 他也在监听着小孩醒来这件事:
package simulation;import java.util.*;class WakeUpEvent { private long time; private String location; private Object source; public WakeUpEvent(long time, String location, Child source) { 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 Object getSource() { return source; } public void setSource(Object source) { this.source = source; }}class Child implements Runnable { private List<WakeUpListener> listeners = new ArrayList<WakeUpListener>(); public void addWakeUpListener(WakeUpListener listener) { this.listeners.add(listener); } public void wakeUp() { for (Iterator<WakeUpListener> it = this.listeners.iterator(); it .hasNext();) { WakeUpListener l = it.next(); l.actionToWakeUp(new WakeUpEvent(System.currentTimeMillis(), "bed", this)); } } // public boolean isWakeUp() { // return this.wakeUp; // } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); }}interface WakeUpListener { public void actionToWakeUp(WakeUpEvent event);}class Dad implements WakeUpListener { public void actionToWakeUp(WakeUpEvent event) { System.out.println("feed child"); }}class Grandpa implements WakeUpListener { public void actionToWakeUp(WakeUpEvent event) { System.out.println("hug child"); }}class Dog implements WakeUpListener { public void actionToWakeUp(WakeUpEvent event) { System.out.println("Wang!!!!"); }}public class Test { public static void main(String[] args) { Dad d = new Dad(); Grandpa g = new Grandpa(); Child c = new Child(); c.addWakeUpListener(d); c.addWakeUpListener(g); Dog dog = new Dog(); c.addWakeUpListener(dog); new Thread(c).start(); }}
由上面的代码可以看见, 只是新建了一个Dog的类, 然后再在main方法里添加了
Dog dog = new Dog(); c.addWakeUpListener(dog);
这两句话,其他的完全没有修改。主要的逻辑类Child完全没有变,可扩展性很高!
再进一步思考!假如我现在有一个Student类,他也可以发出WakeUpEvent这件事,那么我只需要复制Child里的代码到Student这个类里面就可以了,WakeUpEvent这个类也得到了复用,灵活性更高!!!想想AWT里面,除了Button这个类会发出ActionEvent这件事, TextField也会, 因此ActionEvent也被重用了。在这里,Button相当于Child, Textfield相当于Student, ActionEvent相当于WakeUpEvent。
另外还可以封装一个CryEvent类,HappyEvent类等等各种各样的event,然后再封装一个abstract class Event
,让各种event从这个抽象类继承,方法传参数的时候形参定义为Event类型,这样灵活性更强。
反思与总结
从一开始第一种设计方法的一两个类实现了基本功能,到最后的多个类、接口,可以看到:使用设计模式是一把双刃剑。
优点:
- 可扩展性强
- 维护成本降低
缺点:
- 复杂度增加
- 开发成本增加
但是切忌为了使用设计模式而使用设计模式,要具体问题具体分析。
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之--Observer
- 设计模式之--Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之Observer
- 设计模式之 observer
- Java基础视频教程第17天_Collections、Arrays、1.5新特性
- 【Unity技巧】Unity中的优化技术
- Leetcode NO.179 Largest Number
- Linux Shell 脚本编程从入门到精通 (笔记1)
- c- structure initialization
- 设计模式之Observer
- Java并发编程实践 重庆大学 学习笔记 I
- How I Became A Madman
- Java最大的优势真的在于跨平台吗?
- 在Virtualbox中的Ubuntu虚拟机中,安装Guest Additions客户端增强包时出错:分配介质 虚拟光盘 xxx\VBoxsGuestAdditions.iso 到虚拟电脑 xxx 失
- [我的翻译]效代码审查:来自前质疑者的9个建议
- Java-类初始化顺序-2
- VirtualBox 4.3.12安装ubuntu 14.04 分辨率过小问题的解决方法
- [我的翻译]30分钟编写一个Flask应用