Windows环境下实现设计模式——观察者模式(JAVA版)

news2024/11/26 20:32:22

我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现观察者模式(设计模式)。

不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。

我今天就以JAVA编程语言来实现一个观察者模式,真实的实现一个,你看懂代码后,自然就明白了。

观察者模式Observer Pattern(行为型设计模式)

定义:对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新。

上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。

比如说一场足球比赛,两队正踢的非常焦灼,互有攻守。这时其中的A队的队长给全体队员做了一个暗示,所以前峰和前卫队员心领神会,趁对方队员向前冲的时候,突然让中场队员发起了一次突然进攻。很快他们就利用这次机会进球了。

在软件系统中对象并不是孤立存在的,一个对象行为的改变可能会导致一个或多个其他与之存在依赖关系的对象行为发生改变,就像一支足球队的队长和队员一样。观察者模式用于描述对象之间的依赖关系,为实现多个对象之间的联动提供了一种解决方案,它是一种使用频率非常高的设计模式。随着球队队长的指挥的变化,队员的行为也将随之变化,队长一人可以指挥多名队员。

在软件系统中有些对象之间也存在类似队长和队员之间的关系,一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式就逐渐形成了,它定义了对象之间的一种一对多的依赖关系,让一个对象的改变能够影响其他对象。一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

这里有一个有趣的现象,很多程序员从事十年了也没有听说过观察者模式,这是为什么?其实是因为大家叫法不同,观察者模式还有其他一些叫法,比如发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式。观察者模式是一种对象行为型模式。我相信发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式这两种大多数JSP、PHP、ASP.NET程序员都知道,因为很多MVC开发其实就是在运用这种模式。

观察者模式结构中通常包括观察目标和观察者两个继承层次结构,其结构如下。

观察者模式包含以下4个角色。

(1)Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。

(2)ConcreteSubject(具体目标):具体目标是目标类的子类,它通常包含有经常发生改变的数据,当它的状态发生改变时将向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有)。如果无须扩展目标类,则具体目标类可以省略。

(3)Observer(观察者):观察者将对观察目标的改变作出反应,观察者一般定义为接口,该接口声明了更新数据的方法update().因此又称为抽象观察者。

(4)ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的 update()方法。通常在实现时可以调用具体目标类的attach(O方法将自己添加到目标类的集合中或通过 detach()方法将自己从目标类的集合中删除。

观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态,以使其状态与目标状态同步。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。下面通过JAVA代码对观察者模式进行进一步分析。

JAVA代码实现

import java.util.*;

public abstract class Subject {  
   //定义一个观察者集合用于存储所有观察者对象
  protected ArrayList observers<Observer>= new ArrayList();
  
   //注册方法,用于向观察者集合中增加一个观察者
   public void attach(Observer observer){
     observers.add(observer);
  }

   //注销方法,用于从观察者集合中删除一个观察者
   public void detach(Observer observer){
     observers.remove(observer);

  //声明抽象通知方法
  public abstract void notify();
}

public class ConcreteSubject extends subject
  
   //实现通知方法
  public void notify(){  
   //遍历观察者集合,调用每一个观察者的响应方法
   for(Object obs: observers){
     ((Observer)obs). update();
      }
   }
}
  
public interface Observer {
  //声明响应方法
  public void update();
}

public class ConcreteObserver implements Observer{
   //实现响应方法
  public void update(){
  /具体响应代码
   }
}

在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类 ConcreteSubject中的状态(属性),因此在 ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject 实例,通过该实例获取存储在 ConcreteSubject中的状态。如果ConcreteObserver的 update()方法不需要使用到ConcreteSubject中的状态属性,则在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。

如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标之间类有时候需要修改原有观察者的代码,在一定程度上违反了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。

应用实例

大家应该打过CS对战游戏吧,我以CS1.5为例,在这个游戏中,多个玩家可以加入同一战队组成队伍,当战队中的某一成员受到敌人攻击时将给所有其他队友发送通知,队友收到通知后将作出响应。我们这里使用观察者模式设计并实现该过程,以实现战队队员之间的联动,

但这里我要声明一点,我们这个观察者模式设计出来是给CS1.5里面的机器人用的,不是给真实的游戏玩家用。因为真实的玩家自己会作判断下一步该做什么,因为是人控制的。我这里是给游戏里游戏自动生成的机器人用,机器人比较遵守团队规则,一旦发现队员受到攻击,马上会去支援。

CS1.5系统中战队成员之间的联动过程可以描述如下:队员受到攻击→发送通知给队友→队友作出响应。如果按照上述思路来设计系统,一个战队队员在受到攻击时需要通知他的每一位队友,每个队员都需要知道其他所有队友的信息,因此可以引入一个角色——“队员生存情况列表”来负责维护和管理每个战队中所有成员的信息。当一个队员受到攻击时将向生存情况列表发送信息,生存情况列表可通知到每个队友,队友再作出响应。

(1)AllyControlCenter: 队员生存情况列表类,充当抽象目标类。

package designpatterns. observer;
import java.util.*;

public abstract class AllyControlCenter {
   protected String allyName;  //CS战队名称  
   //定义一个集合用于存储战队成员
   protected ArrayList <Observer> players = new ArrayList <Observer>();
  
   public void setAllyName(String allyName){
     this. allyName= allyName;
  }

  public String getAllyName(){
     return this. allyName;
  }
  
   //注册方法
  public void join(observer obs){
     System.out.println(obs.getName()+"加入"+this. allyName);
     players. add(obs);
   }
  
   /注销方法
  public void quit(Observer obs){
      System.out.println(obs.getName()+"退出"+this. allyName);
  players.remove(obs)i
  }

  //声明抽象通知方法
  public abstract void notifyObserver(String name);
}

(2)ConcreteAllyControlCenter:具体队员生存情况列表类,充当具体目标类。

package designpatterns. observer;

public class ConcreteAllyControlCenter extends AllyControlCenter {
   public ConcreteAllyControlCenter(String allyName){
      System.out.println(allyName+"组建成功");
      System.out.println("************************");
     this. allyName= allyName;
  }
  
   //实现通知方法
  public void notifyObserver(String name){
     System.out.println(this. allyName+"紧急通知!队友"+name+"受到攻击!");
    //遍历观察者集合,调用每一个队友(自己除外)的支援方法
      for(object obs:players){
        if (!((Observer)obs).getName().equalsIgnoreCase(name)){
             ((Observer)obs). help();
          }
      }
   }
}

(3)Observer:抽象观察者类。

package desigmpatterns. observer;

public interface Observer {
  public String getName();
  public void setName(String name);
  public void help();    //声明支援队友方法
  public void beAttackec(AllyControlCenter acc); //声明遭受攻击方法
}

(4)Player:战队成员类,充当具体观察者类。

package designpatterns. observer;

public class Player implements Observer{
  private String name;
  
   public Player(String name){
  this. name = name;
   }

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

  public String getName(){
  return this. name;
   }

  //支援盟友方法的实现
  public void help()(
     System.out.println("先躲好,"+this. name+"来支援你");
  }

   //遭受攻击方法的实现,当遭受攻击时将调用队员生存情况列表类的通知方法notifyObserver()
   //来通知盟友
  public void beAttacked(AllyControlCenter acc)(
   System.out.println(this.name +"被攻击");
   acc.notifyObserver(name);
   }
}

(5)Client:客户端测试类。

package designpatterns. observer;

public class Client{
  public static void main(String args[]){
  /定义观察目标对象
  AllyControlCenter acc;
  acc = new ConcreteAllyControlCenter("CS战队");

  //定义4个观察者对象
  Observer player1, player2, player3, player4;

  player1= new Player("战士1");
  acc. join(playerl);

  player2= new Player("战士2");
  acc. join(player2);

  player3= new Player("战士3");
  acc. join(player3);
   
   player4= new Player("战士4");
  acc. join(player4);

  //某成员遭受攻击
  player1.beAttacked(acc);
   }
}

编译并运行程序,输出结果如下:

CS战队组建成功

***********************************

战士1加入CS战队

战士2加人CS战队

战士3加入CS战队

战士4加入CS战队

战士1被攻击

CS战队紧急通知!队友战士1受到攻击

先躲好,战士2来支援你

先躲好,战士3来支援你

先躲好,战士4来支援你

在本实例中实现了两次对象之间的联动,当一个游戏玩家Player对象的beAttacked()方法被调用时将调用队员生存情况列表AllyControlCenter的 notifyObserver()方法进行处理,而在notifyObserver()方法中又将调用其他 Player 对象的 help()方法。 Player的 beAttacked()方法,AllyControlCenter的notifyObserver()方法以及 Player的 help()方法构成了一个联动效应,执行顺序如下:

Player.beAttacked()→AllyControlCenter.notifyObserver()→Player. help()

各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的各类设计模式实现。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python实操】用python写软件弹窗

文章目录前言组件label 与 多行文本复选框组件Radiobutton单选组件Frame框架组件labelframe标签框架列表框Listboxscrollbar滚动条组件scale刻度条组件spinbox组件Toplevel子窗体组件PanedWindow组件Menu下拉菜单弹出菜单总结针对组件前言 python学习之路任重而道远&#xff0…

P6入门:创建项目计划工作分解结构WBS

目录 引言 Primavera P6 中的自顶向下方法 工作分解结构

LeetCode:1590. 使数组和能被 P 整除

记录一下今天遇到的每日一题&#xff0c;很绕&#xff0c;还需要考虑很多细节 思路 遍历数组&#xff0c;找出数组除余p之后余下的数字k&#xff0c;需要寻找的子数组也需要满足除余p之后余k。 找出所有满足的子数组&#xff0c;求出最短长度。 具体求子数组&#xff0c;求出…

CI/CD | 不可忽略的Jenkins基础架构修复问题

在系列文章第一篇和第二篇中&#xff0c;大家已经看到了在CloudBees的帮助下&#xff0c;让管理Jenkins解决方案从一个大麻烦变成轻而易举就能解决的事情。但是&#xff0c;现在让我们反思并退一步。有时候&#xff0c;这些问题并不是表面上的——它们是在成长的过程中造成的&a…

yyds,Elasticsearch Template自动化管理新索引创建

文章目录一、什么是Elasticsearch Template&#xff1f;二、Elasticsearch Template的用法2.1、创建模板2.2、验证模板2.3、应用模板2.4、删除模板2.5、组合模板2.6、如何在同一个模板中定义多种匹配模式2.7、模板优先级2.8、提前模拟索引的最终映射三、Elasticsearch Template…

Python多线程详解

文章目录1. 多线程2. 创建线程2.1 直接创建2.2 继承创建3. 守护线程4. 阻塞线程5. 线程锁5.1 互斥锁(Lock)5.2 递归锁(RLock)5.3 信号量(Semaphore)5.4 事件(Event)6. ThreadLocal7. 线程池7.1 基本使用7.2 as_completed 方法7.3 wait方法7.4 map方法8. Python线程真相参考文章…

linux时间的特殊用法

今天介绍linux下Date时间命令相关的特殊用法 date (当前的时间) 修改系统当前时间&#xff1a; date -s "2022-6-20 9:33:50" 昨天的时间是我们比较常用的&#xff1a; date -d "yesterday" %Y%m%d ( 昨天的时间) date -d "1 day ago" %Y%m%d …

如何写一个简单的爬虫

学习爬虫重要的是知识储备以及实战能力&#xff0c;最近有个学妹要求我帮她写一个爬虫程序&#xff0c;我将我编写的爬虫代码以及解释都记录下来&#xff0c;方便后期更多的伙伴们学习参考。 前置知识-爬虫定义 爬虫指的是一种自动化程序&#xff0c;用于在互联网上获取和抓取…

VB6换个思路解决微信下载文件只读的问题(含源码)

日期&#xff1a;2023年3月10日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

Android Framework——zygote 启动 SystemServer

概述 在Android系统中&#xff0c;所有的应用程序进程以及系统服务进程SystemServer都是由Zygote进程孕育&#xff08;fork&#xff09;出来的&#xff0c;这也许就是为什么要把它称为Zygote&#xff08;受精卵&#xff09;的原因吧。由于Zygote进程在Android系统中有着如此重…

docker使用教程(装linux比虚拟机方便)

目录 一、介绍 二、使用 1.下载操作系统 2.查看docker内的容器有哪些 3. 运行指定容器 4.进入容器 ​1.attach进入容器&#xff08;输入容器ID前4位&#xff09; 2.exec进入容器&#xff08;可以输入ID或者NAMES&#xff09; 5.退出容器 6.在宿主机器和容器之间拷贝文…

时间同步Chrony

时间同步chrony一、Chrony时间服务1、Chrony介绍2、Chrony优点二、配置Chrony服务三、验证一、Chrony时间服务 1、Chrony介绍 chrony 是基于NPT协议的实现时间同步服务&#xff0c;它既可以当做服务端&#xff0c;也可以充当客户端。chrony是ntp的代替品&#xff0c;能更精确…

数据传输服务DTS(阿里巴巴)

数据传输服务DTS(阿里巴巴) 什么是数据传输服务DTS 数据传输服务DTS&#xff08;Data Transmission Service&#xff09;是阿里云提供的实时数据流服务&#xff0c;支持关系型数据库&#xff08;RDBMS&#xff09;、非关系型的数据库&#xff08;NoSQL&#xff09;、数据多维分…

CentOS 7 使用 Composer 配置 phpmyadmin 并管理多个mysql

phpMyAdmin 中文文档 准备工作 CentOS 7 yum 方式安装 phpCentOS 7 安装 Apache HTTP Server安装Composer 安装 phpMyAdmin 按照官方文档 用Composer安装 要安装phpMyAdmin&#xff0c;只需运行&#xff1a; composer create-project phpmyadmin/phpmyadmin 建立网站配置文…

skywalking部暑(zookeeper、kafka、elasticsearch)

服务器IP部暑角色192.168.11.100zookeeper kafka elasticsearch 一、docker部暑 。。。 二、.安装Zookeeper path/data/zookeeper mkdir -p ${path}/{data,conf,log} chown -R 1000.1000 ${path}echo "0" > ${path}/data/myid #zookeeper配置文件 cat > ${p…

Gitee初练 --- 问题合集(一)

Gitee一、Windows找不到gpedit.msc请确定文件名是否正确的提示二、windows 10 凭据无法保存三、解决 git pull/push 每次都要输入用户名密码的问题一、Windows找不到gpedit.msc请确定文件名是否正确的提示 就随便在一个地方建立一个文本文件&#xff0c;将一下内容复制进去 e…

从0-1超详细教你使用nginx打包部署静态资源,以及hash和history配置汇总

首先呢&#xff0c;我们要有以下几个方面的知识和操作&#xff0c;来实现项目部署 第一&#xff1a;我们要搭建nginx部署基础环境 具体流程可参考这个链接从0-1超详细教你实现前端代码nginx部署全流程 第二&#xff1a;我们要知道前端路由hash和history实现以及区别 路由功…

Reactor响应式流的核心机制——背压机制

响应式流是什么&#xff1f; 响应式流旨在为无阻塞异步流处理提供一个标准。它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者&#xff0c;而不需要发布者阻塞&#xff0c;或订阅者有无限制的缓冲区或丢弃。 响应式流模型存在两种基本的实现机制。一种就是传统…

【OpenAI 多模态预训练】VideoGPT?微软透露GPT-4或将在下周发布

【多模态预训练】VideoGPT&#xff1f;微软透露GPT-4或将在下周发布 先让我猜个名字&#xff0c;VideoGPT&#xff1f; 太绝了&#xff01;看完ChatGPT之后就感觉OpenAI正在做多模态的预训练语言模型。万万没想到来的这么快。据介绍&#xff0c;GPT-4或将为多模态大模型&#…

redis经典五种数据类型及底层实现

目录一、Redis源代码的核心部分1.redis源码在哪里2.src源码包下面该如何看&#xff1f;二、我们平时说redis是字典数据库KV键值对到底是什么1.6大类型说明(粗分)2.6大类型说明3.上帝视角4.Redis定义了redisObject结构体4.1 C语言struct结构体语法简介4.2 字典、KV是什么4.3 red…