定义一下观察者模式:

  观察者模式又叫  发布-订阅  模式,定义的两个对象之间是一种一对多的强依赖关系,当一个对象的状态发生改变,所有依赖它的对象

将得到通知并自动更新(摘自Hand First)。

关键角色:

1.主题(Subject)

  抽象主题以及具体的主题

2.观察者(Observer)

  抽象观察者以及具体观察者

我们可以这样理解两者之间的关系:

这就好比一个多个用户订阅同一个微信公众号,当公众号有内容更新,就立马通知所有的订阅用户。如图:

举个例子:

先来定义一个主题抽象类Subject:

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:

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:

package org.observer;

/**
* Created by Administrator on 2016/11/24.
* 定义一个抽象的观察者
*/
public abstract class Observer { //观察者自动更新执行的方法,当主题发出通知,自动调用这个方法
public abstract void update( String message );
}

继承Observer的具体观察者Observer1:

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:

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方法:

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 );
     subject1.addObserver( o2 );
//主题发送内容
subject1.send( "今天天气变冷了" );
}
}

运行结果为:

再举个例子:

Swing中的事件驱动模型就是典型的观察者模式。

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

定义一个监听器(观察者):

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.消息的接受者。

  消息的发布者只需发布消息存放在消息队列中,不需要依赖任何的抽象接收者,而接收者

根据自己的订阅的主题从队列中接受消息,两者没有任何联系,自然也就不存在什么依赖关系,

这很好解决了观察者模式的不足。

模型简图:

代码如下:

消息队列类:

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 );
}
}

消息内容类:

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;
}
}

获取主题名字的工具类:

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文件,主要是用来存放全部主题名字:

#配置所有主题的名字
topicNames = news,sport

发送者:

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

接受者:

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方法:

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

运行结果为:

--------------------------------------------------------------------------------

浅谈Java五大设计原则之观察者模式的更多相关文章

  1. 浅谈Java五大设计原则之代理模式

    我们来定义一下  AOP(面向切面编程) 它是面向对象的一种补充或者是一种增强,它在这基础上增加了一些 而外的功能增强. 它可以在原有的行为不改变的前提,在这之前或者之后完成一些而外 的事情. 而AO ...

  2. 浅谈Java五大设计原则之责任链模式

    首先我们得先定义一个责任链模式: 责任链模式是一种线性执行流程,多个对象都有机会去执行同一个任务,只是在执行过程中, 由于执行的权利和范围不一样,那么当自己不能处理此任务时,就必须将这个任务抛给下一个 ...

  3. 浅谈java中内置的观察者模式与动态代理的实现

    一.关于观察者模式 1.将观察者与被观察者分离开来,当被观察者发生变化时,将通知所有观察者,观察者会根据这些变化做出对应的处理. 2.jdk里已经提供对应的Observer接口(观察者接口)与Obse ...

  4. 浅谈PHP代码设计结构

    浅谈PHP代码设计结构 您的评价:       还行  收藏该经验       coding多年,各种代码日夜相伴,如何跟代码友好的相处,不光成为职业生涯的一种回应,也是编写者功力的直接显露. 如何看 ...

  5. 浅谈Android五大布局

    Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...

  6. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  7. 浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

  8. 【转】浅谈Java中的hashcode方法(这个demo可以多看看)

    浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native i ...

  9. 浅谈Java的集合框架

    浅谈Java的集合框架 一.    初识集合 重所周知,Java有四大集合框架群,Set.List.Queue和Map.四种集合的关注点不同,Set 关注事物的唯一性,List 关注事物的索引列表,Q ...

随机推荐

  1. 【Swift学习】Swift编程之旅---函数(十)

    函数是一组用于执行特定任务的独立的代码段,你用一个名字来标识函数,这个名字是用来“调用”函数来执行它的任务. swift统一函数的语法具有足够的灵活性来表达任何一个简单的不带参数的名称与本地和外部的每 ...

  2. OpenJudge 666:放苹果 // 瞎基本DP

    666:放苹果 总时间限制:  1000ms     内存限制:  65536kB 描述 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1 ...

  3. OpenJudge1700:八皇后问题 //不属于基本法的基本玩意

    1700:八皇后问题//搜索 总时间限制:  10000ms 内存限制:  65536kB 描述 在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方. 输入 无输入. 输出 按给定顺序和 ...

  4. 一个ActionResult中定位到两个视图—<团委项目>

         在使用MVC做项目的时候一般的情况就是一个ActionResult一个视图,这样对应的Return View();就可以找到下面对应的视图,这是根据一个原则,"约定大于配置&quo ...

  5. Wamp 简单使用方法

    1.在wamp的安装目录 \bin\apache\Apache2.4.4\conf 中找到  httpd.conf文件删除 Include conf/extra/httpd-vhosts.conf 这 ...

  6. Sandcastle入门:创建C#帮助文档

    Sandcastle入门:创建C#帮助文档 今天学到了一个东西:利用vs2005生成的dll/xml来生成帮助文档. 完成这个伟大任务的是Sandcastle,微软推出的类库文档编译工具. 在开始这篇 ...

  7. javascript类型注意事项

    以下是javascript类型的注意事项: null:表示尚未存在的对象,注意,尽管尚未存在,也是个对象啊,所以用typeof检测一个null值变量的结果是Object:不过,为了便于写if语句,在j ...

  8. linux两个文件修改主机名

    linux修改主机名的方法 用hostname命令可以临时修改机器名,但机器重新启动之后就会恢复原来的值. #hostname   //查看机器名#hostname -i  //查看本机器名对应的ip ...

  9. C#编程总结(五)多线程带给我们的一些思考

    C#编程总结(五)多线程带给我们的一些思考 如有不妥之处,欢迎批评指正. 1.什么时候使用多线程? 这个问题,对于系统架构师.设计者.程序员,都是首先要面对的一个问题. 在什么时候使用多线程技术? 在 ...

  10. C ~ 链式队列与循环队列

          此处的链式与循环队列可以应用于BFS和树的层序遍历.下面是对其结构和基本操作的程序描述. 1.循环队列 解决循环队列的队空和队满的方法: [1].增加一个参数count,用来记录数组中当前 ...