目录
一.专栏介绍
本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。
本章将开始模型-视图-控制器模式(MVC) 的学习,全称是MODEL-VIEW-CONTROLLER 。这是一种复合模式,也就是好几个设计模式组合一起使用。
二.认识模型-视图-控制器
想象你正在使用一个MP3播放器。我们可以用它的界面来管理播放列表(比如对歌曲进行增加,删除,查找,改歌曲的名字等等)。播放器维护一个小型数据库,里面有所有的歌曲以及相关的名称和数据。当然播放器也可以播放歌曲,此时,用户页面会显示不断更新的歌曲标题、运行时间等。
这个MP3的运行逻辑就是模型-视图-控制器(MVC)。

下面看一个更详细的图:

- 视图:呈现模型,视图通常直接从模型中取得显示所需的状态和数据。
- 控制器:取得用户输入并解读其对模型的含义。
- 模型:模型持有所有数据、状态和应用逻辑。模型对视图和控制器是无视的,虽然它提供了操纵和检索其状态的接口,并发送状态改变通知给观察者(注册的视图)。(模型是观察者模式中的主题)
下面我们理一下具体流程:
- **你是用户,你和视图交互。**视图是你看模型的窗口,当你对视图做一些事时(例如点击播放按钮),视图就告诉控制器你做了什么,控制器的工作是负责处理。
- **控制器要求模型改变状态。**控制器接受并解读你的动作,如果你点击某个按钮,控制器的任务就是理解这个动作的含义,以及如何基于此动作操纵模型。
- **控制器也可能要求视图做改变。**作为控制器从视图接收到动作的结果,控制器可能需要告诉视图改变。例如,控制器可以使得界面上的某些按钮或菜单项有效或无效。
- **当模型状态改变时,模型通知视图。**不管是基于一些你所做的动作(例如点击按钮),还是其他内部改变(例如播放列表的下一首歌开始),当模型中某些东西改变时,模型通知视图它的状态已改变。
- **视图直接从模型取得要显示的状态。**例如,当模型通知视图新歌开始播放,视图向模型询问歌名并显示。当控制器请求视图做某些改变时,视图也可能向模型询问状态。
三.把MVC理解位一套模式
MVC是复合模式,就是好几个设计模式的组合。
让我们从模型开始,模型使用观察者模式 来保持视图和控制器(其实这两个都可以作为观察者)可以随最新的状态变化而更新。另外,视图和控制器实现了策略模式 (视图组合使用控制器),控制器是视图的策略,如果你要不同的行为,换一个控制器很容易。视图本身也内部使用一个模式来管理widget,button,以及其他显示组件:组合模式。
我们通过书上的图来更详细地看一下:


1.控制器可以变成模型的观察者吗?
当然。在某些设计中,控制器会向模型注册,然后被通知变化。这是模型直接影响用户界面控件的情况。例如,模型中的某些状态可能支配界面的某些项目变成有效或无效。如果这样,要求视图更新相应的显示,确实是控制器的工作。
2.控制器所做的事情,就是从视图得到用户输入并发送到模型,对吗?如果只是做这些事,为什么要有控制器?为什么不把代码放在视图自身?大多数情况下,控制器不是只调用模型的方法吗?
当然可以;但是,你不会想这么做,有两个原因:其一,这会让视图的代码更复杂,因为现在视图有两个责任 :管理用户界面和处理如何控制模型的逻辑。其二,造成模型和视图之间紧耦合 。如果你要把这个视图复用于其他模型,别指望了。控制器把控制逻辑从视图中分离,把视图从模型解耦。通过保持视图和控制器松耦合,你建造了更有弹性和扩展性的设计,更容易容纳以后的改变。
3.控制器会实现任何应用逻辑吗?
不会。控制器为视图实现行为,它聪明地将来自视图的动作翻译成模型上的动作。模型收到这些动作,实现应用逻辑来决定如何响应动作。控制器也要做一些决定,决定调用模型的哪个方法,但这不能算是 "应用逻辑"。应用逻辑指管理和操纵存在于你的模型中的数据的代码。
4.我总是觉得 "模型" 这个词让我很头痛。我现在知道它是应用的内核,但是为什么要用这么模糊难懂的词汇来描述 MVC 的这个方面?
当给 MVC 起名时,他们需要用一个 M 开头的词,否则就不能叫作 MVC 了。
说正经的,我们同意你的看法。一开始大家都会挠头,纳闷模型是什么。不过大家也都认识到,除了模型,还真找不到更好的词。
5.你说了许多关于模型的状态。这是不是意味着用到状态模式?
不,我们指的是一般意义上的状态。当然,有些模型使用状态模式来管理它们的内部状态。
6.我看过有些人把 MVC 的控制器描述成视图和模型之间的 "中介者(mediator)"。控制器是否实现 "中介者模式"?
从某种程度来看,控制器可以被看作一个中介者,因为视图不会直接设置模型的状态,而是通过控制器进行。但是记住,视图的确持有模型的引用,以访问模型的状态。如果控制器真的是中介者,视图就必须也要通过控制器来取得模型的状态。
7.视图总是必须向模型询问状态吗?我们是否可以用推送模型,在更新通知时,顺便发送模型状态?
是的,模型当然可以把状态和通知一起发送,我们可以对 BeatModel 做类似的事情,通过仅发送视图感兴趣的状态。如果你记得观察者模式一章,或许还记得这样做有什么缺点。MVC 模型已经被适配到许多类似的模型(尤其是 Web 浏览器 / 服务器环境),因此你会发现这条规则有一大堆例外。
8.如果有两个以上的视图,我是不是总是需要两个以上的控制器?
通常来说,运行时一个视图需要一个控制器,但是,同一个控制器类可以轻易管理多个视图。
9.视图不应该操纵模型。但是,我注意到你的实现中,对于改变模型状态的方法,视图拥有完全的访问权限。这危险吗?
你说得对。对于模型的方法集,我们给视图完全的权限。这么做的原因是为了保持简单,但可能会有某些情形,你想只让视图访问模型的部分 API。有一个很棒的设计模式,允许你适配一个接口,起到只提供一个子集的作用,你能想起是哪个吗?(适配器模式)
四.总结
- 复合模式把两个或更多的模式结合成一个解决方案,解决重复发生或一般的问题。我们有一个新的类目!MVC 是一个复合模式。
- 模型 - 视图 - 控制器(MVC)是复合模式,结合了观察者、策略和组合模式。
- 模型使用观察者模式,以便可以保持观察者更新,同时依然保持两者之间解耦。
- 控制器是视图的策略,视图可以使用不同的控制器实现,得到不同的行为。
- 视图使用组合模式实现用户界面。用户界面通常组合了嵌套的组件,像面板、框架和按钮。
- 这些模式携手合作,解耦了 MVC 模型的三个层,这样可以保持设计干净又有弹性。
- 适配器模式用于把新的模型适配到已有的视图和控制器。
- MVC 已经被适配到 各种GUI框架,但Qt中的是一种变体。
- 把 MVC 模式适配到各种客户 / 服务器的应用结构,就得到许多 Web MVC 框架。各种Web MVC框架的不同点在于视图,控制器和模型在客户端还是服务端,又或者客户端和服务端共享。