MV系列模式 & Viper

692 阅读13分钟

前言

日常开发中总是会遇到如何设计项目的框架模式问题,我们都知道MVC、MVP、MVVM这些的常用的设计模式。但是对于他们之间的联系和区别却不是非常的清晰。所以个人总结平时的开发经验和阅读积累,对他们在此做简单的整理和并列举几个简单的例子。

一、什么是设计模式

在软件工程中,设计模式(design pattern)是对软件设计中普遍存(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。 设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

设计模式旨在帮助开发者编写易于理解和重用的代码模块。

二、为什么在乎架构的选择?

因为如果你不这样做,终有一天,你在调试一个拥有着数十个不同方法和变量(things)的庞大的类文件时,你会发现你无法找到并修复此文件中的任何问题。自然地,也很难把这个类文件当做一个整体而熟稔于心,这样你可能总是会错过一些重要的细节。

一个好的架构应该有的特点:

  1. 能把代码职责均衡的解耦到不同的功能类里
  2. 良好的可测性
  3. 易用、维护成本低

三、几种设计模式

App的工作内容大致可分为以下内容:

  1. 数据业务:数据层面业务,比如数据请求、数据计算、数据持久化 等工作
  2. UI业务: 视图界面层业务,根据数据处理页面的展示、动画等工作
  3. 其他: 页面路由、用户交互处理。。。等工作

MV系列框架

MV系列的设计框架是我们平时经常接触到的。有以下三种:MVC(M-V-C)、MVP(M-V-P)、MVVM(M-V-VM)。

注意我在小括号中的表示,基本表示了MV系列框架设计中模块功能的区分 和 他们之间的区别。

在 MV 系列框架中,M 和 V 指 Model 层和 View 层,但是其功能会因为框架的不同而变化:

  • M (Model) 模型层 : 用于用于数据存储和数据访问
  • V (View) 视图层 :用于展示界面的具体表现/接收用户操作
  • C(Controller)/P(Presenter)/MV(ViewModel) : 模型(Model)和视图(View)的粘合剂、中介,通常的负责处理用户在视图(View)上的操作响应和具体业务逻辑处理然后通知模型(Model)更新、并通过模型的改变来更新视图(View)。
1. MVC框架

MVC 框架是 MVC、MVP、MVVM 这3个框架中历史最悠久的。MVC和MVVM都是在此基础上进行改良和演变的。 在框架的的改良和发展过程中,MVC框架也有各自不同的变化和差异。下面是我整理各个MVC特点和差异。

·基本MVC模式

图1 如上图所示

  1. Model:模型管理应用程序的数据,响应有关其状态信息(通常来自View)的请求,并响应指令以更改状态(通常来自Controller)。
  2. View:视图管理数据的展示。
  3. Controller:控制器解释用户的输入,并通知模型、视图进行状态更新。

其中,View和Controller依赖于Model,而Model并不依赖于View和Controller。这种设计模式的优点在于允许Model不受View的影响,从而能够进行独立的构建和测试。

此外,根据Model在数据变化是是否主动通知视图更新变化 可分为:主动型Model、被动型Model。

·被动型Model MVC模式

当只有一个Controller操控着Model时可以采用被动型Model。Controller定义Model,并在Model发生改变时通知View,后者再进行更新。在这种场景下,Model完全独立于View和Controller。实际上,被动型Model MVC模式就是基本的MVC模式。

·主动型Model MVC模式

当Model的状态未受Controller干扰的情况下发生变化时,使用主动型Model。当其他来源正在更改数据并且必须立刻反应到View中时,可能会发生这种情况。 为了实现主动型Model,通常使用Observer模式来提供了一种机制来提醒其他对象的状态变化,避免引入依赖关系。各个View实现Observer接口并向Model注册。当Model发生变化时,Model会遍历所有注册的观察者并通知他们相关的变化。这种方法通常被称为“发布 - 订阅”。Model从不需要关于任何View的任何信息。事实上,在Controller需要被告知Model变化的情况下(例如,启用或禁用菜单选项),所有Controller必须通过实现Observer接口并订阅Model的变化。 具体结构如下图所示: 图2

·传统MVC模式

传统MVC模式如图所示(实线表示调用,虚线表示通知)

上图所示为传统MVC设计模式,其通过Composition、Strategy、Observer等基本设计模式协同工作以实现。用户操作在复合结构的某个层次上操作View,生成一个事件。Controller接收事件,并进行解释。这个过程使用Strategy模式实现,可以是通过消息请求一个Model对象来更新其状态或请求一个View对象来更新其行为或外观。Model对象则在其状态改变时通知所有已注册为观察者的对象。如果观察者是对象,则可以相应更新其外观。

·苹果的MVC

苹果认为传统的MVC模式中,View通过Observer模式直接观察Model对象以获取相关的通知,而这样的设计会导致View和Model对象不能被广泛复用,因为View与其观察的Model之间存在耦合关系。因此,苹果版MVC与传统MVC基本一致,只是隔离了View和Model。

但是在实际的开发使用中视图View和控制Controller往往是一一对应的,控制器拥有视图并控制视图的展示和生命周期 所以实际上ios的情况如下图所示:

随着开发的过程中,我们的业务需求不断增加,控制器上的视图功能不断增加,此时控制器不仅仅页面路由还要负责处理视图的UI逻辑和数据业务逻辑,视图控制器代码变得过于臃肿切难以维护。因此,开发过程中给视图控制器"瘦身"(View Controller offloading)成了iOS开发者中的一个重要话题。

MVC 框架流程:

实例代码
// Model 数据模型
struct PersonModel { 
	let name: String
	let age: String
}

//  View 展示视图
class PresnetView:UIView{
    let content = UILabel();
}

// Controller 控制器 
// 在OC中 UIViewController 实际上是View+Controller 两者是绑定在一起的。此处为了区分理解 V 和 C 这里暂且看成没有视图的控制器
class Controller: UIViewController {
	var person: PersonModel!
    var pView: PresnetView!
	let clickButton = UIButton()
	
	override func viewDidLoad() {
		super.viewDidLoad()
		self.clickButton.addTarget(self, action: "didTapButton:", forControlEvent: .TouchUpInside)
	}
    
    //在此处处理用户的点击逻辑  访问PersonModel的数据 处理后 用View显示处理结果
	func didTapButton(button: UIButton) {
		let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
		self.pView.content.text = greeting
	}
	
	// 布局代码在这儿
	......	
}

// 组合MVC
let model = PersonModel(name: "数据模型", age: "20")
let view = PresentView()
let controller = Controller()
//控制器成为V和Model建的沟通桥梁
Controller.person = model;
Controller.pView = view;

2. MVP

MVP(Modell-View-Presenter)模式就是为了解决MVC中Controller越来越臃肿的问题,进一步明确代码分工。MVP与苹果版MVC非常相似,最大区别就是Controller不再处理繁重的页面UI和业务逻辑,而是把它们交给了Presenter处理。

上面图解中的MVP框架和苹果中预期设计的MVC框架几乎没什么差别,区别只在于处理UI和业务逻辑的地方不同。MVC中View和Controller是紧密耦合的,然而,MVP的中介——Presenter与View Controller的生命周期没有任何关系,并且很容易模拟View,所以Presenter工作就是将原本MVC控制器中与数据处理和用户操作逻辑处理抽离出来进行单独的处理而不关心界面布局和视图的生命周期

实例代码

// Model 数据模型
struct PersonModel { 
	let name: String
	let age: String
}

//  View 展示视图
class PresnetView:UIView{
    let content = UILabel();
}

// Presenter 处理视图
class ViewPresenter:NSObject{
    let view : PresentView
    let person : PersonModel
    init(view: PresentView,person :PersonModel){
        self.view = view;
        self.person = person;
    }
    //处理用户响应逻辑
    func dealClickAction(){
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
		self.pView.content.text = greeting
    }
}

// Controller 控制器 
// 在OC中 UIViewController 实际上是View+Controller 两者是绑定在一起的。此处为了区分理解 V 和 C 这里暂且看成没有视图的控制器
class Controller: UIViewController {
    var pView: PresnetView!
	var presenter : ViewPresenter!
	let clickButton = UIButton()
	
	override func viewDidLoad() {
		super.viewDidLoad()
		self.clickButton.addTarget(self, action: "didTapButton:", forControlEvent: .TouchUpInside)
	}
    
    //在此处处理用户的点击逻辑  将用户响应交给Presenter处理
	func didTapButton(button: UIButton) {
		self.presenter.dealClickAction();
	}
	
	// 布局代码在这儿
	......	
}

// 组合MVP
let model = PersonModel(name: "数据模型", age: "20")
let view = PresentView()
//创建Presenter并绑定视图和模型
let presenter = ViewPresenter(view:view,person:model);
let controller = Controller()
Controller.pView = view;
controller.presenter = presenter

3. MVVM

在上面的MVP模式中虽然Controller工作量得到了有效的减负,而且我们发现Presenter生成的时候需要将对应的视图View和数据Model绑定到自身上面。这样看起来没什么关系,但是在项目开发和维护过程中难免会对之前开发的界面和数据进行修改,View页面改变时,相应绑定的Presenter可能也要修改。这样对于一些大型项目切业务逻辑比较复杂的项目,维护成本也随开发进行而增加。而且随着页面内容元素的增加,Presenter也要面临之前MVC中Controller代码臃肿的问题。为了解除View和Presenter之间的绑定和代码后期臃肿问题,MVVM应运而生。 MVVM模式用,用ViewModel来代替Presenter中页面与数据的交互的工作部分,而页面的UI的工作则交由View自身负责(此前页面的工作代码可能做简单的页面创建和用户响应并不负责UI业务逻辑处理)。ViewModel不再绑定View,而是由View持有ViewModel。这样不仅解除了绑定,也解决了ViewModle中因处理UI逻辑而变得代码臃肿的问题。不仅如此,因为ViewModel不在处理UI逻辑,修改View代码不会对ViewModel产生影响所以它可以与View实现解耦,从而绑定到不同的View上实现了数据业务层代码复用。

实例代码

// Model 数据模型
struct PersonModel { 
	let name: String
	let age: String
}

// ViewModel 视图模型
class ViewModel:NSObject{
    let person : PersonModel
    init(person :PersonModel){
        self.view = view;
        self.person = person;
    }
    //处理业务数据
    func sayHello() -> String{
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        return greeting;
    }
    //处理业务数据
    func askAge()-> String{
        let ageString = "MyAgeIs:"+ person.age;
        return ageString;
    }
}

//  View 展示视图
class PresnetView:UIView{
    let content = UILabel();
    let clickButton = UIButton()
    var viewModelObj : ViewModel;
    init(viewmodel:ViewModel){
        self.viewModelObj = viewModel;
        self.clickButton.addTarget(self, action: "didTapButton:", forControlEvent: .TouchUpInside);
    }
    
    //在此处处理用户的点击逻辑  处理UI逻辑
	func didTapButton(button: UIButton) {
        let text = self.viewModelObjc.sayHello();
        //let text = self.viewModelObjc.askAge();
        self.content.text = text;
	}
}



// Controller 控制器 
// 在OC中 UIViewController 实际上是View+Controller 两者是绑定在一起的。此处为了区分理解 V 和 C 这里暂且看成没有视图的控制器
class Controller: UIViewController {
    var pView: PresnetView!
	
	override func viewDidLoad() {
		super.viewDidLoad()
	}
	// 布局代码在这儿
	......	
}

// 组合MVVM
// 视图持有视图模型,视图模型持有数据模型
// view < viewModel < Model 
let model = PersonModel(name: "数据模型", age: "20");
let viewModel = ViewModel(person:model);
let view = PresentView(viewModel:viewModel);

let controller = Controller();
controller.pView = view;

Viper模式

VIPER的全称是View-Interactor-Presenter-Entity-Router。示意图如下:

相比之前的MVX架构,VIPER多出了两个东西:Interactor(交互器)和Router(路由)。

各部分职责
  1. View
    • 提供完整的视图,负责视图的组合、布局、更新
    • 向Presenter提供更新视图的接口
    • 将View相关的事件发送给Presenter
  2. Presenter
    • 接收并处理来自View的事件
    • 向Interactor请求调用业务逻辑
    • 向Interactor提供View中的数据
    • 接收并处理来自Interactor的数据回调事件
    • 通知View进行更新操作
    • 通过Router跳转到其他View
  3. Router
    • 提供View之间的跳转功能,减少了模块间的耦合
    • 初始化VIPER的各个模块
  4. Interactor
    • 维护主要的业务逻辑功能,向Presenter提供现有的业务用例
    • 维护、获取、更新Entity
    • 当有业务相关的事件发生时,处理事件,并通知Presenter
  5. Entity
    • 和Model一样的数据模型
和MVX的区别

VIPER把MVC中的Controller进一步拆分成了Presenter、Router和Interactor。和MVP中负责业务逻辑的Presenter不同,VIPER的Presenter的主要工作是在View和Interactor之间传递事件,并管理一些View的展示逻辑,主要的业务逻辑实现代码都放在了Interactor里。Interactor的设计里提出了"用例"的概念,也就是把每一个会出现的业务流程封装好,这样可测试性会大大提高。而Router则进一步解决了不同模块之间的耦合。所以,VIPER和上面几个MVX相比,多总结出了几个需要维护的东西:

  • View事件管理
  • 数据事件管理
  • 事件和业务的转化
  • 总结每个业务用例
  • 模块内分层隔离
  • 模块间通信

而这里面,还可以进一步细分一些职责。VIPER实际上已经把Controller的概念淡化了,这拆分出来的几个部分,都有很明确的单一职责,有些部分之间是完全隔绝的,在开发时就应该清晰地区分它们各自的职责,而不是将它们视为一个Controller。

总结

  1. MV系列模式中,MVC是所有MV系列中历史最久的也是其他MV系列发展的基础。
  2. MVP为了解决MVC中控制器的臃肿问题,由MVP中的Presenter绑定响应的View和Model来分担Controller中的部分工作。
  3. MVVM又为了解决MVP的Presenter视图绑定问题,使用了ViewModel来代替Presenter实现视图解绑和代码的复用。

以上 就是我个人的简单理解,如有勘误不吝赐教,谢谢!^v^

参考

iOS面试题:MVVM和MVC的区别 mvc mvp mvvm的区别与联系_iOS开发MVC到MVP再到MVVM的演化 [译]iOS架构模式——解密MVC、MVP、MVVM和VIPER iOS VIPER架构实践