观察者模式(Observer Pattern)
别名: 依赖,发布/订阅(Another Name: Dependents, Publish/Subscribe)
概念
定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
如何使用?
当一个对象的数据更新时,需要通知其他对象,而又不希望和被通知的对象形成紧耦合时
示例
比如我们有个天气服务(主题),然后有多个使用它的客户端(观察者),包括android和iphone端app的服务(观察者),那么就可以使用这么模式。
我们需要一种结构存放天气信息(注意,省略了get、set方法!):
//天气的消息实体
public class WeatherInfo {
private long time;
private String weather;
public WeatherInfo(long time,String weather){
this.time = time;
this.weather = weather;
}
@Override
public boolean equals(Object obj) {
WeatherInfo info = (WeatherInfo) obj;
return info.time==this.time&&info.weather.equals(this.weather);
}
}
然后我们定义天气服务的接口(主题),以表示它应实现哪些功能:
//主题
public interface IWeatherService {
void addClient(Client client); //添加观察者
boolean deleteClient(Client client);//删除观察者
void notifyClients(); //通知
void updateWeather(WeatherInfo info);//主题内容更新
}
接着就是客户端的接口描述:
//观察者
public interface Client {
void getWeather(WeatherInfo info);
}
然后实现具体的天气服务,这里同样用到了单例模式:
//具体主题
public enum WeatherService implements IWeatherService{
instance;
private LinkedList<WeatherInfo> weatherInfos = new LinkedList<WeatherInfo>();
private LinkedHashSet<Client> clients = new LinkedHashSet<Client>(); //存放观察者
//添加观察者
@Override
public void addClient(Client client) {
clients.add(client);
}
//删除观察者
@Override
public boolean deleteClient(Client client) {
return clients.remove(client);
}
//通知观察者
@Override
public void notifyClients() {
Iterator<Client> iterator = clients.iterator();
while(iterator.hasNext()){
iterator.next().getWeather(weatherInfos.peekFirst());
}
}
//更新天气
@Override
public void updateWeather(WeatherInfo info) {
if(weatherInfos.size()>0)
if(weatherInfos.peekFirst().equals(info)) return;
weatherInfos.push(info);
if(clients.size()==0) return;
notifyClients();
}
}
最后就是具体的客户端(观察者,此处给出两个):
public class ClientAndroidServer implements Client {
private static String name = "安卓服务";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(){
System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
}
}
public class ClientIphoneServer implements Client {
private static String name = "苹果服务";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(){
System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
}
}
好,现在就可以直接使用了:
public class TestUse {
public static void main(String args[]){
//创建主题
WeatherService service = WeatherService.instance;
//添加观察者
service.addClient(new ClientAndroidServer());
service.addClient(new ClientIphoneServer());
//更新主题
service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "多云"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+10006060*24, "多云转晴"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+10006060242, "晴"));
}
}
运行后,控制台有如下输出:
安卓服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息…
苹果服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息…
安卓服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息…
苹果服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息…
安卓服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息…
苹果服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息…
可以看出,观察者模式是一对多的。而本例是将更新的内容整个推给客户端。
而观察者模式中的数据有推和拉的区别,上例是推。
推的方式会将主题更改的内容全部直接推给客户端,拉的方式就是主题的数据更新后,不直接将数据推给客户端,而是先推送一个通知并提供对应的方法供客户端拉取数据。
如果上例中,天气服务每半小时更新(半点和整点推消息),还有一个客户端,不需要特别即时的天气消息,只取整点的消息,那么我们就可以使用拉的方式,数据更新后,给客户端推送一个标志,客户端自己按需取得数据(天气服务需要提供这样一个接口)。这就是拉。
java.util包中也提供了观察者模式的支持,因为java程序设计中使用比较广泛。有一个Observable类(相当于这里的具体主题)和一个Observer接口(相当于这里的主题接口):
public interface Observer {
void update(Observable o, Object arg);
}
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() { obs = new Vector<>();}
public synchronized void addObserver(Observer o) {
if (o == null) throw new NullPointerException();
if (!obs.contains(o)) { obs.addElement(o); }
}
public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }
public void notifyObservers() { notifyObservers(null); }
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() { obs.removeAllElements(); }
protected synchronized void setChanged() { changed = true; }
protected synchronized void clearChanged() { changed = false; }
public synchronized boolean hasChanged() { return changed; }
public synchronized int countObservers() { return obs.size(); }
}
其实跟上面的例子大体差不多,如果有这方面的需求,也可以直接使用Java的API。 但可以看到里面还在使用Vector(已过时),这其实是不推荐的,我们可以自己实现观察者模式,如果是多线程中,我们也可以自己实现同步。
23种设计模式:https://blog.csdn.net/anxpp/article/details/51224293