java 行为型模式 观察者模式 监听目标对象的状态改变

本文介绍了Java中的观察者模式,这是一种一对多的依赖关系,当主题对象状态变化时,会通知所有观察者进行更新。文章讨论了如何使对象成为观察者,即通过继承Observer接口并调用Observable对象的addObserver方法。同时,解释了被观察者如何通知观察者,以及观察者如何接收这些通知。示例中展示了被观察者和观察者的具体实现,并提到了两种通知方式:推送和拉取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,让他们能够更新状态。

适用场景:
(1)当一个抽象模型,其中一个方面依赖于另一个方面,把这两者封装在独立的对象中,使其可以独立的改变和复用。
(2)改变一个对象需要同时改变其他对象,但不知道有多少对象待改变。
(3)一个对象必须通知其他对象,但又不能假定这些对象,比如数据采集模块,当采集到数据以后,必须通知其他对象,但在开发采集模块的时候,事先并不知道其他对象是什么。
 
观察者模式的组成:
抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类或者接口来实现。

抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。

具体的主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知,具体主题角色通常用一个子类实现。

具体观察者角色:该角色实现抽象观察者所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要具体观察者角色可以保存一个指向具体主题角色的 引用。通常用一个子类实现。

UML类图:

                              

                              

应用
 
 
/**
 * 抽象观察者
 * @author lm
 *
 */
public interface Observer {

	public void Upate(Object obj);
}

 

package com.observers;

/**
 * 具体观察者角色
 * @author lm
 *
 */
public class ConcreateObserver implements Observer{

	@Override
	public void Upate(Object obj) {
		// TODO Auto-generated method stub
		System.out.println("Recevied: "+ obj );
	}

}
 
package com.observers;
/**
 * 抽象主题角色
 * 1.增加观察者方法
 * 2.删除观察者方法
 * 3.通知
 * @author lm
 *
 */
public interface Subject {

	void Attach(Observer observer);
	
	void Delete(Observer observer);
	
	void NotifyObservers(Object object);
}
 
package com.observers;

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

/**
 * 具体主题
 * @author lm
 *
 */
public class ConcreteSubject implements Subject{

	
	private List<Observer> list = new ArrayList<Observer>();
	
	
	@Override
	public void Attach(Observer observer) {
		// TODO Auto-generated method stub
		list.add(observer);
	}

	@Override
	public void Delete(Observer observer) {
		// TODO Auto-generated method stub
		list.remove(observer);
	}

	@Override
	public void NotifyObservers(Object object) {
		// TODO Auto-generated method stub
		for (Observer observer : list) {
			observer.Upate(object);
		}
	}
	
	/**
	 * 供客户端调用
	 */
	public void Invoke(){
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
			this.NotifyObservers(i);
		}
	}

}
 
测试程序:
package com.observers;

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		ConcreteSubject subject =new ConcreteSubject();
		
		
		Observer observer1 = new ConcreateObserver();
		Observer observer2 = new ConcreateObserver();
		Observer observer3 = new ConcreateObserver();
		
		
		subject.Attach(observer1);
		subject.Attach(observer2);
		subject.Attach(observer3);
		
		
		subject.Delete(observer3);
		
		subject.Invoke();
		
	}

}
 
调用结果:
0
Recevied: 0
Recevied: 0
1
Recevied: 1
Recevied: 1
2
Recevied: 2
Recevied: 2
3
Recevied: 3
Recevied: 3
4
Recevied: 4
Recevied: 4
5
Recevied: 5
Recevied: 5
6
Recevied: 6
Recevied: 6
7
Recevied: 7
Recevied: 7
8
Recevied: 8
Recevied: 8
9
Recevied: 9
Recevied: 9
另外一个程序:
 
抽象主题角色:
//主题接口
 interface Subject {
     //添加观察者
     void addObserver(Observer obj);
     //移除观察者
     void deleteObserver(Observer obj);
     //当主题方法改变时,这个方法被调用,通知所有的观察者
     void notifyObserver();
 
观察者:
 
interface Observer {
    //当主题状态改变时,会将一个String类型字符传入该方法的参数,每个观察者都需要实现该方法
    public void update(String info);
}
 
 
具体主题角色 被观察者:
 
public class TeacherSubject implements Subject {
    //用来存放和记录观察者
    private List<Observer> observers=new ArrayList<Observer>();
    //记录状态的字符串
    private String info;
    
    @Override
    public void addObserver(Observer obj) {
        observers.add(obj);
    }

    @Override
    public void deleteObserver(Observer obj) {
        int i = observers.indexOf(obj);
        if(i>=0){
            observers.remove(obj);
        }
    }

    @Override
    public void notifyObserver() {
        for(int i=0;i<observers.size();i++){
            Observer o=(Observer)observers.get(i);
            o.update(info);
        }
    }
    //布置作业的方法,在方法最后,需要调用notifyObserver()方法,通知所有观察者更新状态
    public void setHomework(String info){
        this.info=info;
        System.out.println("今天的作业是"+info);
        this.notifyObserver();
    }

}

具体观察类:
public class StudentObserver implements Observer {

    //保存一个Subject的引用,以后如果可以想取消订阅,有了这个引用会比较方便
    private TeacherSubject t;
    //学生的姓名,用来标识不同的学生对象
    private String name;
    //构造器用来注册观察者
    public Student(String name,Teacher t) {
        this.name=name;
        this.t = t;
        //每新建一个学生对象,默认添加到观察者的行列
        t.addObserver(this);
    }


    @Override
    public void update(String info) {
        System.out.println(name+"得到作业:"+info);
        
    }

}
 测试程序:
 
public class TestObserver {

    public static void main(String[] args) {
        
        TeacherSubject teacher=new TeacherSubject();
        StudentObserver zhangSan=new StudentObserver("张三", teacher);
        StudentObserver LiSi=new StudentObserver("李四", teacher);
        StudentObserver WangWu=new StudentObserver("王五", teacher);
        
        teacher.setHomework("第二页第六题");
        teacher.setHomework("第三页第七题");
        teacher.setHomework("第五页第八题");
    }
}

打印结果:
今天的作业是第二页第六题
张三得到作业:第二页第六题
李四得到作业:第二页第六题
王五得到作业:第二页第六题
今天的作业是第三页第七题
张三得到作业:第三页第七题
李四得到作业:第三页第七题
王五得到作业:第三页第七题
今天的作业是第五页第八题
张三得到作业:第五页第八题
李四得到作业:第五页第八题
王五得到作业:第五页第八题
 

以上都是推送模式

 
java内置的观察者模式:
在java.util包中包含有基本的Observer接口和Observable抽象类.功能上和Subject接口和Observer接口类似.不过在使用上,就方便多了,因为许多功能比如说注册,删除,通知观察者的那些功能已经内置好了.

如何使对象变为观察者

继承观察者接口(java.util.Observer),然后调用Observable对象的addObserver()方法.不想再当观察者时,调用deleteObserver()就可以了.

被观察者(主题)如何发出通知

第一步:先调用setChanged()方法,标识状态已经改变的事实.
 
第二步:调用notifyObservers()方法或者notifyObservers(Object arg),这就牵扯到推(push)和拉(pull)的方式传送数据.如果想用push的方式"推"数据给观察者,可以把数据当做数据对象传送给notifyObservers(Object arg)方法,其中的arg可以为任意对象,意思是你可以将任意对象传送给每一个观察者.如果调用不带参数的notifyObserver()方法,则意味着你要使用pull的方式去主题对象中"拉"来所需要的数据.

观察者如何接收通知

观察者只需要实现一个update(Observable o,Object arg)方法,第一个参数o,是指定通知是由哪个主题下达的,第二个参数arg就是上面notifyObserver(Object arg)里传入的数据,如果不传该值,arg为null.

 

被观察者TeacherSubject:

import java.util.Observable;

public class Teacher extends Observable {
        //布置作业的状态信息字符串
    private String info;
    public void setHomework(String info) {

        this.info=info;
        System.out.println("布置的作业是"+info);

        setChanged();
        notifyObservers();
    }
    public String getInfo() {
        return info;
    }
}

观察者StudentObserver:

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

public class Student implements Observer{

    private Observable ob;
    private String name;

    public Student(String name,Observable ob) {
        this.ob = ob;
        this.name=name;
        ob.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        Teacher t=(Teacher)o;
        System.out.println(name+"得到作业信息:"+t.getInfo());
        
    }

  测试代码和打印结果我就不再写了,和上面的例子是一样一样的,在这个例子中我使用的是"pull"的方式拉数据,在需要传递状态的TeacherSubject中定义了一个info字符串的get方法,在观察者对象中调用get方法得到所需数据,如果希望使用push的方式,只需要在TeacherSubject类的notifyOservers()方法中传入String类型的info字符串即可在update()方法中直接通过第二个参数获取到arg,即使前面传过来的info字符串.

 

 

我推你拉

 

 

观察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本。
一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中“拉”出来的。
还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要,先给你啦。
这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目标角色比较简单,则“拉模式”就很合适啦。
 

我觉得另一篇写的也不错 http://ifeve.com/observer-design-pattern-in-java-example-tutorial/ 可以看看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值