设计模式—观察者模式(Observer)

目录

思维导图

一、什么是观察者模式?

二、有什么优点吗?

三、有什么缺点吗?

四、什么时候使用观察者模式?

五、代码展示

①、双向耦合的代码

②、解耦实践一

③、解耦实践二

④、观察者模式

六、这个模式涉及到了哪些知识点?


思维导图

一、什么是观察者模式?

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

观察者类似于卧底这样一个角色。在上班的时候,难免会有人趁老板不在的时候做一些工作之外的事情,例如炒炒股,看看NBA之类的,那如何应对老板突然回来这件事情呢?我们就需要卧底这样一个人给我们放风,老板一回来就通知这些人“老板回来了”。这其实就是一种观察者模式

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

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

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

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

二、有什么优点吗?

观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。满足依赖倒转原则

三、有什么缺点吗?

如果观察者很多的话,一个一个的通知会影响效率

四、什么时候使用观察者模式?

当一个对象的改变需要同时改变其他对象的时候。

而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。


五、代码展示

场景:小菜公司的同事在老板外出的期间偷偷地通过网页看股票行情,但是一不小心老板就回来了,让老板看到工作的时候做这些总是不太好的,如果避免这样的情况呢?就需要前台秘书这样的一个卧底,老板一回来了,前台秘书童子喆就拨了个电话告诉同事。但有一次因为老板回来直接就找童子喆做一些其他事情,童子喆就没来得及打电话,导致同事就没被通知到“老板回来了”

①、双向耦合的代码

	//前台秘书类class Secretary{//同事列表private IList<StockObserver> observers = new List<StockObserver>();    //IList接口,List泛型,集合数据类型是StockObserverprivate string action;                  //成员变量//构造方法public Secretary() { }//增加public void Attach(StockObserver observer){observers.Add(observer);           //向集合里面添加同事}//通知public void Notify(){foreach (StockObserver o in observers)    //foreach遍历集合:数据类型  变量名 in 遍历的集合o.Update();                           //一个一个的给同事通知}//前台状态public string SecretaryAction{get { return action; }                //get访问器set { action = value; }             //set访问器}}//看股票同事类class StockObserver{private string name;            //字符串成员变量private Secretary sub;          //前台秘书类类型成员变量public StockObserver(string name, Secretary sub)    //构造方法{this.name = name;           //赋值this.sub = sub;}public void Update()    //通知{Console.WriteLine("{0}{1}关闭股票行情,继续工作!", sub.SecretaryAction, name); //要通知的语句}} 

客户端代码:

//前台小姐童子喆
Secretary tongzizhe = new Secretary();//看股票的同事
StockObserver tongshi1 = new StockObserver("魏关姹", tongzizhe);
StockObserver tongshi2 = new StockObserver("易管查", tongzizhe);//前台记下了两位同事
tongzizhe.Attach(tongshi1);
tongzizhe.Attach(tongshi2);//发现老板回来
tongzizhe.SecretaryAction = "老板回来了";//通知两个同事
tongzizhe.Notify();Console.ReadKey ();

问题:前台类和看股票者类之间互相耦合。

②、解耦实践一

抽象观察类


abstract class Observer
{protected string name;protected Secretary sub;public Observer(string name, Secretary sub)     //有参的构造方法{this.name = name;this.sub = sub;}//通知public abstract void Update();
}

看股票的同事

class StockObserver : Observer
{//继承父类的构造方法public StockObserver(string name, Secretary sub) : base(name, sub) { }public override void Update(){Console.WriteLine("{0}{1} 关闭股票行情,继续工作!", sub.SecretaryAction, name);}
}

看NBA的同事

class NBAObserver : Observer
{public NBAObserver(string name, Secretary sub) : base(name, sub) { }public override void Update(){Console.WriteLine("{0}{1} 关闭NBA直播,继续工作!", sub.SecretaryAction, name);}
}

   前台秘书类

	class Secretary{private IList<Observer> observers = new List<Observer>();   //集合private string action;                                      //成员变量//增加public void Attach(Observer observer)    //针对抽象编程,减少了与具体类的耦合{observers.Add(observer);}//减少public void Detach(Observer observer)    //针对抽象编程,减少了与具体类的耦合{observers.Remove(observer);}//通知public void Notify(){foreach (Observer o in observers)o.Update();}//前台状态public string SecretaryAction     //属性{get { return action; }set { action = value; }}} 

客户端代码

 //前台小姐童子喆
Secretary tongzizhe = new Secretary();//看股票的同事
StockObserver tongshi1 = new StockObserver("魏关姹", tongzizhe);
StockObserver tongshi2 = new StockObserver("易管查", tongzizhe);//前台记下了两位同事
tongzizhe.Attach(tongshi1);
tongzizhe.Attach(tongshi2);//发现老板回来
tongzizhe.SecretaryAction = "老板回来了";//通知两个同事
tongzizhe.Notify();Console.ReadKey ();

③、解耦实践二

    通知者接口

	interface Subject{void Attach(Observer observer);     //增加void Detach(Observer observer);     //减少void Notify();                      //通知string SubjectState                 //前台状态{get;set;}}

    老板

	class Boss : Subject{//同事列表private IList<Observer> observers = new List<Observer>();private string action;//增加public void Attach(Observer observer){observers.Add(observer);}//减少public void Detach(Observer observer){observers.Remove(observer);}//通知public void Notify(){foreach (Observer o in observers)o.Update();}//前台状态public string SubjectState{get { return action; }set { action = value; }}}

前台秘书类

	class Secretary{//同事列表private IList<Observer> observers = new List<Observer>();private string action;//增加public void Attach(Observer observer){observers.Add(observer);}//减少public void Detach(Observer observer){observers.Remove(observer);}//通知public void Notify(){foreach (Observer o in observers)o.Update();}//前台状态public string SubjectState{get { return action; }set { action = value; }}}

抽象观察者

	abstract class Observer{protected string name;          //成员变量,姓名protected Subject sub;          //成员变量,通知者public Observer(string name, Subject sub)   //构造方法{this.name = name;                   //赋值this.sub = sub;}public abstract void Update();}

看股票的同事

	class StockObserver : Observer{public StockObserver(string name, Subject sub) : base(name, sub) { }public override void Update(){Console.WriteLine("{0}{1} 关闭股票行情,继续工作!", sub.SubjectState, name);}}#endregion

客户端代码

//老板胡汉三
Boss huhansan = new Boss();//看股票的同事
StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
//看NBA的同事
StockObserver tongshi2 = new StockObserver("易管查", huhansan);huhansan.Attach(tongshi1);
huhansan.Attach(tongshi2);huhansan.Detach(tongshi1);            //魏关姹其实是没有被老板通知到,所以减去//老板回来
huhansan.SubjectState = "我胡汉三回来了";
//发出通知
huhansan.Notify();Console.ReadKey();

④、观察者模式

Subject:抽象通知者/主题,一般用一个抽象类或者一个接口实现.它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以由任何数量的观察者.抽象主题提供一个接口,可以增加和删除观察者对象

	abstract class Subject{private IList<Observer> observers = new List<Observer>();//增加观察者public void Attach(Observer observer){observers.Add(observer);}//删除观察者public void Detach(Observer observer){observers.Remove(observer);}//通知public void Notify(){foreach (Observer o in observers)o.Update();}}

Observer类:抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己.这个接口叫做更新接口.抽象观察者一般用一个抽象类或者一个接口实现.更新接口通常包含一个Update()方法,这个方法叫做更新方法.

abstract class Observer
{public abstract void Update();
}

ConcreteSubject:具体主题/具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知.具体主题角色同一个具体子类实现.

class ConcreteSubject : Subject
{private string subjectState;//具体被观察者状态public string SubjectState{get { return subjectState; }set { subjectState = value; }}
}

// ConcreteObserver:具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调.具体观察者角色可以保存一个指向具体主题对象的引用.具体观察者角色通常用一个具体子类实现.

	class ConcreteObserver : Observer{//成员变量private string name;private string observerState;private ConcreteSubject subject;//构造方法public ConcreteObserver(ConcreteSubject subject,string name){this.subject = subject;    //赋值this.name = name;}//重写抽象类Update方法public override void Update(){observerState = subject.SubjectState;     //赋值Console.WriteLine("观察者{0}的新状态是{1}", name, observerState);  //控制台输出结果}//具体观察者状态public ConcreteSubject Subject {get { return subject; }set { subject = value; }}}

观察者模式的动机是将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就需要维护相关对象间的一致性.我们不希望为了维持一致性而使各类紧密耦合,这样会给维护\扩展和重用都带来不便.


六、这个模式涉及到了哪些知识点?

①、一个类里面有哪些东西?

②、字段和属性

字段

是什么?与类相关的变量;

干什么的?用来保存数据

属性

是什么?一个方法或一对方法

什么作用?具有两个方法,get、set访问器,读、写值

  • get访问器:返回与声明的属性相同的数据类型,表示的意思是调用时可以得到内部字段的值或引用;
  • set访问器:没有显式设置参数,但它有一个隐式参数,用关键字value表示,它的作用是调用属性时可以给内部的字段或引用赋值

显示和隐式字段初始化

字段的初始值必须是编译时可确定的。

如果没有初始化语句,字段的值会被编译器设为默认值,默认值由字段的类型决定。每种值类型的默认值都是0,bool型是false,引用类型默认为null。

class MyClass、
{int F1;	         //初始化为0     -值类型string F2;		 //初始化为null  -引用类型int F3 =25;		 //初始化为25string F4="abcd";  //初始化为“abcd”//声明多个字段,东逗号分隔int F1,F3 =25;string F2,F4="abcd";
}

③、访问修饰符

什么作用?指定程序的其他部分如何访问成员

有哪些?

  • private:私有的,只在类的内部可访问,成员默认是这个
  • public:公有的,对任何类可访问
  • protected:受保护的,只允许该类的派生类访问
  • internal:内部的,同一项目所有类可访问

④、构造方法

目的:对类进行初始化

特点:与类同名,无返回值、无void、在new时候调用

所有的类都有构造方法,不带参数的构造方法称为“默认构造方法”,如果你不编码则系统默认生成空的构造方法。若定义了新的构造方法,那么默认的构造方法就会失效。

⑤、方法重载

目的:在不改变原方法的基础上,新增功能

特点:一同二不同:多个方法名相同、参数类型/个数不同

class Animal
{private string name;//方法重载:方法名相同、数据类型/个数不同public Animal(){}          //无参的构造方法public Animal(string name) //有参的构造方法{this.name = name;}
}

⑥、foreach循环

连续访问数组中的每一个元素。

//语法
foreach(Type Identifier in ArrayName)Statement//实例
int[] arr1 = {10,11,12,13};foreach(int item in arr1)Console.WriteLine("Item Value:{0}",item);

⑦、List泛型集合

  • arrayList集合:不知道存什么类型,不知道存多少个
  • List:知道存什么类型,不知道存多少个。就是为了专门处理某种类型,在尖括号中写什么类型,这个集合就变成了什么类型的集合
  • 添加数据、插入数据、索引访问数据都是这个类型的,不用考虑所有的转换问题
static void Main(string[] args)
{List<int> list = new List<int>();     //实例化int类型//随机的往这个List集合中添加十个数字,不能重复,求和,求最大值,求最小值,求平均值Random r = new Random();int num = 0;while (list.Count !=10){num = r.Next(1, 100);if (!list.Contains (num)){list.Add(num);}}Console.WriteLine("最大值:{0}",list.Max ());Console.WriteLine("最小值:{0}",list.Min ());Console.WriteLine("和为:{0}",list .Sum ());Console.WriteLine("平均值为:{0}",list.Average ());Console.ReadKey();List<string> listStr = new List<string>();   //实例化string类型listStr.Add("哈哈,小杨又变帅了");
}

⑧、数组、ArrayList和List三者之间的对比:

⑨、抽象类

目的:抽取相同代码,实现封装思想

特点:

  • 抽象类不能实例化;
  • 抽象方法是必须被子类重写的方法;
  • 如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其他一般方法

⑩、重写

将父类实现替换为它自己的实现

虚成员

抽象成员

关键字

virtual

abstract

实现体

有实现体

没有实现体,被分号取代

在派生类中被覆写

可重写,也可不重写,使用override

必须被重写,使用override

⑩①、接口

接口提出了一种契约(或者说规范),让使用接口的程序设计人员必须严格遵守接口提出的约定。接口就可以看做是这种标准,它强制性地要求派生类必须实现接口约定的规范,以保证派生类必须拥有某些特性。

特点:

  • 不能实例化;
  • 不能有构造方法和字段;
  • 不能有修饰符;
  • 接口包含(事件、索引器、方法、属性);
  • 不包含方法的实现;
  • 实现接口的类必须实现接口中的所有方法和属性

⑩②、六大原则:依赖倒转原则

-高层模块不应该依赖底层模块。两个都应该依赖抽象(接口/抽象类)。

-抽象不应该依赖细节(具体类)。细节应该依赖抽象。

我现在要设计一个汽车,我先设计汽车大概的样子,先设计轮胎,根据轮胎在设计底盘,根据底盘在设计车身。现在我想要修改轮胎的尺寸,是不是就需要修改底盘,修改车身,整个全部都得修改?如果我们倒过来看,在设计车身,根据车身在设计底盘,根据底盘在设计轮胎,这样的话如果轮胎的尺寸变了,也不会影响到其他的部分

谁也不要依赖谁,除了约定的接口,大家都可以灵活自如。

针对书上所举的例子:无论主板、CPU、内存、硬盘都是在针对接口设计的。CPU作为主板上一个可移动的、可扩展的部分,在设计主板的时候只需要把接口定义好,内部再复杂我也不让外界知道,而主板只需要预留与CPU阵脚的插槽就可以了。内存、硬盘、显卡都是如此,哪部分坏了直接更换那部分就行了,而不会导致整个主板全部都要换。

 

⑩③、六大关系

  • 依赖:使用关系,一个类的使用需要另一个类的协助

实现:局部变量、构造方法的参数、静态方法的调用

图形:虚线+箭头,指向被拥有者

Animal {Public Water Grownup(Water water) {return null;}
}

  • 关联:拥有关系,一个类需要使用另一个类的属性和方法

实现:成员变量

图形:实线+箭头

class Water {public Climate m_Climate;public Water(){}
}class Climate {public Climate() {}
}

  • 聚合:整体和部分的关系,部分不能脱离整体而单独存在

实现:成员变量+构造方法的参数赋值

图形:实线+空心菱形,菱形指向整体

class GooseGroup {public Goose goose;Public GooseGroup(Goose goose) {this.goose = goose;}
}
class Work
{private State current;public Work(){current = new ForenoonState();}
}
  • 继承:子类继承父类

实现:子类:父类

图形:实线+空心三角形,箭头指向父类

class Cat:Animal
{}

  • 实现:类和接口的关系,类实现接口的所有特征

实现:类:接口

图形:虚线+空心三角,箭头指向接口

Class WideGoose:Ifly
{ }

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

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

相关文章

开发一个npm包

1 注册一个npm账号 npm https://www.npmjs.com/ 2 初始化一个npm 项目 npm init -y3编写一段代码 function fn(){return 12 }exports.hellofn;4发布到全局node_module npm install . -g5测试代码 创建一个text文件 npm link heath_apisnode index.js6登录(我默认的 https…

docker,nvidia-docker安装

卸载先前的docker Docker 的旧版本被称为 docker&#xff0c;docker.io 或 docker-engine 。如果已安装&#xff0c;请卸载它们&#xff1a; sudo apt-get remove docker docker-engine docker.io containerd runc使用 Docker 仓库进行安装 设置仓库 更新 apt 包索引 sudo…

基于单片机教室人数实时检测系统

一、系统方案 主程序中main函数主要是引脚的初始化&#xff0c;给单片机引脚初始化&#xff0c;初始化LCD1602&#xff0c;初始化红外对管&#xff0c;通过对LCD1602赋值&#xff0c;采集进入教室的人数&#xff0c;显示在LCD1602上面进出人数我们采用按键的形式&#xff0c;检…

opencv鼠标事件函数setMouseCallback()详解

文章目录 opencv鼠标事件函数setMouseCallback()详解1、鼠标事件函数&#xff1a;&#xff08;1&#xff09;鼠标事件函数原型&#xff1a;setMouseCallback()&#xff0c;此函数会在调用之后不断查询回调函数onMouse()&#xff0c;直到窗口销毁&#xff08;2&#xff09;回调函…

肖sir__linux详解__001

linux详解: 1、ifconfig 查看ip地址 2、6版本&#xff1a;防火墙的命令&#xff1a; service iptables status 查看防火墙状态 service iptables statrt 开启防火墙 service iptables stop 关闭防火墙 service iptables restart 重启防火墙状态 7版本&#xff1a; systemctl s…

考前冲刺上岸浙工商MBA的备考经验分享

2023年对于许多人来说都是不平凡的一年&#xff0c;历经三年的抗争&#xff0c;我们终于成功结束了疫情。而我也很幸运的被浙工商MBA项目录取&#xff0c;即将开始全新的学习生活。身为一名已在职工作6年的人&#xff0c;能够重回校园真是一种特别令人激动的体验。今天&#xf…

域内密码喷洒

在Kerberos阶段认证的AS-REQ阶段&#xff0c;请求包cname对应的值是用户名&#xff0c;当用户名存在时候&#xff0c;密码正确和错误两种情况下&#xff0c;AS-REP返回包不一样&#xff0c;所以可以利用这一点对域用户名进行密码喷洒攻击 域内密码喷洒工具 Kerbrute kerbrut…

【Redis】redis入门+java操作redis

目录 一、Redis入门 1.1 Redis简介 1.2 Redis下载与安装 1.2.1 下载 1.2.2 linux安装 1.2.3 windows安装 1.3 Redis服务启动与停止 1.3.1 linux启动、停止Redis服务 1.3.2 windows启动、停止Redis服务 1.4 修改Redis启动密码 1.4.1 Linux修改设置 1.4.2 windows设…

使用HTTPS模式建立高效爬虫IP服务器详细步骤

嘿&#xff0c;各位爬虫小伙伴们&#xff01;想要自己建立一个高效的爬虫IP服务器吗&#xff1f;今天我就来分享一个简单而强大的解决方案——使用HTTPS模式建立工具&#xff01;本文将为你提供详细的操作步骤和代码示例&#xff0c;让你快速上手&#xff0c;轻松建立自己的爬虫…

【力扣每日一题】2023.8.29 带因子的二叉树

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一些元素&#xff0c;让我们用这些元素连接形成特定的二叉树&#xff0c;每种元素可以使用任意次数&#xff0c;形成的二叉树要…

【云计算•云原生】5.云原生之初识DevOps

文章目录 1.DevOps背景2.DevOps概念3.DevOps工具链 1.DevOps背景 软件开发必须包含两个团队&#xff1a;开发团队和运维团队 开发团队负责开发项目&#xff0c;系统迭代更新运维团队负责项目测试以及部署上线&#xff0c;维持系统稳定运行 一个软件周期中是由这两个团队相互…

数据库备份

数据库备份&#xff0c;数据库为school&#xff0c;素材如下 1.创建student和score表 目录 数据库备份&#xff0c;数据库为school&#xff0c;素材如下 1.创建student和score表 2.为student表和score表增加记录 3.备份数据库school到/backup目录 4.备份MySQL数据库为带…

Windows10上使用llama-recipes(LoRA)来对llama-2-7b做fine-tune

刚刚在Windows10上搭建环境来对llama2做finetune&#xff0c;里面坑还是挺多的&#xff0c;这里把印象中的坑整理了一下以作备忘。 llama-recipes是meta的开源项目&#xff0c;Github地址为&#xff1a;GitHub - facebookresearch/llama-recipes: Examples and recipes for Ll…

Go几种读取配置文件的方式

比较有名的方案有 使用viper管理配置[1] 支持多种配置文件格式&#xff0c;包括 JSON,TOML,YAML,HECL,envfile&#xff0c;甚至还包括Java properties 支持为配置项设置默认值 可以通过命令行参数覆盖指定的配置项 支持参数别名 viper[2]按照这个优先级&#xff08;从高到低&am…

UE4 显示遮挡物体

SceneDepth是你相机能够看见的物体的深度距离 CustomDepth是你相机包括看不见被遮挡的物体的深度距离 如果CustemDepth比SceneDepth的距离相等&#xff0c;那么就是没有被遮挡的物体&#xff0c;如果被遮挡那么就是CustemDepth比SceneDepth深度距离远&#xff0c;然后再做对应…

【业务功能篇91】微服务-springcloud-多线程-线程池执行顺序

一、线程的实现方式 1. 线程的实现方式 1.1 继承Thread class ThreadDemo01 extends Thread{Overridepublic void run() {System.out.println("当前线程:" Thread.currentThread().getName());} }1.2 实现Runnable接口 class ThreadDemo02 implements Runnable{…

Win7下设置“定时关机”的方法

【Win7下设置定时关机的方法】 ●【所有程序】→【附件】→【系统工具】→【任务计划程序】 ● 右键单击&#xff0c;选择【创建基本任务】&#xff0c;然后在【任务名称】中填自定义名称&#xff0c;如“定时关机” ● 之后&#xff0c;按照下面各图的提示进行“任务触发器”…

在Ubuntu上安装CUDA和cuDNN以及验证安装步骤

在Ubuntu上安装CUDA和cuDNN以及验证安装步骤 本教程详细介绍了如何在Ubuntu操作系统上安装CUDA&#xff08;NVIDIA的并行计算平台&#xff09;和cuDNN&#xff08;深度神经网络库&#xff09;&#xff0c;以及如何验证安装是否成功。通过按照这些步骤操作&#xff0c;您将能够…

QTday4

一、闹钟 头文件 源文件 二、XMind思维导图

Vue框架--理解MVVM

我们知道&#xff0c;MVVM是Model-View-ViewModel的简写。它本质上就是MVC的改进版。我们看看MVVM的模型架构&#xff0c;如下所示: 架构理解与实例