「设计模式(二) - 观察者模式」
本文已参与「新人创作礼」活动,一起开启掘金创作之路
一、回复TD退订
日常生活中,这种短信无处不在,各种广告,在互联网高速发展的今天,个人信息可以说是透明的。没有任何隐私可言,类似这种通知其实跟我们开发过程使用的观察者模式(Observer Pattern)如出一辙。更贴切的像初中时代,英语学习的周报订阅,一个班级大部分还是会订阅,基本上一周一期。这个算是比较典型的观察者模式也即-发布-订阅。可以这样理解,Publishers+Subscribers=Obeserver Pattern。像这种一对多的关系,一个对象状态的改变,所有订阅它的对象都会被通知到并进行自己的一些操作可以用观察者模式来解释。
二、观察者模式 Obeserver Pattern
对象之间存在像这种一对多的依赖关系,当被订阅的对象(Publishers)状态发生变化时,订阅者们(Subscribers)会收到相应的通知并作出相应的操作(更新自身的状态或行为操作),即为观察者模式,是一种对象行为型模式。
三、组成部分
以上述为例:
-
需要抽象的主题(Subject),
英语周报就是这个主题,直观思考就可以理解,需要存储所有订阅了的学生(Observers),以便确保每个订阅的学生都能收到。需要可以删除不在订阅的学生(Observer),那么同样的也可以新增想要订阅的学生(Observer)。发布者Subject 需要持有所有的订阅者,并提供新增、删除订阅的方法,通知订阅者们自身状态改变的抽象方法(Notify)
-
需要抽象的订阅者(Observser),
学生在这里可以作为充当订阅者的角色,可以抽象为Observer,更加通用一点,老师同样可以订阅,其他有需要的人一样可以订阅。Observer即可理解为角色的抽象。订阅者Observer,抽象的接口或抽象类(一般常见的为接口),提供更新自己行为、或属性的抽象方法
-
需要具体订阅者实现类(Concrete Observer),前面提到了,
学生仅仅是众多订阅类型的一种,任何有需要的人都可以订阅,仅需实现Observer接口即可,设计的易扩展性。 -
同样的具体主题实现者(Concrete Subject),是对
Subject的具体实现,好处不用多说,这也是为什么我们在学习设计模式之初首先需要理解六种基本的设计原则。抽象不依赖实现细节,细节应该依赖抽象,抽象约束了细节使细节更规范可控。 -
结构图:
四、代码实现
1.设计一个价格变动系统
水果店里的水果众多,应季水果通常很贵,像现在这个季节的车厘子(还没实现车厘子自由🍒),不易保存的水果,草莓等等,价格统一管理并下发到各个门店。
- 主题
Subject,也即是发布者
/**
* Created by Sai
* on: 10/01/2022 11:41.
* Description:
*/
public abstract class Subject {
private final Logger logger = Logger.getLogger(Subject.class.getName());
/** 保存订阅者对象集合 */
private final List<Observer> observerList = new CopyOnWriteArrayList<>();
/** 新增订阅者Observer */
public boolean attach(Observer observer) {
logger.info(observer + "添加成功");
return observerList.add(observer);
}
/** 解除已经绑定的订阅者的关系 */
public boolean detach(Observer observer) {
logger.info(observer + "解绑成功");
return observerList.remove(observer);
}
/** 通知更新到Observers */
protected void notifyObservers() {
if (observerList.isEmpty()) {
return;
}
for (Observer observer : observerList) {
observer.priceChanged(this);
}
}
}
- 具体的实现类-如车厘子
/**
* Created by Sai
* on: 10/01/2022 12:16.
* Description:
*/
public class Cherry extends Subject {
/** 价格 */
private long price;
/** 名称 */
private String name;
public Cherry(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
/** 当价格发生改变通知到所有门店 */
this.price = price;
notifyObservers();
}
@Override
public String toString() {
return "Cherry{" + "price=" + price + ", name='" + name + '\'' + '}';
}
}
- 定义观察者
Observer接口
/**
* Created by Sai
* on: 10/01/2022 11:34.
* Description:
*/
public interface Observer {
//通知的是主题的一些信息
void priceChanged(Subject subject);
}
- 具体实现类-门店Store
/**
* Created by Sai
* on: 10/01/2022 12:22.
* Description:
*/
public class Store implements Observer {
private String storeName;
public Store(String storeName) {
this.storeName = storeName;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
private void show(Subject subject) {
System.out.println(this.storeName + subject.toString());
}
@Override
public void priceChanged(Subject subject) {
System.out.println(this.storeName);
System.out.println("Price Changed - Refreshing price");
show(subject);
System.out.println("---------------------------------------->");
}
}
- 测试用例
/**
* Created by Sai
* on: 10/01/2022 12:27.
* Description:
*/
public class Demo {
public static void main(String[] args) {
Cherry cherry = new Cherry("车厘子");
Store one = new Store("门店一");
Store two = new Store("门店二");
cherry.attach(one); cherry.attach(two);
cherry.setPrice(100);
//价格涨了
cherry.setPrice(200);
//门店一倒闭了
cherry.detach(one);
cherry.setPrice(99);
}
}
- 打印信息
信息: com.observer.sai.Store@2f92e0f4添加成功
1月 10, 2022 1:01:22 下午 com.observer.sai.Subject attach
信息: com.observer.sai.Store@4f3f5b24添加成功
1月 10, 2022 1:01:22 下午 com.observer.sai.Subject detach
信息: com.observer.sai.Store@2f92e0f4解绑成功
门店一
Price Changed - Refreshing price
门店一Cherry{price=100, name='车厘子'}
---------------------------------------->
门店二
Price Changed - Refreshing price
门店二Cherry{price=100, name='车厘子'}
---------------------------------------->
门店一
Price Changed - Refreshing price
门店一Cherry{price=200, name='车厘子'}
---------------------------------------->
门店二
Price Changed - Refreshing price
门店二Cherry{price=200, name='车厘子'}
---------------------------------------->
门店二
Price Changed - Refreshing price
门店二Cherry{price=99, name='车厘子'}
---------------------------------------->
Process finished with exit code 0
2.一些思考
门店管理系统,实现了水果价格变动时动态的下发信息到各个门店下面,这个动态的联动关系;只要主题对象的状态或者行为发生改变。那么订阅者们感知到这个变化相应的作出自身的行为。虽然有一定的耦合,但也是抽象层面的,耦合程度还是比较低的,扩展性得到了保证。设计的初衷也仅仅就为了解决这几个问题:降低耦合,提高内聚、扩展性、层次结构性。
- 降低了发布者(
Publishers)与订阅者(Subscribers)直接的耦合程度,抽象间耦合。 - 实现了发布者与订阅者之间的动态联动,订阅者对发布者行为的改变作出自身的改变。
当然缺点也是很明显的,从例子中就可以看出,随着门店的扩张,系统的调用深度也会增加。其次门店的闭店来不及解绑的情况下,也导致了一些无谓的通知,增加系统的负担。
- 即时订阅与即时解绑,前者会导致通知不到位,错过重要的信息;而后者则会造成无谓的通知。
- 如果存在互相依赖的情况下,那么会出现
死循环的情况,当然这种场景下,首先应该考虑的到的是观察者模式是否真的适合在此时使用?
五、实际问题
在题主目前工作内容中涉及到使用Observer Pattern的场景还是比较多的,其中购物车模块业务,当商品被加到购物车中:总价格发生变化、商品数目红点数量信息改变、商品库存、单个商品被选购的次数等都发生了变动。反之删除商品同样。
public interface OnCartObserver {
...
void onDeleteProduct(int position, int needUpdateCount);
void onUpdateProduct(int position, CartProductVO product);
void onCartClear();
//备注的更新
void onRemarkUpdate(String[] remarks);
...
}
public abstract class BaseOnCartObserver implements OnCartObserver {
...
@Override
public void onDeleteProduct(int position, int needUpdateCount) {
}
@Override
public void onCartClear() {
}
@Override
public void onRemarkUpdate(String[] remarks) {
}
@Override
public void onUpdateProduct(int position, CartProductVO product) {
}
...
}
//调用采用了匿名内部类的形式,并没有严格的遵循观察者模式UML实现
public class CartGoodsFragment extends BaseFragment {
private final OnCartObserver onCartObserver = new BaseOnCartObserver() {
//行为的更新
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
XX.provideController.addObserver(onCartObserver);
}
@Override
public void onDestroy() {
super.onDestroy();
XX.provideController.removeObserver(onCartObserver);
}
}
严格意义上这并不是标准的观察者模式,但是观察者模式的本质-一对多,主题状态改变时,依赖者会即时感知并作出反应,通俗的讲就是出发联动效果。
还是回到设计的本质,设计的初衷降低耦合、降低复杂度、提高扩展性,设计没有定式,如果完全照搬不仅会适得其反,其次也可能使项目变得四不像。为了设计而设计并不可取,重要的还是细想的理解。实际业务与功能千变万化,对业务的抽象能力,思考才是最重要,设计模式仅仅是把思路转化为实现细节的手段。
六、该何时使用
- 抽象模型之间存在
一对多这种关系时,并且主体的改变会引起其他依赖者的改变时。 - 需要
动态联动的关系,且构建尽可能低的耦合度的系统。