观察者模式使用场景:
在软件构造中,需要为某些对象建立一种“通知依赖关系”,一个对象的状态如果发生变化,所有的依赖对象都将得到通知。如果这种依赖关系过于紧密,将导致软件不能很好的抵抗变化。而使用面向对象的方法可以将这种依赖关系弱化,并且形成一种稳定的依赖关系,从而实现软件体系结果的松耦合。
观察者模式定义:
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖与她的对象都得到通知并且自动更新
现在假设我们需要完成一个功能:用进度条显示游戏加载了多少。我们有以下的函数
// 假设这个类是窗体类
class Windows
{
public:
Windows()
{
// 构造函数
}
};
// 假设这个类是进度条类
class ProcessBar
{
public:
ProcessBar()
{
//构造函数
}
void SetValue(int value)
{
// 设置进度为多少
}
};
那么我们怎么才能完成游戏的加载功能呢?第一个反应就是在游戏加载类中添加一个进度条。
// 加载游戏类1,这个类需要显示游戏加载了多少
class GameLoad1
{
public:
ProcessBar *bar;
};
上面这个方法,在加载类里面有一个具体的进度条类型
但是进度条在pc端可能是一个样子,但是在IOS端或者安卓端可能又是另外一个样子
那么怎么保持代码的可移植性呢?
而且这个代码会导致GameLoad1和ProcessBar类是编译时依赖,但是我们更加喜欢运行时依赖
那么我们也需要一个方法使他变成运行时依赖。
方法如下
我们可以设置一个进度条的接口类
class IProcessBar
{
public:
// 进度设置
virtual void DoProgress(int value) = 0;
};
上面的这一段代码必然可以在任何操作系统端进行,因为它没有绑定任何具体的类。
然后我们在窗体类内实现这个接口
// 因为IProgressBar是接口,因此可以使用继承的方式。
class Windows1 :public IProcessBar
{
ProcessBar* bar;
public:
virtual void DoProgress(int value)
{
// 具体的实现
bar->SetValue(10);
}
};
此时,游戏加载类内的代码为
class GameLoad2
{
public:
IProcessBar* bar;
};
我们比较这个方法和上面的方法,发现,第二个方法内的游戏加载类是使用进度条接口,内部无具体实现,说明这段代码可以在任何系统上执行。
此外,第二个方法将进度条功能实现转移到了窗体类中,为什么要这么转移呢?因为这样子就解除了游戏加载和具体进度条的耦合性。
那有人可能会说,最后进度条还是要实现,到了新的系统中还是需要重新写相关的代码,又有什么用呢?
比较上面的两种方法,发现第一种移植到了新的系统中需要对游戏加载类进行修改,第二个方法移植到了新的系统中只需要对窗体类进行修改。而新的系统窗体类必然是要修改的。这样子就把变化点转移到了窗体类中。
最后对代码进行一些改进,实现多种通知,实现方法很简单代码如下:
// 现在假设我们需要多种通知方式,比如说在进度条显示的时候控制台也要通知,如何实现呢
class ConsoleNotice:IProcessBar
{
virtual void DoProgress(int value)
{
// 控制台通知的具体实现
}
};
class GameLoad3
{
public:
vector<IProcessBar*>Bars;
};
总结
使用面向对象的抽象,观察者模式可以使我们独立地改变目标与观察者,从而使两者之间地依赖达到松耦合
目标发送通知时,无需指定观察者,通知可以自动传播
观察者自己决定是否需要订阅通知,目标对象对此一无所知
观察者模式时基于事件地UI框架中非常常用地设计模式,也是MVC模式地一个重要组成部分。
我的话:
这个只是我看设计模式这个视频的笔记,很多话说的不好,我也是一知半解。大家如果对设计模式有兴趣的话,可以在B站搜索设计模式,我看的是11个小时的那个。李建忠老师讲的不错。
目前看起来,设计模式很大程度上就是使用一层接口来隔离一些部分。