设计模式—观察者模式(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.rhkb.cn/news/118750.html

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

相关文章

在 Python 中构建卷积神经网络; 从 0 到 9 的手绘数字的灰度图像预测数字

一、说明 为了预测从0到9的数字&#xff0c;我选择了一个基于著名的Kaggle的MNIST数据集的数据集。数据集包含从 <0> 到 <9> 的手绘图数字的灰度图像。在本文中&#xff0c;我将根据像素数据&#xff08;即数值数据&#xff09;和卷积神经网络预测数字。 二、 卷积…

智能合约安全分析,针对 ERC777 任意调用合约 Hook 攻击

智能合约安全分析&#xff0c;针对 ERC777 任意调用合约 Hook 攻击 Safful发现了一个有趣的错误&#xff0c;有可能成为一些 DeFi 项目的攻击媒介。这个错误尤其与著名的 ERC777 代币标准有关。此外&#xff0c;它不仅仅是众所周知的黑客中常见的简单的重入问题。 这篇文章对 …

2、Nginx 安装

文章目录 2、Nginx 安装2.1 官网下载2.2 安装 nginx2.2.1 第一步2.2.2 第二步2.2.3 第三步&#xff0c;安装 nginx2.2.4 第四步&#xff0c;修改防火漆规则 【尚硅谷】尚硅谷Nginx教程由浅入深 志不强者智不达&#xff1b;言不信者行不果。 2、Nginx 安装 2.1 官网下载 nginx…

软件测试面试怎样介绍自己的测试项目?会问到什么程度?

想知道面试时该怎样介绍测试项目&#xff1f;会问到什么程度&#xff1f;那就需要换位思考&#xff0c;思考HR在这个环节想知道什么。 HR在该环节普遍想获得的情报主要是下面这2个方面&#xff1a; 1&#xff09;应聘者的具体经验和技术能力&#xff0c; 2&#xff09;应聘者的…

Python实战之数据表提取和下载自动化

在网络爬虫领域&#xff0c;动态渲染类型页面的数据提取和下载自动化是一个常见的挑战。本文将介绍如何利用Pyppeteer库完成这一任务&#xff0c;帮助您轻松地提取动态渲染页面中的数据表并实现下载自动化。 一、环境准备 首先&#xff0c;确保您已经安装了Python环境。接下来…

Android.mk开发模板

今天简单写了一个 Android.mk 的示例模板&#xff0c;供初学者参考。 本模板主要给大家示例 Android NDK 开发中的如下几个问题&#xff1a; 如何自动添加需要编译的源文件列表如何添加第三方静态库、动态库的依赖如何构造一个完整的NDK工程框架 假设我们的项目依赖 libmath.…

【Go 基础篇】Go语言结构体基本使用

在Go语言中&#xff0c;结构体是一种重要的数据类型&#xff0c;用于定义和组织一组不同类型的数据字段。结构体允许开发者创建自定义的复合数据类型&#xff0c;类似于其他编程语言中的类。本文将深入探讨Go语言中结构体的定义、初始化、嵌套、方法以及与其他语言的对比&#…

小赢科技,寻找金融科技核心价

如果说金融是经济的晴雨表&#xff0c;是通过改善供给质量以提高经济质量的切入口&#xff0c;那么金融科技公司&#xff0c;就是这一切行动的推手。上半年&#xff0c;社会经济活跃程度提高背后&#xff0c;金融科技公司既是奉献者&#xff0c;也是受益者。 8月29日&#xff0…

postgresql并行查询(高级特性)

######################## 并行查询 postgresql和Oracle一样支持并行查询的,比如select、update、delete大事无开启并行功能后,能够利用多核cpu,从而充分发挥硬件性能,提升大事物的处理效率。 pg在9.6的版本之前是不支持的并行查询的,从9.6开始支持并行查询,但是功能非常…

go学习part21(3)redis连接池

连接池 1.介绍 每次使用数据就就建立链接再关闭可以&#xff0c;但是如果有大量客户端频繁请求连接&#xff0c;大量创建连接和关闭会非常耗费资源。 所以就建立一个连接池&#xff0c;里面存放几个不关闭的连接&#xff0c;谁要用就分配给谁。 说明:通过Golang 对 Redis操…

WebGPT VS WebGPU

推荐&#xff1a;使用 NSDT编辑器 快速搭建3D应用场景 随着WebGPU的引入&#xff0c;Web开发发生了有趣的转变&#xff0c;WebGPU是一种新的API&#xff0c;允许Web应用程序直接访问设备的图形处理单元&#xff08;GPU&#xff09;。这种发展意义重大&#xff0c;因为 GPU 擅长…

sublime编辑latex 出现参考文献无法编译报错:citation “...” undefined

问题描述 使用sublime编译latex文件时&#xff0c;参考文献按照常规的方式放好&#xff0c;ctrl B 编译的时候&#xff0c;显示找不到参考文献&#xff0c;编译出的pdf文件也没有references&#xff1a; 但是把文件放到overleaf上就可以直接编译出来&#xff0c;说明是本地编…

快速为RPG辅助工具MTool增加更多快捷键(一键保存等)

起源&#xff1a;MTool是个好工具&#xff0c;本身固然好用&#xff0c;但是它本身的快捷键功能很少&#xff0c;虽然内置了一个录制工具&#xff0c;但是一个个的录&#xff0c;又麻烦&#xff0c;一般人也难以掌握 本文用快速方法增加更多快捷键&#xff0c;可以做到一键保存…

c++ qt--线程(一)(第八部分)

c qt–线程&#xff08;一&#xff09;&#xff08;第八部分&#xff09; 一.进程&#xff08;Process&#xff09; 在任务管理器中的进程页下&#xff0c;可以看到进程&#xff0c;任务管理器将进程分为了三类&#xff0c;应用、后台进程、window进程 应用&#xff1a; 打开…

【UE 材质】实现方形渐变、中心渐变材质

步骤 一、实现方形渐变 1. 新建一个材质&#xff0c;材质域选择“后期处理” 2. 通过“Mask”节点单独获取R、G通道&#xff0c;可以看到R通道是从左到右0~1之间的变化&#xff0c;对应U平铺 可以看到G通道是从上到下0~1之间的变化&#xff0c;对应V平铺 3. 完善如下节点 二、…

Leetcode1090. 受标签影响的最大值

思路&#xff1a;根据值从大到小排序&#xff0c;然后在加的时候判断是否达到标签上限即可&#xff0c;一开始想用字典做&#xff0c;但是题目说是集合却连续出现两个8&#xff0c;因此使用元组SortedList进行解决 class Solution:def largestValsFromLabels(self, values: li…

Java后端开发面试题——多线程

创建线程的方式有哪些&#xff1f; 继承Thread类 public class MyThread extends Thread {Overridepublic void run() {System.out.println("MyThread...run...");}public static void main(String[] args) {// 创建MyThread对象MyThread t1 new MyThread() ;MyTh…

aarch64-linux交叉编译libcurl带zlib和openssl

交叉编译libcurl需要依赖zlib和openssl 需要先用aarch64工具链编译zlib和openssl aarch64-linux环境搭建 下载工具链 gcc用于执行交叉编译 gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnusysroot是交叉版本的库文件集合 sysroot-glibc-linaro-2.25-2019.12-aarch64-lin…

iOS逆向进阶:iOS进程间通信方案深入探究与local socket介绍

在移动应用开发中&#xff0c;进程间通信&#xff08;Inter-Process Communication&#xff0c;IPC&#xff09;是一项至关重要的技术&#xff0c;用于不同应用之间的协作和数据共享。在iOS生态系统中&#xff0c;进程和线程是基本的概念&#xff0c;而进程间通信方案则为应用的…

在 Spring Boot 中集成 MinIO 对象存储

MinIO 是一个开源的对象存储服务器&#xff0c;专注于高性能、分布式和兼容S3 API的存储解决方案。本文将介绍如何在 Spring Boot 应用程序中集成 MinIO&#xff0c;以便您可以轻松地将对象存储集成到您的应用中。 安装minio 拉取 minio Docker镜像 docker pull minio/minio创…