Java设计模式--观察者模式

概述

是不是你平时安装程序的时候都会让你选择是否订阅通知,或者我们在网上买东西的物流过程中,每到一个新的进度点都会进行更新,以及通知。其实这里就用到了我们的观察者模式。

概念

观察者模式:定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
在这里插入图片描述
比如我们对于天气的订阅,是不是当我们的天气进行变化的时候,应用就会发出通知,告知我们天气的变化。图中的Subject就是天气,Observer就是用户。当天气进行变化的时候Subject中的Notify方法就会被调用,这样Observer中的Update方法就会做出相应的更新。而下面的两个分别是上面的实现类。
观察者模式是单向依赖的,观察者对象的状态是依赖于目标对象的,只有目标对象的状态发生改变,才会改变观察者对象的状态。
观察者模式主要有两种实现方法:推模型和拉模型。

  • 推模型

目标对象主动向观察者推送目标的详细信息,推送的信息通常是目标对象的全部或者部分数据。

  • 拉模型

目标对象在通知观察者的时候只传递少量信息。如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据,这种实现中一般会把目标对象通过Update方法传递给观察者。

观察者模式实现代码

手动实现

我们根据类图可以知道观察者模式主要是由4个类,Subject,Observer,ConcreteSubject,ConcreteObserver。在Subject中我们需要定义一个集合来储存我们的观察者对象,并且在其中要实现观察者的添加以及删除,并且要完成通知所有注册的观察者对象的方法。
Subject.java(推)

package com.xjh.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
 * @author Gin
 *
 */
public class Subject {

	//保存注册的观察者对象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//添加观察者
	public void attach(Observer observer) {
		observers.add(observer);
	}
	
	//删除观察者
	public void detach(Observer observer) {
		observers.remove(observer);
	}
	
	//通知所有注册的观察者对象
	protected void notifyObservers(String context) {
		for(Observer observer : observers) {
			observer.update(context);
		}
	}
}

Subject.java(拉)

package com.xjh.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
 * @author Gin
 *
 */
public class Subject {

	//保存注册的观察者对象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//添加观察者
	public void attach(Observer observer) {
		observers.add(observer);
	}
	
	//删除观察者
	public void detach(Observer observer) {
		observers.remove(observer);
	}
	
	//通知所有注册的观察者对象
	protected void notifyObservers() {
		for(Observer observer : observers) {
			observer.update(this);
		}
	}
}

然后定义观察者接口,里面拥有Update方法
Observer.java(推)

package com.xjh.observer;
/**
 * 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
 * @author Gin
 *
 */
public interface Observer {

	/**
	 * 更新接口
	 * @param subject 传入对象目标,方便获取相应的目标对象的状态
	 */
	public void update(String context);
}

Observer.java(拉)

package com.xjh.observer;
/**
 * 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
 * @author Gin
 *
 */
public interface Observer {

	/**
	 * 更新接口
	 * @param subject 传入对象目标,方便获取相应的目标对象的状态
	 */
	public void update(Subject subject);
}

之后就要编写实现类,在Subject的实现类中我们需要定义状态,用来更新,在set方法中调用父类的notifyObservers方法进行对观察者的通知。
ConcreteSubject.java(推)

package com.xjh.observer;
/**
 * 具体的目标对象,负责把有关状态存人到相应的观察者对象中
 * @author Gin
 *
 */
public class ConcreteSubject extends Subject {

	//目标对象状态
	private String subjectState;

	public String getSubjectState() {
		return subjectState;
	}

	public void setSubjectState(String subjectState) {
		this.subjectState = subjectState;
		this.notifyObservers(subjectState);
	}
	
}

ConcreteSubject.java(拉)

package com.xjh.observer;
/**
 * 具体的目标对象,负责把有关状态存人到相应的观察者对象中
 * @author Gin
 *
 */
public class ConcreteSubject extends Subject {

	//目标对象状态
	private String subjectState;

	public String getSubjectState() {
		return subjectState;
	}

	public void setSubjectState(String subjectState) {
		this.subjectState = subjectState;
		this.notifyObservers();
	}
	
}

然后我们在Observer的实现类中实现Update方法就好了。
ConcreteObserver.java(推)

package com.xjh.observer;
/**
 * 具体的观察者对象,实现更新的方法,使自身状态和目标的状态保持一致
 * @author Gin
 *
 */
public class ConcreteObserver implements Observer {

	//观察者的状态
	private int id;
	private String observerState;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	/**
	 * 获取目标类的状态同步到观察者的状态
	 */
	@Override
	public void update(String context) {
		observerState = context;
		System.out.println(id+":"+observerState);
	}

}

ConcreteObserver.java(拉)

package com.xjh.observer;
/**
 * 具体的观察者对象,实现更新的方法,使自身状态和目标的状态保持一致
 * @author Gin
 *
 */
public class ConcreteObserver implements Observer {

	//观察者的状态
	private int id;
	private String observerState;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	/**
	 * 获取目标类的状态同步到观察者的状态
	 */
	@Override
	public void update(Subject subject) {
		observerState = ((ConcreteSubject)subject).getSubjectState();
		System.out.println(id+":"+observerState);
	}

}

当然我们怎么去使用观察者模式呢,这里就要编写一个测试类:

Client.java

package com.xjh.observer;

public class Client {

	public static void main(String[] args) {
		//创建目标
		ConcreteSubject subject = new ConcreteSubject();
		//创建观察者
		ConcreteObserver one = new ConcreteObserver(1);
		ConcreteObserver two = new ConcreteObserver(2);
		//注册观察者
		subject.attach(one);
		subject.attach(two);
		//目标发布天气
		subject.setSubjectState("明天开会");
		//删除观察者
		subject.detach(one);
		//目标发布天气
		subject.setSubjectState("后天开会");
	}

}

运行结果如下,满足了我们的要求。
运行结果

使用Java提供的接口实现

之前为了更好的理解观察者模式,我们已经实现了基本功能,当时Java其实已经提供好了观察者模式的实现。我们先说下使用Java实现与我们自己实现的不同吧。

  • 不需要再定义观察者和目标接口
  • 具体的目标实现里面不需要再维护观察者的注册信息,这个在Java中的Observable类里面实现好了。
  • 触发通知的方式发生改变,需要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能。
  • 具体观察者的实现里面,update方法其实已经能同时支持推模型和拉模型

我们使用Java所提供的接口实现的好处就是更加的规范。
我们实现Observable接口,里面已经实现了添加,删除以及通知的方法。
ConcreteSubject.java

package com.xjh.jdkobserver;

import java.util.Observable;
/**
 * 天气目标具体实现类
 * @author Gin
 *
 */
public class ConcreteSubject extends Observable {
	
	private String context;

	public String getContext() {
		return context;
	}

	public void setContext(String context) {
		this.context = context;
		//通知所有观察者
		this.setChanged();
		this.notifyObservers(context);//推模型
		//this.notifyObservers();//拉模型
	}
	
}

因为Observable实现了推拉模型的结合,所以在重写update方法里面有两个参数,分别代表的就是更新内容,以及Observable对象的引用。
ConcreteObserver.java

package com.xjh.jdkobserver;

import java.util.Observable;
import java.util.Observer;

public class ConcreteObserver implements Observer {

	//观察者ID
	private int id;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	@Override
	public void update(Observable arg0, Object arg1) {
		//推模型,推送过来消息
		System.out.println(id+":"+arg1);
		//拉模型,主动到目标对象中去拉
		//System.out.println(id+":"+((ConcreteSubject)arg0).getContext());
	}

}

然后我们在 Main函数中按照步骤实现就好了。
Client.java

package com.xjh.jdkobserver;

public class Client {

	public static void main(String[] args) {
		//创建目标
		ConcreteSubject subject = new ConcreteSubject();
		//创建观察者
		ConcreteObserver one = new ConcreteObserver(1);
		ConcreteObserver two = new ConcreteObserver(2);
		ConcreteObserver three = new ConcreteObserver(3);
		//注册观察者
		subject.addObserver(one);
		subject.addObserver(two);
		subject.addObserver(three);
		//目标发布天气
		subject.setContext("明天开会");
		//删除观察者
		subject.deleteObserver(one);
		//目标发布天气
		subject.setContext("后天开会");
	}

}

执行结果
我们可以发现他的排序是321,但是我们add的顺序是123,所以可以发现他的插入和读取是类似与栈的形式(我猜的)。

总结

推模型是假定目标对象知道观察者需要的数据,推模型可能会使观察者对象难以复用。
拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值,然而在拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要。

适用场景
  • 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
  • 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变
  • 当一个对象必须通知其他的对象,但是你又希望这个对象和其他的被通知的对象是松散耦合的
优点
  • 观察者模式实现了观察者和目标之间的抽象耦合,解除了实现耦合
  • 观察者模式实现了动态联动
  • 观察者模式支持广播通知
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值