观察者模式-精简举例阐释

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">1.概念</span>
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式;
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

2.模式中的角色
抽象主题角色:Subject(被观察者角色)
     把所有对观察者对象的引用保存在一个聚集里(ArrayList),每个主题都可以有任何数量的观察者;提供了增加和删除观察者对象接口及通知方法(只给所有已注册的观察者通知)。
具体主题角色:ConcreteSubject(具体被观察者角色)
     将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有登记过的观察者发出通知。
观察者角色接口:Observer(抽象观察者)
     为所有具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
具体观察者角色:ConcreteObserver(具体观察者角色)
     存储与主题状态相同的状态,同时实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态保持协调;如果需要,还可以保持一个指向具体主题对象的引用。

3.举例(以报刊发布-订阅为例)
概念太抽象,精简下:一个被关注对象的状态在改变的同时,引起关注该对象的观察者状态同时改变,并要求它们之间的耦合度不能太高(这是关键)。
被关注对象:报社
观察者:小报刊亭(关注最新杂志)
状态:最新杂志
3.1简单说明
一个报社每次都会发布新消息(杂志),有3个小报刊亭,由于经费问题,他们未向报社订阅杂志,每次都是自己去报社取最新的杂志,由于取的时间有早有晚,导致它们的杂志有时不是最新的;由于杂志热销,现在3个小报刊亭要求每次都拿到最新杂志,他们一商量,决定向报社统一订阅,后续报社一发新杂志,就给他们送去,这样就能保证它们的杂志是最新的了(这就是观察者模式中push方式的一种,由报社主动推送指定状态,还有一种是pull方式,大家另外到网上搜搜)。

3.2场景描述

报社发布最新杂志为第十期杂志,由于关注方(观察者)报刊亭1,2未准时去取杂志,它们的最新杂志分别停留在第八期和第九期上(未使用观察者模式)。
------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------
被关注对象:杂志社[最新杂志=最新消息:第十期杂志]
关注方:报刊亭1, 最新杂志=最新消息:第八期杂志]
关注方:报刊亭2, 最新杂志=最新消息:第九期杂志]
关注方:报刊亭3, 最新杂志=最新消息:第十期杂志]

报刊亭1,2,3向报社订阅了最新杂志,每次报社一发布最新杂志,就给它们送一份最新的杂志(使用观察者模式)。
------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----
被关注对象:杂志社[最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭1, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭2, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭3, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]

3.3代码实现
实际只需要两个类就可以了,但为了尽量减少类之间的耦合度,进行了抽象类和接口的抽取,变成了四个类(接口和抽象类属于特殊的类)。
1.报社抽象类(被观察主题):内部维护所有订阅最新杂志的报刊亭(内部维护了一个已注册观察者集合)、报社添加报刊亭方法(注册观察者)、报社移除报刊亭方法(移除观察者)、通知所有报刊亭更新杂志方法(将新状态通知给所有已注册观察者);
2.实际报社类(实际被观察主题):内部维护了最新杂志(被关注对象),发布最新杂志方法(状态改变方法)-报社杂志改变及调用父类通知所有报刊亭杂志改变方法;
3.更新杂志接口(观察者的更新状态方法):内部声明一个更新杂志的方法,需要具体的子类实现(更灵活、同时降低耦合度,不同的子类可以有不同的实现方式);
4.具体报刊亭实现更新杂志接口:将旧杂志替换为新杂志(this.observerStatus = newStatus)。

1.报社抽象类(公共代码部分,不管是什么报社,都需要拥有这些代码,被观察主题Subject)
public abstract class Subject {
	/**
	 * 已注册观察者集合(报刊亭集合)
	 */
	private List<Observer> observers = new ArrayList<Observer>();
	
	/**
	 * 注册观察者(报社添加报刊亭方法)
	 * @param observer 待注册的观察者
	 */
	public void addObserver(Observer observer){
		observers.add(observer);
	}
	
	/**
	 * 移除注册观察者(<span style="font-family: Arial, Helvetica, sans-serif;">报社移除报刊亭方法</span>)
	 * @param observer 待移除的注册观察者
	 */
	public void deleteObserver(Observer observer){
		observers.remove(observer);
	}
	
	/**
	 * 将新状态通知给所有已注册观察者(关键点:给子类调用,报社发布新杂志,并给所有订阅的报刊亭一份最新杂志)
	 * @param newStatus
	 */
	public void notifyObservers(String newStatus){
		for(Observer observer:observers){
			observer.update(newStatus);
		}
	}
}
2.实际报社类(ConcreteSubject):继承抽象报社类,内部维护被关注对象(最新杂志,String status),状态改变方法(发布最新杂志)-本身杂志改变及调用父类通知所有报刊亭方法。
public class ConcreteSubject extends Subject {
	private String status;//被关注状态:最新杂志

	public ConcreteSubject(String status) {
		this.status = status;
	}
	
	// 状态发生改变,自己改变同时通知各个观察者(报社发布最新杂志)
	public void changeStatus(String newStatus) {
		this.status = newStatus;
		this.notifyObservers(newStatus);
	}

	@Override
	public String toString() {
		return "被关注对象:杂志社[最新杂志=" + status + "]";
	}
}
3.更新杂志接口(观察者的更新状态方法):内部声明一个更新杂志的方法,需要具体的子类实现(更灵活、同时降低耦合度,不同的子类可以有不同的实现方式);
public interface Observer {
	/**
	 * 观察者状态更新(最新杂志更新)
	 * @param status
	 */
	public void update(String status);
}
4.具体报刊亭实现更新杂志接口:将旧杂志替换为新杂志(this.observerStatus = newStatus)。
public class ConcreteObserver implements Observer{
	private int id;
	private String observerStatus;
	
	public ConcreteObserver(int id, String observerStatus) {
		this.id = id;
		this.observerStatus = observerStatus;
	}

	@Override
	public void update(String newStatus) {
		this.observerStatus = newStatus;
	}

	@Override
	public String toString() {
		return "关注方:报刊亭" + id + ", 最新杂志="
				+ observerStatus + "]";
	}
}
<pre name="code" class="html">3.4测试
 
 
<span style="white-space:pre">	</span>public void testUpdate() throws Exception {
		//创建4个对象:实际报社类(实际被观察主题)、三个报刊亭(观察者)及它们未关联前的状态
		ConcreteSubject subject = new ConcreteSubject("最新消息:第十期杂志");
		ConcreteObserver observer1 = new ConcreteObserver(1,"最新消息:第八期杂志");//由于某些原因,未获取到最新杂志消息(不正常)
		ConcreteObserver observer2 = new ConcreteObserver(2,"最新消息:第九期杂志");//同上
		ConcreteObserver observer3 = new ConcreteObserver(3,"最新消息:第十期杂志");//获取到最新消息,与主题保持同步(正常)
		
		//打印报社和3个报刊亭的最新杂志
		System.out.println("------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------");
		System.out.println(subject);
		System.out.println(observer1);
		System.out.println(observer2);
		System.out.println(observer3);
		
		//为确保报刊亭杂志与报社保持一致,报刊亭订阅该杂志
		subject.addObserver(observer1);
		subject.addObserver(observer2);
		subject.addObserver(observer3);
		
		//报社发布新杂志,查看报社及报刊亭的所有最新杂志是否一致
		System.out.println("------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----");
		subject.changeStatus("最新消息:第十一期杂志,SDR入篮了!");//重点:杂志发表新消息后,所有订阅方都必须获取到最新消息
		System.out.println(subject);
		System.out.println(observer1);
		System.out.println(observer2);
		System.out.println(observer3);
	}
测试结果:
------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------
被关注对象:杂志社[最新杂志=最新消息:第十期杂志]
关注方:报刊亭1, 最新杂志=最新消息:第八期杂志]
关注方:报刊亭2, 最新杂志=最新消息:第九期杂志]
关注方:报刊亭3, 最新杂志=最新消息:第十期杂志]
------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----
被关注对象:杂志社[最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭1, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭2, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭3, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值