(一)什么是观察者模式
发布-订阅,这两个词语是对观察者的最好解释,现实生活中,这样的案例有很多,比如在篮球比赛过程中教练,喊一个暂停,然后球员和裁判都做相关的响应,还有比如OA里面发布的放假通知等等。无论是篮球比赛,还是OA的通知,都存在一个角色,它的作用就是保持对相关问题的关注,在问题发生变化的时候,是Ta把消息通知给相关各方。观察者模式也差不多这样,它抽象一类对象(观察者)专门负责"盯着"目标对象,当目标对象状态有变动的时候,每个观察者就会获得通知并迅速做出响应,观察者模式解决的也是调用通知关系所带来的依赖。
上面的概念是比较官方的,我喜欢用自己的语言来讲解,比如现实生活中的,蝴蝶效应,我们程序里面的,下订单,就要发短信,减少库存呀,等等一系列的连锁反应,一句话,就是某个人发出一个行为,会引起一系列的连锁反应。
( 二 ) 一个简单的案例
南美洲亚马孙河边热带雨林中的蝴蝶,偶尔扇几下翅膀,就有可能在两周后引起美国得克萨斯的一场龙卷风。原因在于:蝴蝶翅膀的运动,导致其身边的空气系统发生变化,并引起微弱气流的产生,而微弱气流的产生又会引起它四周空气 或其他系统产生相应变化,由此引起连锁反应,最终导致其他系统的极大变化。“蝴蝶效应”听起来有点荒诞,但说明了事物发展的结果,对初始条件具有极为敏感的依赖性;初始条件的极小偏差,将会引起结果的极大差异。这就是蝴蝶效应,下面我们用程序来模拟整个过程。
假设蝴蝶煽动翅膀,可以引起鸡飞狗跳,老鼠出洞,天气变化,飓风形成。
/// <summary> /// 蝴蝶 /// 假设蝴蝶煽动翅膀,可以引起鸡飞狗跳,老鼠出洞,天气变化,飓风形成 /// </summary> public class ButterFly { /// <summary> /// 蝴蝶起飞 /// </summary> public void Fly(){ Console.WriteLine("蝴蝶起飞啦"); Console.WriteLine("---------------产生连锁反应---------------"); new Chicken().Fly(); new Dog().Jump(); new Mouse().GoOut(); new Weather().Change(); new Wind().Generate(); Console.WriteLine("---------------蝴蝶效应结束---------------"); } }
这就是我们蝴蝶类,它起飞会造成这么多影响,会和这么多类产生关联(耦合太高了)。如果有一天说要增加一个老虎上山了呢,或者说要移除一个,或者说改变一下顺序,现在我们就要改变我们的蝴蝶类。我们来分析造成这个的原因是为啥呢,因为这个类违法了单一职责的原则,蝴蝶飞就可以了,那为啥还要去管飞完之后产生的连锁反应呢,那行我们知道了这个就是不稳定的原因,那接下来开始我们的耍锅套路,把不是自己的东西丢出去,做好自己的事情就可以了。
那怎么丢呢,我们都应该要开发一个接口给别人添加关联进来, 因为这里边有很多类,并且都没有啥关联,所以只能才有一个接口,把他们都联系起来。所以我们接下来定义一个接口IObserver,让鸡,狗,天气等等类实现这个接口。
public interface IObserver{ void Action(); }
鸡类
public class Chicken:IObserver { public void Fly() { Console.WriteLine("鸡鸡起飞了"); } public void Action() { Fly(); } }
当所有类实现这个接口的时候,我们就可以在蝴蝶类里面这样定义了;先给定义一个集合和添加观察者的方法,用来存储观察者的集合。
private List<IObserver> _objServers=new List<IObserver>(); public void AddObserver(IObserver item) { this._objServers.Add(item); } /// <summary> /// 蝴蝶起飞 /// </summary> public void Fly(){ foreach (var obj in _objServers){ obj.Action(); } }
我们在客户端调用呢
ButterFly butterFly=new ButterFly(); butterFly.AddObserver(new Chicken()); butterFly.AddObserver(new Dog()); butterFly.AddObserver(new Mouse()); butterFly.AddObserver(new Weather()); butterFly.AddObserver(new Wind()); butterFly.Fly();
可能很多同学还是会说,这里还是有依赖,对确实的,但是我们把依赖交给了上游,上游改变不会影响下游,也就是蝴蝶类。
从上面这个例子,我们看出观察者模式,有三个东西,第一:引起连锁反应的类(主题) 第二:有一个接口,实现了这个接口都是可以成为观察者的,第三:观察者。
(三)观察者模式的演变
下面请看代码。有三个类,A,B,C想接收来自X的消息,简单的说X发生变化,他们也跟着变化。
public class A {
public int Data;
public void Update(int data) {
this.Data = data;
}
}
public class B {
public int Count;
public void NotifyCount(int data) {
this.Count = data;
}
}
public class C {
public int N;
public void Set(int data) {
this.N = data;
}
}
//上面这三个类,类A,B,C是希望获得X通知的类型
public class X {
private int data;
public A InstanceA;
public B InstanceB;
public C InstanceC;
public void SetData(int data) {
this.data = data;
InstanceA.Update(data);
InstanceB.NotifyCount(data);
InstanceC.Set(data);
}
}
在我们调用的时候,应该这么做
A a = new A();
B b = new B();
C c = new C();
X x = new X();
x.InstanceA = a;
x.InstanceB = b;
x.InstanceC = c;
x.SetData(10);
Console.WriteLine("x发出消息了");
Console.WriteLine(a.Data);
Console.WriteLine(b.Count);
Console.WriteLine(c.N);
对于上面这种做法,大家应该都会。下面是这个上面例子的类图关系,从这里,我们可以看得X和太多类直接关联了。已经很不符合面向对象的设计原则了。
经过观察,我们发现类A,B,C都有个共同之处,都是有一个类似于更新的方法,如果对他们进行抽象的话呢,就可以让类X仅仅依赖于一个抽象的类型。下面直接看代码
public interface IUpdatableObject {
int Data {get;}
void Update(int newData);
}
public class A : IUpdatableObject {
private int data;
public int Data { get { return this.data; } }
public void Update(int newData) {
this.data = newData;
}
}
public class B : IUpdatableObject {
private int data;
public int Data { get { return this.data; } }
public void Update(int newData) {
this.data = newData;
}
}
public class C : IUpdatableObject {
private int data;
public int Data { get { return this.data; } }
public void Update(int newData) {
this.data = newData;
}
}
public class X {
private IUpdatableObject[] objects = new IUpdatableObject[3];
//这个是索引器的用法
public IUpdatableObject this[int index] { set { objects[index] = value; } }
private int data;
public void Update(int newData) {
this.data = newData;
foreach(IUpdatableObject obj in objects) {
obj.Update(newData);
}
}
}
//调用代码如下
X x = new X();
IUpdatableObject a = new A();
IUpdatableObject b = new B();
IUpdatableObject c = new C();
x[0] = a;
x[1] = b;
x[2] = c;
x.Update(10);
Console.WriteLine("x发出了消息");
Console.Write(a.Data);
Console.Write(b.Data);
Console.Write(c.Data);
通过上面我们就可以依赖于抽象了,比如当我们要新增加一个订阅者的时候,也不用去类X里面改内部代码,请看类图。
下面开始我们的版本的三;
先看看这幅图,以后我们把消息的发布者,叫做主题,接收着叫做观察者。主题类似于前文的X类,观察者类似于A,B,C类。
第二版本的时候,我们已经解决了两个对象之间的松耦合,关于观察者的一切,主题只需要知道他实现了哪个接口(前文中的IUpdatableObject接口),只要是实现了这个接口的,主题就会把他认为是观察者。
继续这观察者模式的发展和演变,到了我们第三个版本的观察者模式。到了这步,我们想到如果有观察者要退出,不订阅这个主题了,等等操作(当主题发生变化的时候,,我们应该怎么做)。根据我们的面向对象的经验,我们应该很容易想到应该把主题对象也要抽象化。
public interface ISubject { int Data { get; set; } //这个Data就是类似于用来发送消息的。 /// <summary> /// 追加 /// </summary> /// <param name="obs"></param> void Attach(IObserver obs); /// <summary> /// 删除 /// </summary> /// <param name="obs"></param> void Detach(IObserver obs); /// <summary> /// 监听 /// </summary> void Notify(); } public interface IObserver { int Data { get; set; } void Update(int n); } public class ConcreteSubjectA : ISubject { public int Data { get; set; } public List<IObserver> ObsList = new List<IObserver>(); public void Attach(IObserver obs) { Console.WriteLine("添加观察者成功"); ObsList.Add(obs); } public void Detach(IObserver obs) { Console.WriteLine("删除观察者成功"); ObsList.Remove(obs); } public void Notify() { foreach(var obs in ObsList) { obs.Update(this.Data); } } } public class ConcreteObserverA : IObserver { public int Data { get; set; } public void Update(int n) { this.Data = n; } } public class ConcreteObserverB : IObserver { public int Data { get; set; } public void Update(int n) { this.Data = n; } } public class ConcreteObserverC : IObserver { public int Data { get; set; } public void Update(int n) { this.Data = n; } }
ISubject csA = new ConcreteSubjectA(); IObserver a = new ConcreteObserverA(); IObserver b = new ConcreteObserverB(); IObserver c = new ConcreteObserverC(); csA.Attach(a); csA.Attach(b); csA.Attach(c); csA.Data = 20; //移除观察者 csA.Detach(b); csA.Notify(); //我们现在做的这个版本相对于上一个版本就是增加了对观察者的管理。 //以及我们可以让我们的程序都是依赖于抽象。 Console.WriteLine($"类A的数据为:{a.Data}"); Console.WriteLine($"类B的数据为{b.Data}"); Console.WriteLine($"类C的数据为{c.Data}");
降到这里基本上把观察者模式给讲完了,我更加喜欢叫做发布订阅模式,这样更加好理解。
我们上面的那个例子还可以在抽象一点,利用泛型类和泛型方法来做,真正的达到抽象。下面是最终版本的实现代码(把泛型类和泛型方法用上),这个大家可以思考下怎么做?相关代码暂时不贴出来先。
最后,希望大家能够带带我这个菜鸟,谢谢各位。