浅谈Java五大设计原则之观察者模式
义一下观察者模式:
观察者模式又叫 发布-订阅 模式,定义的两个对象之间是一种一对多的强依赖关系,当一个对象的状态发生改变,所有依赖它的对象
将得到通知并自动更新(摘自Hand First)。
关键角色:
1.主题(Subject)
抽象主题以及具体的主题
2.观察者(Observer)
抽象观察者以及具体观察者
我们可以这样理解两者之间的关系:
这就好比一个多个用户订阅同一个微信公众号,当公众号有内容更新,就立马通知所有的订阅用户。如图:

举个例子:
先来定义一个主题抽象类Subject:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package org.theme;import org.observer.Observer;import java.util.ArrayList;import java.util.List;/** * Created by Administrator on 2016/11/24. * 定义一个抽象主题 */public abstract class Subject { //定义关注这个主题的额所有观察者 private List<Observer> list = new ArrayList<>(); //提供一个添加观察者的方法 //( 其实这种行为就是在对应的主题上注册一个观察者 ) public void addObserver( Observer observer ) { list.add( observer ); } //移除观察者 public void removeObserver( Observer observer ) { list.remove( observer ); } //通知,这个是主题的核心任务,当主题的状态发生改变时 //将通知所有注册这个主题的观察者对象 public void notify( String message ) { for ( Observer obs : list ) { obs.update( message ); } } //提供一个抽象的发布消息行为 public abstract void send( String message );} |
继承抽象主题的具体的主题类Subject1:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package org.theme;/** * Created by Administrator on 2016/11/24. */public class Subject1 extends Subject { @Override public void send( String message ) { System.out.println( "Subject1 收到了客户端发来的信息,立马通知对应的观察者" ); message += "。发送者:Subject One:"; notify( message ); }} |
抽象的观察者Observer:
|
1
2
3
4
5
6
7
8
9
10
11
|
package org.observer;/** * Created by Administrator on 2016/11/24. * 定义一个抽象的观察者 */public abstract class Observer { //观察者自动更新执行的方法,当主题发出通知,自动调用这个方法 public abstract void update( String message );} |
继承Observer的具体观察者Observer1:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package org.observer;/** * Created by Administrator on 2016/11/24. * 定义一个观察者 */public class Observer1 extends Observer { @Override public void update(String message) { System.out.println( "Observer1 收到了消息:" + message ); }} |
继承Observer的具体观察者Observer2:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package org.observer;/** * Created by Administrator on 2016/11/24. */public class Observer2 extends Observer { @Override public void update(String message) { System.out.println( "Observer2 收到了消息:" + message ); }} |
Main方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package org.main;import org.observer.Observer;import org.observer.Observer1;import org.observer.Observer2;import org.theme.Subject;import org.theme.Subject1;import org.theme.Subject2;/** * Created by Administrator on 2016/11/24. */public class Main { public static void main(String[] args) { //定义一个主题 Subject subject1 = new Subject1(); //定义观察者 Observer o1 = new Observer1(); //定义观察者 Observer o2 = new Observer2(); //观察者关注主题 subject1.addObserver( o1 );<br> subject1.addObserver( o2 ); //主题发送内容 subject1.send( "今天天气变冷了" ); }} |
运行结果为:

再举个例子:
Swing中的事件驱动模型就是典型的观察者模式。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package org2.main;import javax.swing.*;/** * Created by Administrator on 2016/11/24. * swing驱动事件 */public class MainFrame { //定义个容器变量 JFrame f = null; //定义个按钮变量 JButton btn = null; public MainFrame() { //创建容器 f = new JFrame( "事件模型" ); //创建按钮 btn = new JButton( "按钮" ); //把按钮添加到容器中 f.add( btn ); //设置容器大小 f.setSize( 300, 200 ); //把容器设置为 可见 f.setVisible( true ); //给按钮添加事件处理 //在btn上注册一个监听器 //(btn其实就是具体的主题对象) //(而监听器就是具体的观察者) //当点击按钮时(其实也就是主题对象发生了改变),就会触发监听器事件(观察者更新了) btn.addActionListener( new MyListener() ); } //main方法 public static void main( String[] args ) { new MainFrame(); }} |
定义一个监听器(观察者):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package org2.main;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/** * Created by Administrator on 2016/11/24. * 定义一个监听器(具体观察者) */public class MyListener implements ActionListener { //该方法类似于观察中的update方法 @Override public void actionPerformed( ActionEvent e ) { System.out.println( "按钮被点击,执行一些业务逻辑操作" ); }} |
运行结果:

总的来说,观察者模式所做的工作其实就是在解除耦合。让耦合双方都依赖抽象,
而不是具体。从而使得各自的变化都不影响其他一方。这也符合了依赖倒置原则。
那我们什么时候能用上观察者模式呢?
1.当一个对象的改变需要同时改变其他对象时。
2.一个对象不知道它的改变会影响多少个类的改变。
3.当有一个抽象模型有两个方面,一面依赖另一面。这时观察者模式可以很好地
将两者封装在独立的对象使它们各自独立改变和复用。
观察者模式的不足:
虽然如此,观察者模式还是存在着不足。“抽象主题”还是依赖了“抽象观察者”,万一没有
抽象观察者,通知功能就没法实现了。而且,不是每个 “具体观察者” 都会调用 “更新” 方
法或者说 “更新”方法的更新内容不是都相同的。
那有没有个好方法解决这个难点呢?
有,那就是 消息发布/订阅架构模式。
关键角色:
1.消息队列(存放消息和定制主题的核心)。
2.消息的发送方(将消息发布到消息队列中的人)。
3.消息的接受者。
消息的发布者只需发布消息存放在消息队列中,不需要依赖任何的抽象接收者,而接收者
根据自己的订阅的主题从队列中接受消息,两者没有任何联系,自然也就不存在什么依赖关系,
这很好解决了观察者模式的不足。
模型简图:

代码如下:
消息队列类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package org3.demo;import java.util.HashMap;import java.util.Map;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;/** * Created by Administrator on 2016/11/24. * 编写一个消息队列 */public class MessageQueue { //定义一个消息队列,这个队列中可以有多个子主题的集合 //map的key是主题的名称,value是一个阻塞队列 private Map<String, BlockingQueue<NewsMessage>> map = new HashMap<>(); //构造方法 public MessageQueue() { } //构造方法 public MessageQueue( String path ) { //解析并得到所有的主题名字 String[] topicNames = PropertiesUtil.gettopicNames( path ); for ( String name : topicNames ) { map.put( name, new LinkedBlockingQueue<>() ); } } //提供一个创建主题的方法// public void createTopic( String topicName ) {// //判断容器是否存在该主题// if ( !map.containsKey( topicName ) ) {// map.put( topicName, new LinkedBlockingQueue<NewsMessage>() );// }// } //将消息放入指定的消息队列中 public void put( String topicName, NewsMessage message ) { try { //从map中取根据ey取出对应的主题队列 BlockingQueue queue = map.get( topicName ); //add方法继承ArrayBlockingQueue,不会产生阻塞 //put方法,当queue满了就产生阻塞 queue.put( message ); } catch ( Exception e ) { e.printStackTrace(); } } //从指定的主题队列中获取消息 public NewsMessage take( String topicName ) { try { //从map中取根据ey取出对应的主题队列 BlockingQueue queue = map.get( topicName ); //当队列中没有消息,则会阻塞 NewsMessage message = ( NewsMessage )queue.take(); System.out.println( "消息队列大小为:" + queue.size() ); return message; } catch ( Exception e ) { e.printStackTrace(); } return null; } //移除主题的方法 public void removeTopic( String topicName ) { map.remove( topicName ); }} |
消息内容类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package org3.demo;/** * Created by Administrator on 2016/11/24. * 把发布的消息封装到一个类中 */public class NewsMessage { //定义字符串变量 private String content; //getter public String getContent() { return content; } //setter public void setContent(String content) { this.content = content; }} |
获取主题名字的工具类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package org3.demo;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.util.Properties;/** * Created by Administrator on 2016/11/24. * 该工具类主要从topics.properties文件中获取主题名字 */public class PropertiesUtil { //解析properties文件,获取所有主题名称 public static String[] gettopicNames( String path ) { //创建一个Properties对象 Properties pro = new Properties(); try { //创建一个输出流读取properties文件 InputStream fis = PropertiesUtil.class.getClassLoader().getResourceAsStream( path ); //将输入流交给Properties对象进行读写 pro.load( fis ); //获取字符串 String value = pro.getProperty( "topicNames" ); //把字符串切割为字符串数组 return split( value ); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } //分割字符串名称 private static String[] split( String value ) { return value.split( "," ); } //main方法测试 public static void main(String[] args) { String value[] = PropertiesUtil.gettopicNames( "topics.properties" ); for ( String v : value ) { System.out.println( v ); } }} |
topics.properties文件,主要是用来存放全部主题名字:
|
1
2
|
#配置所有主题的名字topicNames = news,sport |
发送者:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package org3.demo;/** * Created by Administrator on 2016/11/24. */public class SendUser extends Thread { //定义MessageQueue成员变量 private MessageQueue queue; public SendUser( MessageQueue queue ) { this.queue = queue; } public void run() { while ( true ) { //实例化消息内容对象 NewsMessage message = new NewsMessage(); //设置内容 message.setContent( "hello message queue" ); //将消息发送到指定的主题下面 queue.put( "news", message ); System.out.println( "发布了:" + message.getContent() ); try { //线程睡眠300毫秒 Thread.sleep( 300 ); } catch (InterruptedException e) { e.printStackTrace(); } } }} |
接受者:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package org3.demo;/** * Created by Administrator on 2016/11/24. */public class ReceiveUser extends Thread{ //定义MessageQueue成员变量 private MessageQueue queue; public ReceiveUser( MessageQueue queue ) { this.queue = queue; } public void run() { while ( true ) { //从消息队列中获取消息 NewsMessage message = queue.take( "news" ); System.out.println( "接受了:" + message.getContent() ); try { //线程睡眠500毫秒 Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); } } }} |
Main方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package org3.demo;/** * Created by Administrator on 2016/11/24. */public class Main { public static void main(String[] args) { //创建一个消息队列 MessageQueue queue = new MessageQueue( "topics.properties" ); //给消息队列创建一个子主题// queue.createTopic( "news" ); //创建消息的发送者 SendUser sender = new SendUser( queue ); //创建消息的接收者 ReceiveUser receiver = new ReceiveUser( queue ); //启动两个线程 sender.start(); receiver.start(); }} |
运行结果为:


浙公网安备 33010602011771号