美文网首页
你真的了解MVC吗?

你真的了解MVC吗?

作者: i_苏 | 来源:发表于2017-07-31 15:31 被阅读0次

    MVC

    MVC(Model-View-Controller)是最老牌的的思想,老牌到4人帮的书里把它归成了一种模式,其中Model就是作为数据管理者,View作为数据展示者,Controller作为数据加工者,Model和View又都是由Controller来根据业务需求调配,所以Controller还负担了一个数据流调配的功能。


    在iOS开发领域,我们应当如何进行MVC的划分?

    这里面其实有两个问题
    1、为什么我们会纠结于iOS开发领域中MVC的划分问题?
    
    2、在iOS开发领域中,怎样才算是划分的正确姿势?  
    

    为什么我们会纠结于iOS开发领域中MVC的划分问题?

    关于这个,每个人纠结的点可能不太一样。但请允许我猜一下:是不是因为UIViewController中自带了一个View,且控制了View的整个生命周期(viewDidLoad,viewWillAppear...),而在常识中我们都知道Controller不应该和View有如此紧密的联系,所以才导致大家对划分产生困惑?,下面我会针对这个猜测来给出我的意见。

    在服务端开发领域,Controller和View的交互方式一般都是这样,比如Yii:

     /*
            ...
                数据库取数据
            ...
                处理数据
            ...
        */
    
        // 此处$this就是Controller
        $this->render("plan",array(
            'planList' => $planList,
            'plan_id' => $_GET['id'],
        ));
    

    这里Controller和View之间区分得非常明显,Controller做完自己的事情之后,就把所有关于View的工作交给了页面渲染引擎去做,Controller不会去做任何关于View的事情,包括生成View,这些都由渲染引擎代劳了。这是一个区别,但其实服务端View的概念和Native应用View的概念,真正的区别在于:从概念上严格划分的话,服务端其实根本没有View,拜HTTP协议所赐,我们平时所讨论的View只是用于描述View的字符串(更实质的应该称之为数据),真正的View是浏览器。。

    所以服务端只管生成对View的描述,至于对View的长相,UI事件监听和处理,都是浏览器负责生成和维护的。但是在Native这边来看,原本属于浏览器的任务也逃不掉要自己做。那么这件事情由谁来做最合适?苹果给出的答案是:UIViewController。

    鉴于苹果在这一层做了很多艰苦卓绝的努力,让iOS工程师们不必亲自去实现这些内容。而且,它把所有的功能都放在了UIView上,并且把UIView做成不光可以展示UI,还可以作为容器的一个对象。


    看到这儿你明白了吗?UIView的另一个身份其实是容器!UIViewController中自带的那个view,它的主要任务就是作为一个容器。如果它所有的相关命名都改成ViewContainer,那么代码就会变成这样:

    - (void)viewContainerDidLoad
    {
        [self.viewContainer addSubview:self.label];
        [self.viewContainer addSubview:self.tableView];
        [self.viewContainer addSubview:self.button];
        [self.viewContainer addSubview:self.textField];
    }
    ... ...
    

    仅仅改了个名字,现在是不是感觉清晰了很多?如果再要说详细一点,我们平常所认为的服务端MVC是这样划分的:

                   ---------------------------
                   | C                       |
                   |        Controller       |
                   |                         |
                   ---------------------------
                  /                           \
                 /                             \
                /                               \
    ------------                                 ---------------------
    | M        |                                 | V                 |
    |   Model  |                                 |    Render Engine  |
    |          |                                 |          +        |
    ------------                                 |      HTML Files   |
                                                 ---------------------
    

    但事实上,整套流程的MVC划分是这样:

    ---------------------------
                   | C                       |
                   |   Controller            |
                   |           \             |
                   |           Render Engine |
                   |                 +       |
                   |             HTML Files  |
                   ---------------------------
                  /                           \
                 /                             \ HTML String
                /                               \
    ------------                                 ---------------
    | M        |                                 | V           |
    |   Model  |                                 |    Browser  |
    |          |                                 |             |
    ------------                                 ---------------
    

    由图中可以看出,我们服务端开发在这个概念下,其实只涉及M和C的开发工作,浏览器作为View的容器,负责View的展示和事件的监听。那么对应到iOS客户端的MVC划分上面来,就是这样:

      ----------------------------
                   | C                        |
                   |   Controller             |
                   |           \              |
                   |           View Container |
                   ----------------------------
                  /                            \
                 /                              \
                /                                \
    ------------                                  ----------------------
    | M        |                                  | V                  |
    |   Model  |                                  |    UITableView     |
    |          |                                  |    YourCustomView  |
    ------------                                  |         ...        |
                                                  ----------------------
    

    唯一区别在于,View的容器在服务端,是由Browser负责,在整个网站的流程中,这个容器放在Browser是非常合理的。在iOS客户端,View的容器是由UIViewController中的view负责,我也觉得苹果做的这个选择是非常正确明智的。

    因为浏览器和服务端之间的关系非常松散,而且他们分属于两个不同阵营,服务端将对View的描述生成之后,交给浏览器去负责展示,然而一旦view上有什么事件产生,基本上是很少传递到服务器(也就是所谓的Controller)的(要传也可以:AJAX),都是在浏览器这边把事情都做掉,所以在这种情况下,View容器就适合放在浏览器(V)这边。

    但是在iOS开发领域,虽然也有让View去监听事件的做法,但这种做法非常少,都是把事件回传给Controller,然后Controller再另行调度。所以这时候,View的容器放在Controller就非常合适。Controller可以因为不同事件的产生去很方便地更改容器内容,比如加载失败时,把容器内容换成失败页面的View,无网络时,把容器页面换成无网络的View等等。


    在iOS开发领域中,怎样才算是MVC划分的正确姿势?

    这个问题其实在上面已经解答掉一部分了,那么这个问题的答案就当是对上面问题的一个总结吧。

    M应该做的事:

    给ViewController提供数据
    给ViewController存储数据提供接口
    提供经过抽象的业务基本组件,供Controller调度
    

    C应该做的事:

    管理View Container的生命周期
    负责生成所有的View实例,并放入View Container
    监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。
    

    V应该做的事:

    响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
    界面元素表达
    

    MVCS

    苹果自身就采用的是这种架构思路,从名字也能看出,也是基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分
    是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操作的角度上讲,它拆开的是Controller。
    

    这算是瘦Model的一种方案,瘦Model只是专门用于表达数据,然后存储、数据处理都交给外面的来做。MVCS使用的前
    提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去做。所以对应到MVCS,它在一开始就是拆分
    的Controller。因为Controller做了数据存储的事情,就会变得非常庞大,那么就把Controller专门负责存取数
    据的那部分抽离出来,交给另一个对象去做,这个对象就是Store。这么调整之后,整个结构也就变成了真正意义上的
    MVCS。
    

    关于胖Model和瘦Model

        许多人看见这个是一脸懵逼,根本没听过啊!!!!!!!
        直到现在,知道胖Model和瘦Model的概念的人不是很多。大约两三年前国外业界曾经对此有过非常激烈的讨论,
    主题就是Fat model, skinny controller。现在关于这方面的讨论已经不多了,然而直到今天胖Model和
    瘦Model哪个更好,业界也还没有定论,所以这算是目前业界悬而未解的一个争议。我很少看到国内有讨论这个的资料,
    所以在这里我打算补充一下什么叫胖Model什么叫瘦Model。以及他们的争论来源于何处。
    

    什么叫胖Model?

          胖Model包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不
    用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上。举个例子:
    
    Raw Data:
        timestamp:1234567
    
    FatModel:
        @property (nonatomic, assign) CGFloat timestamp;
        - (NSString *)ymdDateString; // 2015-04-20 15:16
        - (NSString *)gapString; // 3分钟前、1小时前、一天前、2015-3-13 12:34
    
    Controller:
        self.dateLabel.text = [FatModel ymdDateString];
        self.gapLabel.text = [FatModel gapString];
    
       把timestamp转换成具体业务上所需要的字符串,这属于业务代码,算是弱业务。FatModel做了这些弱业务之后,
    Controller就能变得非常skinny,Controller只需要关注强业务代码就行了。众所周知,强业务变动的可能性要
    比弱业务大得多,弱业务相对稳定,所以弱业务塞进Model里面是没问题的。另一方面,弱业务重复出现的频率要大于
    强业务,对复用性的要求更高,如果这部分业务写在Controller,类似的代码会洒得到处都是,一旦弱业务有修改
    (弱业务修改频率低不代表就没有修改),这个事情就是一个灾难。如果塞到Model里面去,改一处很多地方就能跟着
    改,就能避免这场灾难。
      然而其缺点就在于,胖Model相对比较难移植,虽然只是包含弱业务,但好歹也是业务,迁移的时候很容易拔出萝卜
    带出泥。另外一点,MVC的架构思想更加倾向于Model是一个Layer,而不是一个Object,不应该把一个Layer应该做
    的事情交给一个Object去做。最后一点,软件是会成长的,FatModel很有可能随着软件的成长越来越Fat,最终难以
    维护。
    

    什么叫瘦Model?

      瘦Model只负责业务数据的表达,所有业务无论强弱一律扔到Controller。瘦Model要达到的目的是,尽一切可能
    去编写细粒度Model,然后配套各种helper类或方法来对弱业务做抽象,强业务依旧交给Controller。举个例子:
    
    Raw Data:
    {
        "name":"casa",
        "sex":"male",
    }
    
    SlimModel:
        @property (nonatomic, strong) NSString *name;
        @property (nonatomic, strong) NSString *sex;
    
    Helper:
        #define Male 1;
        #define Female 0;
        + (BOOL)sexWithString:(NSString *)sex;
    
    Controller:
        if ([Helper sexWithString:SlimModel.sex] == Male) {
            ...
        }
    
      由于SlimModel跟业务完全无关,它的数据可以交给任何一个能处理它数据的Helper或其他的对象,来完成业务。
    在代码迁移的时候独立性很强,很少会出现拔出萝卜带出泥的情况。另外,由于SlimModel只是数据表达,对它进行维
    护基本上是0成本,软件膨胀得再厉害,SlimModel也不会大到哪儿去。
      缺点就在于,Helper这种做法也不见得很好,这里有一篇[文章](http://nicksda.apotomo.de/2011/10/rails-misapprehensions-helpers-are-shit/)
    批判了这个事情。另外,由于Model的操作会出现在各种地方,SlimModel在一定程度上违背了DRY
    (Don't Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出现代码膨胀。
    
      话说回来,MVCS是基于瘦Model的一种架构思路,把原本Model要做的很多事情中的其中一部分关于数据存储的代码
    抽象成了Store,在一定程度上降低了Controller的压力。
    

    相关文章

      网友评论

          本文标题:你真的了解MVC吗?

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