美文网首页
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