python设计模式9模型视图控制器MVC(model-view
关注点分离(SoC separation of concerns)师软件工程相关的设计原则之一。SoC原则背后的想法是将应用程序分成不同的部分,每个部分解决一个单独的问题。这种关注点的例子是分层设计中使用的层(数据访问层、业务逻辑层、表现层等等)。使用SoC原则可以简化软件应用程序的开发和维护。
MVC模式只不过是将SoC原则应用于OOP。该模式的名称来自于用于分割软件应用的三个主要组件:模型、视图和控制器。MVC被认为是一种架构模式,而不是一种设计模式。架构模式和设计模式的区别在于,前者比后者的范围更广。然而,MVC太重要了,不能因为这个原因就跳过。即使我们永远不需要从头开始实现它,我们也需要熟悉它,因为所有常见的框架都使用MVC或其变种。
模型是核心组件。它代表知识。它包含并管理应用程序的(业务)逻辑、数据、状态和规则。视图是模型的可视化表示。视图的例子有计算机GUI、计算机终端的文本输出、智能手机的应用GUI、PDF文档、饼状图、柱状图等等。视图只显示数据,它并不处理数据。控制器是模型和视图之间的纽带/粘合剂。模型和视图之间的所有通信都通过控制器进行。
典型的使用MVC的应用程序,在初始屏幕呈现给用户后,其使用情况如下。
- 用户通过点击(输入、触摸等)按钮来触发一个视图。
- 视图将用户的操作告知控制器
- 控制器处理用户输入并与模型进行交互
- 模型执行所有必要的验证和状态变化,并通知控制器应该做什么。
- 控制器指示视图按照模型给出的指示,适当地更新和显示输出。
你可能想知道,为什么控制器部分是必要的?我们就不能跳过它吗?可以,但这样我们就会失去很大的好处:可以在不修改模型的情况下使用多个视图(甚至同时使用,如果我们想这样的话)。为了实现模型和其表现形式之间的解耦,每个视图通常都需要自己的控制器。如果模型直接与特定的视图通信,我们就不能使用多个视图(或者至少,不能以干净和模块化的方式)。
真实世界的例子
MVC是应用于OOP的SoC原则。SoC原则在现实生活中被大量使用。例如,如果你建造一栋新房子,你通常会指派不同的专业人员去做。1)安装水管和电;以及,2)喷漆。
在餐馆里,服务员接受订单并为顾客提供菜肴,但饭菜是由厨师烹制的。
在Web开发中,有几个框架采用了MVC的思想。
- Web2py框架MVC模式的轻量级Python框架。
- Django也是一个MVC框架,尽管它使用不同的命名规则。控制器被称为视图,而视图被称为模板。Django使用的是Model-Template-View(MTV)这个名字。根据Django的设计者,视图描述了用户看到的数据是什么,因此,它使用视图这个名字作为特定URL的Python回调函数。Django中的模板一词是用来将内容和表现形式分开的。它描述了用户是如何看到数据的,而不是看到哪些数据。
应用
MVC是一个非常通用和有用的设计模式。事实上,所有流行的Web框架(Django、Rails和Symfony或Yii)和应用程序框架(iPhone SDK、Android和QT)都使用了MVC或其变体--模型-视图-适配器(MVA)、模型-视图-呈现器(MVP),等等。然而,即使我们不使用这些框架,在我们自己身上实现该模式也是有意义的,因为它提供了以下好处。
- 视图和模型之间的分离使得图形设计师可以专注于UI部分,程序员可以专注于开发,而不会相互干扰。
- 由于视图和模型之间的松散耦合,每个部分都可以被修改/扩展而不影响其他部分。例如,添加一个新的视图是微不足道的。只要为它实现一个新的控制器。
- 每个部分的维护都很容易,因为责任很明确。
当从头开始实现MVC时,要确保你创建了智能模型、瘦控制器和傻瓜视图。
模型被认为是智能的,因为:
- 包含所有的验证/业务规则/逻辑
- 处理应用程序的状态
- 可以访问应用程序的数据(数据库、云等)。
- 不依赖于UI
控制器被认为是瘦的,因为。
- 当用户与视图交互时更新模型
- 当模型发生变化时更新视图
- 如有必要,在向模型/视图传递数据之前对其进行处理
- 不显示数据
- 不直接访问应用程序的数据
- 不包含验证/业务规则/逻辑
视图被认为是傻瓜的,因为:
- 显示数据
- 允许用户与之交互
- 只做最小的处理,通常由模板语言提供(例如,使用简单的变量和循环控制)。
- 不存储任何数据
- 不直接访问应用程序的数据
- 不包含验证/业务规则/逻辑
如果你正在从头开始实施MVC,并想知道你是否做对了,你可以试着回答一些关键问题。
-
应用程序的GUI是否可以换肤?你能多容易地改变它的皮肤/外观和感觉?你能让用户在运行时改变你的应用程序的皮肤吗?如果这并不简单,那就意味着你的MVC实现出了问题。
-
如果你的应用程序没有GUI(例如,如果它是一个终端应用程序),添加GUI支持有多难?或者,如果添加GUI无关紧要,那么添加视图以在图表(饼图、柱状图等)或文档(PDF、电子表格等)中显示结果是否容易?如果这些变化不是很琐碎(只是创建一个新的控制器,并在其上附加一个视图,而不修改模型),那么MVC就没有正确实现。
如果你确保这些条件得到满足,与不使用MVC的应用程序相比,你的应用程序将更加灵活和可维护。
实例
我们用非常简单的例子向你展示如何从头开始实现MVC:报价打印机。用户输入一个数字,看到与该数字相关的报价。报价被存储在元组中,实际应用多存在数据库、文件等,只有模型可以直接访问它。
quotes = (
'A man is not complete until he is married. Then he is finished.',
'As I said before, I never repeat myself.',
'Behind a successful man is an exhausted woman.',
'Black holes really suck...',
'Facts are stubborn things.'
)
class QuoteModel:
def get_quote(self, n):
try:
value = quotes[n]
except IndexError as err:
value = 'Not found!'
return value
class QuoteTerminalView:
def show(self, quote):
print(f'And the quote is: "{quote}"')
def error(self, msg):
print(f'Error: {msg}')
def select_quote(self):
return input('Which quote number would you like to see? ')
class QuoteTerminalController:
def __init__(self):
self.model = QuoteModel()
self.view = QuoteTerminalView()
def run(self):
valid_input = False
while not valid_input:
try:
n = self.view.select_quote()
n = int(n)
valid_input = True
except ValueError as err:
self.view.error(f"Incorrect index '{n}'")
quote = self.model.get_quote(n)
self.view.show(quote)
def main():
controller = QuoteTerminalController()
while True:
controller.run()
if __name__ == '__main__':
main()
这个模型只有一个 get_quote() 方法,根据它的索引 n 返回 quotes 元组的报价 (字符串)。
视图有三个方法:show(),用来在屏幕上打印一个引用(或消息Not found!),error(),用来在屏幕上打印一个错误信息,select_quote(),读取用户的选择。这可以从下面的代码中看出。
控制器做协调工作。init()方法初始化模型和视图。run()方法验证用户给出的报价指数,从模型中获得报价,并将其传回给视图显示,如下代码所示。
最后但同样重要的是,main()函数初始化并启动控制器。