「设计模式(二) - 观察者模式」

62 篇文章 1 订阅
19 篇文章 1 订阅
一、回复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的具体实现,好处不用多说,这也是为什么我们在学习设计模式之初首先需要理解六种基本的设计原则。抽象不依赖实现细节,细节应该依赖抽象,抽象约束了细节使细节更规范可控

  • 结构图:

图片来自网络.png

四、代码实现
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);
  }
} 

严格意义上这并不是标准的观察者模式,但是观察者模式的本质-一对多,主题状态改变时,依赖者会即时感知并作出反应,通俗的讲就是出发联动效果。

还是回到设计的本质,设计的初衷降低耦合、降低复杂度、提高扩展性,设计没有定式,如果完全照搬不仅会适得其反,其次也可能使项目变得四不像。为了设计而设计并不可取,重要的还是细想的理解。实际业务与功能千变万化,对业务的抽象能力,思考才是最重要,设计模式仅仅是把思路转化为实现细节的手段。

六、该何时使用
  • 抽象模型之间存在一对多这种关系时,并且主体的改变会引起其他依赖者的改变时。
  • 需要动态联动的关系,且构建尽可能低的耦合度的系统。

是标准的观察者模式,但是观察者模式的本质-一对多,主题状态改变时,依赖者会即时感知并作出反应,通俗的讲就是出发联动效果。

还是回到设计的本质,设计的初衷降低耦合、降低复杂度、提高扩展性,设计没有定式,如果完全照搬不仅会适得其反,其次也可能使项目变得四不像。为了设计而设计并不可取,重要的还是细想的理解。实际业务与功能千变万化,对业务的抽象能力,思考才是最重要,设计模式仅仅是把思路转化为实现细节的手段。

六、该何时使用
  • 抽象模型之间存在一对多这种关系时,并且主体的改变会引起其他依赖者的改变时。
  • 需要动态联动的关系,且构建尽可能低的耦合度的系统。

最后

有小伙伴私信问Compose的问题,好不好用啊,现在要不要学啊?

其实答案很简单,自从谷歌2019年公布了声明式UI框架Jetpack Compose后,两年多的时间,各种大力宣传,和大量资源的倾斜,API功能都趋于稳定了。

至于好不好用,各种用过的同行都是持肯定态度的。优势大概就是这四点:

强大的工具和直观的Kotlin API
简化并加速了Android上的UI开发
可以帮助开发者用更少更直观的代码创建View
有更强大的功能,以及还能提高开发速度

这么大的优势,毋庸置疑,肯定是要学的嘛,而且越快掌握越好。别等刀架到脖子上了,才去练金钟罩。

至于怎么快速上手,可以给大家免费分享一份**《Jetpack Compose 完全开发手册》**,手把手教大家从入门到精通。

第一章 初识 Jetpack Compose

  • 为什么我们需要一个新的UI 工具?

  • Jetpack Compose的着重点

    加速开发
    强大的UI工具
    直观的Kotlin API

图片

  • API 设计

图片

  • Compose API 的原则
    一切都是函数
    顶层函数(Top-level function)
    组合优于继承
    信任单一来源

图片

  • 深入了解Compose
    Core
    Foundation
    Material

图片

  • 插槽API

第二章 Jetpack Compose构建Android UI

  • Android Jetpack Compose 最全上手指南
    Jetpack Compose 环境准备和Hello World
    布局
    使用Material design 设计
    Compose 布局实时预览
    ……

图片

  • 深入详解 Jetpack Compose | 优化 UI 构建
    Compose 所解决的问题
    Composable 函数剖析
    声明式 UI
    组合 vs 继承
    封装
    重组
    ……

图片

  • 深入详解 Jetpack Compose | 实现原理
    @Composable 注解意味着什么?
    执行模式
    Positional Memoization (位置记忆化)
    存储参数
    重组
    ……

图片

第三章 Jetpack Compose 项目实战演练(附Demo)

  • Jetpack Compose应用1
    开始前的准备
    创建DEMO
    遇到的问题

图片

  • Jetpack Compose应用2
  • Jetpack Compose应用做一个倒计时器
    数据结构
    倒计时功能
    状态模式
    Compose 布局
    绘制时钟

图片

  • 用Jetpack Compose写一个玩安卓App
    准备工作
    引入依赖
    新建 Activity
    创建 Compose
    PlayTheme
    画页面
    底部导航栏
    管理状态
    添加页面

图片

  • 用Compose Android 写一个天气应用
    开篇
    画页面
    画背景
    画内容
    ……

图片

  • 用Compose快速打造一个“电影App”
    成品
    实现方案
    实战
    不足
    ……

图片

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
有需要的话可以点下面二维码免费领取↓↓↓

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值