热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

浅谈Android设计模式学习之观察者模式

观察者模式在实际项目中使用的也是非常频繁的,它最常用的地方是GUI系统、订阅——发布系统等。这篇文章主要介绍了浅谈Android设计模式学习之观察者模式,感兴趣的小伙伴们可以参考一下

观察者模式在实际项目中使用的也是非常频繁的,它最常用的地方是GUI系统、订阅——发布系统等。因为这个模式的一个重要作用就是解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。以GUI系统来说,应用的UI具有易变性,尤其是前期随着业务的改变或者产品的需求修改,应用界面也经常性变化,但是业务逻辑基本变化不大,此时,GUI系统需要一套机制来应对这种情况,使得UI层与具体的业务逻辑解耦,观察者模式此时就派上用场了。

概述

观察者模式又被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

模式中的角色

抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

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

具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

1.Subject 和 Observer 是一个一对多的关系,也就是说观察者只要实现 Observer 接口并把自己注册到 Subject 中就能够接收到消息事件;

2.Java API有内置的观察者模式类:java.util.Observable 类和 java.util.Observer 接口,这分别对应着 Subject 和 Observer 的角色;

3.使用 Java API 的观察者模式类,需要注意的是被观察者在调用 notifyObservers() 函数通知观察者之前一定要调用 setChanged() 函数,要不然观察者无法接到通知;

4.使用 Java API 的缺点也很明显,由于 Observable 是一个类,java 只允许单继承的缺点就导致你如果同时想要获取另一个父类的属性时,你只能选择适配器模式或者是内部类的方式,而且由于 setChanged() 函数为 protected 属性,所以你除非继承 Observable 类,否则你根本无法使用该类的属性,这也违背了设计模式的原则:多用组合,少用继承。

观察者模式示例

例如:MyPerson是被观察者

public class MyPerson extends Observable {

  private String name;
  private int age;
  private String sex;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
    setChanged();
    notifyObservers();
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
    setChanged();
    notifyObservers();
  }

  public String getSex() {
    return sex;
  }

  public void setSex(String sex) {
    this.sex = sex;
    setChanged();
    notifyObservers();
  }

  @Override
  public String toString() {
    return "MyPerson [name=" + name + ", age=" + age + ", sex=" + sex + "]";
  }
}

setChanged();告知数据改变,通过notifyObservers();发送信号通知观察者。

MyObserver是观察者

public class MyObserver implements Observer {

  private int id;
  private MyPerson myPerson;

  public MyObserver(int id) {
    System.out.println("我是观察者---->" + id);
    this.id = id;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public MyPerson getMyPerson() {
    return myPerson;
  }

  public void setMyPerson(MyPerson myPerson) {
    this.myPerson = myPerson;
  }

  @Override
  public void update(Observable observable, Object data) {
    System.out.println("观察者---->" + id + "得到更新");
    this.myPerson = (MyPerson) observable;
    System.out.println(((MyPerson) observable).toString());
  }

}

观察者接受到通知后,调用update方法进行更新操作。

public class MainActivity extends Activity {

  private Button btnAddObserver;
  private Button btnChangeData;
  private MyPerson observable;
  private MyObserver myObserver;
  private List myObservers;
  private ListView listview;

  private int i;

  private Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      MyListAdapter myListAdapter = new MyListAdapter(MainActivity.this,
          myObservers);
      listview.setAdapter(myListAdapter);

    };
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    btnAddObserver = (Button) findViewById(R.id.btn_add_observer);
    btnChangeData = (Button) findViewById(R.id.btn_change_data);
    listview = getListView();

    observable = new MyPerson();
    myObservers = new ArrayList();

    btnAddObserver.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        myObserver = new MyObserver(i);
        i++;
        observable.addObserver(myObserver);
        myObservers.add(myObserver);
        handler.sendEmptyMessage(0);
      }
    });

    btnChangeData.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        observable.setName("a" + i);
        observable.setAge(10 + i);
        observable.setSex("男" + i);
        handler.sendEmptyMessage(0);
      }
    });

  }

  @Override
  protected void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    observable.deleteObserver(myObserver);
  }
}

Android源码中的模式实现

在以前,我们最常用到的控件就是ListView了,而ListView最重要的一个点就是Adapter,在我们往ListView添加数据后,我们都会调用一个方法: notifyDataSetChanged(), 这个方法就是用到了我们所说的观察者模式。

跟进这个方法notifyDataSetChanged方法,这个方法定义在BaseAdapter中,代码如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
  // 数据集观察者
  private final DataSetObservable mDataSetObservable = new DataSetObservable();

  // 代码省略

  public void registerDataSetObserver(DataSetObserver observer) {
    mDataSetObservable.registerObserver(observer);
  }

  public void unregisterDataSetObserver(DataSetObserver observer) {
    mDataSetObservable.unregisterObserver(observer);
  }

  /**
   * Notifies the attached observers that the underlying data has been changed
   * and any View reflecting the data set should refresh itself.
   * 当数据集用变化时通知所有观察者
   */
  public void notifyDataSetChanged() {
    mDataSetObservable.notifyChanged();
  }
}

可以发现,当数据发生变化时候,notifyDataSetChanged中会调用mDataSetObservable.notifyChanged()方法

public class DataSetObservable extends Observable {
  /**
   * Invokes onChanged on each observer. Called when the data set being observed has
   * changed, and which when read contains the new state of the data.
   */
  public void notifyChanged() {
    synchronized(mObservers) {
      // 调用所有观察者的onChanged方式
      for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onChanged();
      }
    }
  }

}

mDataSetObservable.notifyChanged()中遍历所有观察者,并且调用它们的onChanged方法。

那么这些观察者是从哪里来的呢?首先ListView通过setAdapter方法来设置Adapter

 @Override
  public void setAdapter(ListAdapter adapter) {
    // 如果已经有了一个adapter,那么先注销该Adapter对应的观察者
    if (mAdapter != null && mDataSetObserver != null) {
      mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    // 代码省略

    super.setAdapter(adapter);

    if (mAdapter != null) {
      mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
      mOldItemCount = mItemCount;
      // 获取数据的数量
      mItemCount = mAdapter.getCount();
      checkFocus();
      // 注意这里 : 创建一个一个数据集观察者
      mDataSetObserver = new AdapterDataSetObserver();
      // 将这个观察者注册到Adapter中,实际上是注册到DataSetObservable中
      mAdapter.registerDataSetObserver(mDataSetObserver);

      // 代码省略
    } else {
      // 代码省略
    }

    requestLayout();
  }

在设置Adapter时会构建一个AdapterDataSetObserver,最后将这个观察者注册到adapter中,这样我们的被观察者、观察者都有了。

AdapterDataSetObserver定义在ListView的父类AbsListView中,代码如下 :

 class AdapterDataSetObserver extends AdapterView.AdapterDataSetObserver {
    @Override
    public void onChanged() {
      super.onChanged();
      if (mFastScroll != null) {
        mFastScroll.onSectionsChanged();
      }
    }

    @Override
    public void onInvalidated() {
      super.onInvalidated();
      if (mFastScroll != null) {
        mFastScroll.onSectionsChanged();
      }
    }
  }

它由继承自AbsListView的父类AdapterView的AdapterDataSetObserver, 代码如下 :

class AdapterDataSetObserver extends DataSetObserver {

    private Parcelable mInstanceState = null;
    // 调用Adapter的notifyDataSetChanged的时候会调用所有观察者的onChanged方法,核心实现就在这里
    @Override
    public void onChanged() {
      mDataChanged = true;
      mOldItemCount = mItemCount;
      // 获取Adapter中数据的数量
      mItemCount = getAdapter().getCount();

      // Detect the case where a cursor that was previously invalidated has
      // been repopulated with new data.
      if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
          && mOldItemCount == 0 && mItemCount > 0) {
        AdapterView.this.onRestoreInstanceState(mInstanceState);
        mInstanceState = null;
      } else {
        rememberSyncState();
      }
      checkFocus();
      // 重新布局ListView、GridView等AdapterView组件
      requestLayout();
    }

    // 代码省略

    public void clearSavedState() {
      mInstanceState = null;
    }
  }

当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者 (AdapterDataSetObserver) 的onChanged方法。这就是一个观察者模式!

总结:AdapterView中有一个内部类AdapterDataSetObserver,在ListView设置Adapter时会构建一个AdapterDataSetObserver,并且注册到Adapter中,这个就是一个观察者。而Adapter中包含一个数据集可观察者DataSetObservable,在数据数量发生变更时开发者手动调用AdapternotifyDataSetChanged,而notifyDataSetChanged实际上会调用DataSetObservable的notifyChanged函数,该函数会遍历所有观察者的onChanged函数。在AdapterDataSetObserver的onChanged函数中会获取Adapter中数据集的新数量,然后调用ListView的requestLayout()方法重新进行布局,更新用户界面。

比较知名的使用观察者模式的开源框架有

  1. EventBus
  2. AndroidEventBus
  3. otto

模式总结

优点

观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

缺点

依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。

适用场景

当一个对象的改变需要给变其它对象时,而且它不知道具体有多少个对象有待改变时。

一个抽象某型有两个方面,当其中一个方面依赖于另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 2023 ARM嵌入式系统全国技术巡讲旨在分享ARM公司在半导体知识产权(IP)领域的最新进展。作为全球领先的IP提供商,ARM在嵌入式处理器市场占据主导地位,其产品广泛应用于90%以上的嵌入式设备中。此次巡讲将邀请来自ARM、飞思卡尔以及华清远见教育集团的行业专家,共同探讨当前嵌入式系统的前沿技术和应用。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 本文总结了在使用Ionic 5进行Android平台APK打包时遇到的问题,特别是针对QRScanner插件的改造。通过详细分析和提供具体的解决方法,帮助开发者顺利打包并优化应用性能。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
author-avatar
叶蕊2502860197
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有