文章目录
- 思考观察者模式
- 1.观察者模式的本质
- 2.何时选用观察者模式
- 3.优缺点
- 4.实现
- 手写观察者模式
- JDK观察者模式
思考观察者模式
观察者模式是典型的发布订阅模式,当一个东西有变化了,就通知所有订阅他的人
1.观察者模式的本质
观察者模式的本质:触发联动。
当修改目标对象的状态的时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法。
而且这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态地控制观察者,来变相地实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。
同时目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动过来。
理解这个本质对我们非常有用,对于我们识别和使用观察者模式有非常重要的意义,尤其是在变形使用的时候。万变不离其宗。
2.何时选用观察者模式
建议在以下情况中选用观察者模式。
-
当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得它们可以独立地改变和复用。
-
如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的那一个对象很明显就相当于是目标对象,而需要连带修改的多个其他对象,就作为多个观察者对象了。
-
当一个对象必须通知其他的对象,但是你又希望这个对象和其他被它通知的对象是松散耦合的。也就是说这个对象其实不想知道具体被通知的对象。这种情况可以选用观察者模式,这个对象就相当于是目标对象,而被它通知的对象就是观察者对象了。
3.优缺点
观察者模式具有以下优点。
-
观察者模式实现了观察者和目标之间的抽象耦合
原本目标对象在状态发生改变的时候,需要直接调用所有的观察者对象,但是抽象出观察者接口以后,目标和观察者就只是在抽象层面上耦合了,也就是说目标只是知道观察者接口,并不知道具体的观察者的类,从而实现目标类和具体的观察者类之间解耦。 -
观察者模式实现了动态联动
所谓联动,就是做一个操作会引起其他相关的操作。由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。 -
观察者模式支持广播通信
由于目标发送通知给观察者是面向所有注册的观察者,所以每次目标通知的信息就要对所有注册的观察者进行广播。当然,也可以通过在目标上添加新的功能来限制广播的范围。
在广播通信的时候要注意一个问题,就是相互广播造成死循环的问题。比如A和B两个对象互为观察者和目标对象,A对象发生状态变化,然后A来广播信息,B对象接收到通知后,在处理过程中,使得B对象的状态也发生了改变,然后B来广播信息,然后A对象接到通知后,又触发广播信息……,如此A引起B变化,B又引起A变化,从而一直相互广播信息,就造成死循环。
观察者模式的缺点是:
- 可能会引起无谓的操作
由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法,如果观察者不需要执行相应处理,那么这次操作就浪费了。其实浪费了还好,最怕引起误更新,那就麻烦了,比如,本应该在执行这次状态更新前把某个观察者删除掉,这样通知的时候就没有这个观察者了,但是现在忘掉了,那么就会引起误操作。
4.实现
模拟:手机降价通知用户
手写观察者模式
1.目标类
/**
* @description:通用目标接口,也可为抽象类
*/
public class Subject {
/**
* 维护一个观察者列表
*/
private List<Observer> list=new ArrayList<>();
/**
* 注册观察者
* @param observer
*/
public void attach(Observer observer){
list.add(observer);
}
/**
* 移除观察者
* @param observer
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知观察者
*/
public void notifyObservers(){
list.stream().forEach(obj->obj.update(this));
}
}
/**
* @description:手机目标对象
*/
@Getter
@ToString
public class PhoneSubject extends Subject{
/**
* 手机价格
*/
private double price;
public void setPrice(double price) {
this.price = price;
//价格小于150为降价
if (price<150){
//通知用户降价
notifyObservers();
}
}
}
2.观察者类
/**
* @description:观察者接口
*/
public interface Observer {
/**
* 被通知的方法
* @param subject 目标对象
*/
void update(Subject subject);
}
/**
* @description:用户(观察者)
*/
@Data
public class UserObserver implements Observer{
/**
* 用户名
*/
private String name;
@Override
public void update(Subject subject) {
System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject)subject).getPrice());
}
}
3.测试类
/**
* @description:测试类
*/
public class Client {
public static void main(String[] args) {
UserObserver userObserver1 = new UserObserver();
userObserver1.setName("张三");
UserObserver userObserver2 = new UserObserver();
userObserver2.setName("李四");
PhoneSubject subject=new PhoneSubject();
//初始价格为150
subject.setPrice(150);
//绑定观察者
subject.attach(userObserver1);
subject.attach(userObserver2);
//涨价不通知
subject.setPrice(250);
//降价才通知用户,第一次降价
subject.setPrice(100);
//降价才通知用户,第二次降价
subject.setPrice(88);
}
}
4.效果
JDK观察者模式
1.目标类继承Observable接口即可
/**
* @description:手机目标对象
*/
@Getter
@ToString
public class PhoneSubject2 extends Observable {
/**
* 手机价格
*/
private double price;
public void setPrice(double price) {
this.price = price;
//价格小于150为降价
if (price<150){
//通知用户降价
notifyObservers();
}
}
}
里面维护了观察者对象,以及注册、移除观察者,通知观察者等
2.观察者类实现Observer接口即可
/**
* @description:用户1
*/
@Data
public class UserObserver2 implements Observer {
/**
* 用户名
*/
private String name;
@Override
public void update(Observable o, Object arg) {
if (o instanceof PhoneSubject2){
System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject2)o).getPrice());
}
}
}
和手写的一模一样
3.测试类
/**
* @author conggq
* @description:测试类
* @createTime 2022/11/29 17:27
*/
public class Client {
public static void main(String[] args) {
UserObserver userObserver1 = new UserObserver();
userObserver1.setName("张三");
UserObserver userObserver2 = new UserObserver();
userObserver2.setName("李四");
PhoneSubject2 subject2=new PhoneSubject2();
//初始价格为150
subject2.setPrice(150);
//涨价不通知
subject.setPrice(250);
//降价才通知用户,第一次降价
subject.setPrice(100);
//降价才通知用户,第二次降价
subject.setPrice(88);
}
}
结果一样