目录
思维导图
一、什么是观察者模式?
二、有什么优点吗?
三、有什么缺点吗?
四、什么时候使用观察者模式?
五、代码展示
①、双向耦合的代码
②、解耦实践一
③、解耦实践二
④、观察者模式
六、这个模式涉及到了哪些知识点?
思维导图
一、什么是观察者模式?
又叫发布-订阅(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
{ }