美文网首页
IOS架构模式MVC、MVP、MVVM

IOS架构模式MVC、MVP、MVVM

作者: vicentwyh | 来源:发表于2018-06-08 11:15 被阅读0次

软件架构的定义?它的存在意义是什么?

软件架构是一系列相关的抽象模式,用于指导软件系统各个方面的设计。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。

良好的软件架构具有许多的优点:可靠性、安全性、可扩展性、可维护性

软件开发中存在这许多已经相当成熟的和广泛应用的架构模式MVC、MVP、MVVM、VIPER等

什么是MVC

MVC全名是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

M:Model(模型)- 业务数据存储

V:View(视图) - 数据展示以及和用户进行交互

C:Controller (控制器)- 协调Model和View。处理View上的操作,进行相应业务处理,并将结果数据更新到Model上,同时将更改的信息返回到View上展示。

MVC优点?缺点?

优点:

1、低耦合性

视图层和业务层分离,这样就允许更改视图层代码而不用重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变只需要改动MVC的模型层即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则

2、高易用性、可适用性和复用性

其高度的简易性,铸就了其高度的广泛适用性。分离了业务逻辑的数据模型和视图模型又可重复利用。

缺点:

1、职责划分无法保证

在传统的MVC中,MVC更倾向于一种Observer, Composite和Strategy组成的模式,如下

image

View和model是绑定在一起的,这为View绕过Controller而直接对Model进行操作提供了便利,而这样做的结果就是用做展示和交互职责的View不可避免的承担了部分业务逻辑,从而导致了View的职责模糊化。而这部分逻辑又是View基于Model定制的,进而导致了View和Model的耦合性增强。至此Model、View、Controller三者相互都有通信,紧密耦合,不可避免的大大降低了三者的复用性。

    import UIKit
    
    class Person {
        let name: String
        var greeting: String?
        
        init(_ name: String, greetingText greeting: String?) {
            self.name = name
            self.greeting = greeting
        }
    }
    
    class TableViewController: UITableViewController {
        
        var person: Person!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            person = Person("Tom", greetingText: "Hello word")
            tableView.reloadData()
        }
        
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1
        }
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "inputCellReuseId", for: indexPath) as! TableViewInputCell
            cell.delegate = self
            cell.textfield.text = person.greeting
            return cell
        }
    }
    
    extension TableViewController: TableViewInputCellDelegate{
        func inputChange(cell: TableViewInputCell, text: String) {
            title = person.name + " " + (person.greeting ?? " ")
        }
    }
    
    protocol TableViewInputCellDelegate: class {
        func inputChange(cell: TableViewInputCell, text: String)
    }
    
    class TableViewInputCell: UITableViewCell, UITextFieldDelegate {
        
        @IBOutlet weak var textfield: UITextField!
        weak var delegate: TableViewInputCellDelegate?
            
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            person?.greeting = textField.text
            delegate?.inputChange(cell: self, text: textField.text ?? "")
            return true
        }
    }

以上是个简单的例子,当输入变化时,title随之变化。

cell中,当输入变化时直接修改了person中greeting字符串,这就使得cell包含了输入所对应的部分业务,从而导致view的职责模糊化,不单单只是展示数据。

2、维护性差

Controller作为承担胶水代码和业务逻辑部分,开发者将大量代码扔到其中用于协调View和Model,View的最大的任务就是向Controller传递用户动作事件,Controller最终会承担一切代理和数据源的职责,还负责一些分发和取消网络请求以及一些其他的任务,在Controller规模尚小时没有太大问题,但随着版本和业务的迭代,造成Controller逻辑复杂,持续这样下去必定将导致Model View Controller 变成Massive View Controller,代码一天天的烂下去,直到没人敢碰。

苹果开发人员认为View和Model应该是最有效的可复用的部分苹果看MVC,因而传统的MVC并不能满足苹果的期望

苹果期望的MVC运行模式模型如下:


1.png

现在基于Cocoa MVC 对以上的例子做修改

    extension TableViewController: TableViewInputCellDelegate{
        func inputChange(cell: TableViewInputCell, text: String) {
            person?.greeting = text
            title = person.name + " " + (person.greeting ?? " ")
        }
    }
    
    class TableViewInputCell: UITableViewCell, UITextFieldDelegate {
        
        //. . .
        
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            delegate?.inputChange(cell: self, text: textField.text ?? "")
            return true
        }
    }

此时cell内部不在对person数据进行操作,它将用户动作事件传递给delegate即Controller,由其进行相应业务处理。

而MVP架构模式与此十分相似

3、难以测试

Controller混杂了各种视图处理逻辑和业务逻辑,且分散在不同的地方,分离这些成分的单元测试变为一个艰巨的任务


MVC的优化MVP解决的什么问题?

M:Model(模型)- 业务数据存储

V:View / Controller(视图) - 数据展示以及和用户进行交互

P:Presenter (协调器)- 协调Model和View。接受View传递的交互事件,进行相应业务处理,并将结果数据更新到Model上,最后对View进行更新操作。


2.png

对比MVC,在MVP中Presenter替代了Controller的作用,由Presenter进行数据源操作,并更新View的数据和状态,以及其他业务逻辑。它将所有model相关的任务(包括更新model,观察变更,将model变形为可以显示的形式等)从controller层抽离出来,放到Presenter对象中,并在数据更新后调用view的更新接口。

创建Presenter,提取Model处理逻辑和View状态更新代码

    protocol GreetingView: class {
        func setGreeting(_ greeting: String)
    }
    
    protocol GreetingPresenterCompatible {
        init(_ view: GreetingView, person: Person)
        func showGreeting()
    }
    
    class GreetingPresenter: GreetingPresenterCompatible {
        
        unowned let view: GreetingView
        let person: Person
        
        var greetingText: String? {
            return person.greeting
        }
        
        required init(_ view: GreetingView, person: Person) {
            self.view = view
            self.person = person
        }
        func changeGreeting(_ text: String?) {
            person.greeting = text
            showGreeting()
        }
        func showGreeting() {
            // 对View进行相应的操作
        }
    }

如此,对model的数据操作就完全包含在了Presenter中,再来看看TableViewController的变化

    class TableViewController: UITableViewController {
        
        var presenter: GreetingPresenter!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let person = Person("Tom", greetingText: "Hello word")
            presenter = GreetingPresenter(self, person: person)
            tableView.()
        }
        
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            // ……
            cell.textfield.text = presenter.greetingText
            return cell
        }
    }
    
    extension TableViewController: TableViewInputCellDelegate{
        func inputChange(cell: TableViewInputCell, text: String) {
            presenter.changeGreeting(text)
        }
    }

对model的数据源操作后,需要更新View,补全GreetingPresenter中对View处理函数:

    func showGreeting() {
            let greeting = person.name + " " + (person.greeting ?? " ")
            self.view.setGreeting(greeting)
}

TableViewController进行数据展示:

extension TableViewController: GreetingView {
    func setGreeting(_ greeting: String) {
        title = greeting
    }
}

这样一来,Presenter就完成了M、V、C三者的协调整合。

MVP的好处?

1、剥离了Controller的逻辑处理代码,实现了减负作用。

2、View不直接接触Model的数据,而由Presenting间接获取,隐藏了数据,降低了View和Model的耦合性,提高维护性和可复用性

3、利于测试驱动开发

MVP的弊端?

1、一个Presenter只能持有一个View,而在一个Controller中可能存在多个Presenter,这就不可避免的导致代码量的增加。

2、对视图的更新操作放在Presenter中,导致View和Presenter的交互过于频繁,继而使得Presenter与特定试图的联系过于紧密,一旦视图需要变更,那么Presenter也需要变更了


MVC的最终优化MVVM。

M:Model(模型)- 业务数据存储

V:View / Controller(视图) - 数据展示以及和用户进行交互

VM:ViewModel(视图模型)- 协调Model和View。处理View上的操作,进行相应业务处理,并将结果数据更新到Model上,同时将信息变化回馈给View,由View进行相应的数据展示。

某种意义上讲MVVM和MVP非常类似:

将View和Controller作为View层级;

将所有model相关的任务(包括更新model,观察变更,将model变形为可以显示的形式等)从controller层抽离;

解耦View和Model的联系,协调Model和View;

区别在于:

相比Presenter,VM不再持有View,对View的更新操作交由View自身处理。当数据发生变化时,View-Model回馈给View一个数据产生变化的信号,而view在接收到这个信号后将执行与信号绑定的相关更新操作。因此进一步解耦View和View-Model,明确View-Model自身的职责。
MVVM模型图:


3.png

现在让我们MVVM模式下代码是如何实现的
创建ViewModel,存储Model,以及给定参数修改的函数,它叫做GreetingViewModel:

class GreetingViewModel {
    private var person: Person
    
    var subscribe: ((_ greeting: Person) -> Void)?

    // 提供函数进行相应的逻辑操作,这里只是修改greeting,就简单操作了
    var greeting: String? {
        get {
            return person.greeting
        }
        set {
            guard newValue != person.greeting else {
                return
            }
            person.greeting = newValue
            subscribe?(person)
        }
    }
    
    init(person: Person) {
        self.person = person
    }
    // 订阅数据的变化
    func subscribe(_ handler: @escaping (Person) -> Void) {
        self.subscribe = handler
    }
    
    func unSubcribe() {
        self.subscribe = nil
    }

    var sectionNums: Int {
        return 1;
     }
        
    var sectionRows: Int {
        return 1;
    }
    
}

在 TableViewController 增加一个 GreetingViewModel 变量,并初始化它:

class MVVMTableViewController: UITableViewController {

    var viewModel: GreetingViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let person = Person(name: "Hello", greeting: nil)
        viewModel = GreetingViewModel(person: person)
        
        // 订阅viewModel,每次 viewModel 状态改变时,greetingDidChange 都将被调用
        viewModel.subscribe { [weak self] person in
            self?.greetingDidChange(person: person)
        }
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return viewModel.sectionNums
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.rowNums
    }

    func greetingDidChange(person: Person) {
        title = person.name + " " + (person.greeting ?? " ")
    }

    // ...

}

extension MVVMTableViewController: TableViewInputCellDelegate {
    func inputChange(cell: TableViewInputCell, text: String) {
        viewModel.greeting = text
    }
}

MVVM优点:

1、双向绑定技术
当model放生变化时,管理model的View-Model会自自动更新,继而产生数据变化信号令View自动变化,很好的做到了数据的一致性。

2、为Controller实现了减负瘦身功能
将所有model相关的任务(包括更新model,观察变更,将model变形为可以显示的形式等)从controller层抽离出来,放到view-model中

3、方便测试、易维护性
解耦View和Model的联系并抽离业务逻辑类view-model,view无需关系实际的业务逻辑实现,而只承担视图展示和交互传递,易测试修改。而承担业务逻辑的view-model在屏蔽了业务逻辑的同时同样无需关系视图展示易测试。至此其可测试性侧面说明了其易可维护行。

缺点:

不可避免的导致类的增加,以及项目构造的复杂性和代码量增加

实际开发中MVVM经常和一个FRP响应式框架结合使用(例如ReactiveCocoa 或者 ReSwift),快速的将信号和与之相关的操作绑定。


总结:

不论使用哪种软件设计架构,本质都是为了更清晰地管理"用户操作,模型变更,UI反馈"这一数据流动的方式,做到:低耦合,职责明确,提高可复用行、可维护性,可扩展性,而不仅仅只是简单的将实现代码换一个位置存放。

个人能力有限,若有错误还望指出。

相关文章

网友评论

      本文标题:IOS架构模式MVC、MVP、MVVM

      本文链接:https://www.haomeiwen.com/subject/nipusftx.html