从MVC到MVP,记我的两次项目重构实战经历

从MVC到MVP,记我的两次项目重构实战经历

一、前言

最近交流群里或者知乎上看到大家在问一个问题:我们的app该不该用MVP?或者在问MVC\MVP\MVVM之类哪个好用以及重构之类的问题。网络上对于MVC\MVP\MVVM的介绍的文档有很多,官方也有Demo可以参考学习,所以本文不细讲MVP的知识,只是讲述我的项目重构体会。重构的第一个项目相对较大,历史也比较悠久,在代码里边偶尔能看到13年的记录,也经过了无数人的手。这个版本的迭代从五月初开始,共花了两个月,其中包括交互和UI的大改版,再此过程中感谢我的同事浪平哥的指导。第二个重构的项目是录音,项目相对小,总共花的时间大约是一到两周,这次由我独立完成。鄙人愚钝,不足之处,敬请赐教。

二、认识MVP

发现Android出现的一些新技术会在各种博客、微信公众号瞬间扩散,在各大网站也会有更新。MVP已经在很久前出现了,有幸有机会能够在项目中实战,网上有许多的教程,还有官方的Demo,在此就不做深入分析了,仅谈一谈自己的理解。

1、MVC和MVP

MVC是我们之前的开发中一直用的开发模式,这种开发模式结构简单,开发速度快,代码量少。但是View和Contrl基本都在Activity中完成,造成项目中的Activity干的事情太多,逻辑混乱,可读性差。数据和UI纠结在一起,在迭代过程中不好修改。就像拉着一辆破车跑,实在是让人举步维艰。MVP的优势在于让数据和UI分离,V层只管UI的显示操作,M层按照业务划分,根据不同业务有对应不同的model完成网络访问、数据库读取等所欲的数据操作。P层是一个协调者的角色,他将从M层拿到数据,并协调分配给不同的UI,完成在界面上指定控件显示指定的数据。这样分工更加明确,业务之间耦合少,方便修改。

2、MVP最简模型

  • Entity

    public class Monkey(){
        private name;
        set***
        get***
    }
    
  • M层

    public class Model{

    public Monkey getData(){
        Monkey monkey=new Monkey();
        //也许是访问数据库或者网络等复杂的操作
        monkey.setName("code");
        return monkey;
    }
    

    }

  • P层

    public class Presenter{
        private IView mIView;
        private Model mModel;
        public Presenter(){
            mModel=new Model();//初始化一个model
        }
        public attachView(IView view){
            mIView=view;
        }
        public void showView(){
            String name=mModel.getData().getName();//通过业务层获取数据
            mIView.show(name);//在view中显示
        }
    }
    
  • V层

    public interface IView{
        void show(String name);
    }
    
    public class TestActivity extends Activity implements IView{
        private TextView mTextview;//UI
        private Presenter mPresenter;
    
    
        protect void onCreate(){
            mTextView=(TextView)findeById(R.id.xxx);
            mPrsenter=new Presenter();
            mPrsenter.attchView(this);
            mPrsennter.showView();
        }
        public void show(String name){
            mTextView.setText(name);
        }
    
    }
    

    上述还有许多就没有表现出来,比如当Activity销毁的时候,应该调用Present的一个Destory的方法销毁P层,同样的在M层中也要写一个destory的方法供P层调用进行销毁操作,这里一可以避免数据混乱,二也是清除缓存数据,释放内存,并进行必要的销毁操作。还有比如,我们大部分复杂一点的UI控件的显示或者隐藏等和数据有关,这个时候我们就要把基础的数据通过P层交给UI,将与UI相关的数据暴露在Activity中,这Activiy中进行判断等操作。如果需要进行异步加载数据,可以采用回调的方式将数据返回到P层,P层再处理V层视图的显示,这样我们在V层就不用管是不是异步这个问题啦;

三、重构之路

1、重构之前:原代码分析

重构前接手项目有一两个月的时间,紧张的迭代进度并没有给我许多的时间去熟悉代码,中间由于项目的迭代计划出现一些问题,这个空余的时间给了我熟悉代码的机会,然而到后边真正重构的时候才发现做了许多的无用功。

  • 快速熟悉代码的方法
    • 首先从UI分析,可以用逻辑结构图画出来,每个模块有哪些界面,这些界面对应的Activity,fragment是哪个,以便后边看一目了然;
    • 使用Debug调试,在有操作的等关键的地方打好点,一步步调试,看看程序是怎么跑的;
    • 再烂的代码都会有他的套路,而且这个套路运用在整个程序各个地方的开发之中,我经历的项目并不是很多,但是这个问题想想也能确定。所以,如果你对这个套路搞清楚了,这个程序在你的面前就会变得透明的,是一堆骨架。
  • 需要输出的文档
    • 功能结构图,也就是上面说的UI界面对应的Acitivty和Fragment;
    • 数据加载分析文档,每个软件必定会牵涉到数据,我这里的数据加载不是网络或者数据库的原始数据,而是程序运行时数据的读取、传递、计算等缓存的数据。我用的是用表格的形式,填写了某一个实体类,实体类中包含的数据元素以及值。不过后来发现,这里不要弄的太详细,这里边的数据太复杂了。不要把太多的时间放在这里,主要的目的在于把数据加载的过程了解清楚,重构的时候难免要动这一块,不过真正动的时候再用Debug调试了解详细信息就好了。

2、重构之中:我的发现

  • 写好基础Model业务层

    在前期的准备工作中,分析得出某一些Model层业务是几乎所有Model层公有的,这个时候我们要将一些公有的业务写到一个基类里边。每个软件都会有不同的模块,然后我们可以在不同的模块也写一个基类,然后在具体的业务层的model中继承这个基类就可以了,这样节省了许多的代码,也将业务分的更加清晰。

  • 分拆原有工具类方法到Model层

    在MVC模式的开发中,我们避免Activity中的代码过多,常常将某一些共有的操作放到一个工具类中,比如数据库的读取,然后不同的Activity有不同的操作,这些都写在一个工具类中,我们不好分辨谁是谁的。这些活都是由model层来干的,我们可以通过Ctrl+G搜索这个工具类的每一个方法被谁调用了,然后分别将这些方法copy到对应的model层去。

  • 除了 if 记得也要写好 else

    发现以前的代码里边有许多容错处理,比如常常做的是 if 某某某不等于null,然后才进行什么操作,但是else就不管了,这样出了问题好难查,如果我们程序写的时候就在else 中抛出一个异常或者打一个log说明这种情况什么为null,出的什么问题。这样bug来了,找到问题也就是分分钟的事情。为什么敢去重构代码,这是一个高风险的活,因为我在每个可能存在问题的地方都有Log说明,问题来了也好找。

  • 从不知何处下手到感觉这只是一个套路

    我的同事已经将一个独立的新模块完全用MVP模式写的,为了学习MVP模式,我这个模块写了一个demo,深刻觉得当无从下手的时候一定要让自己动手。刚进行重构的时候,都不清楚该怎么写model,怎么写presenter,但是经过一段时间的改造,已经觉得没有那么有挑战性了,我只是将代码的逻辑结构进行了一些调整,基本写的是一些框架性的东西,然后将原有的代码copy进去,实现具体的细节。写到后边都觉得自己只是在进行一些体力劳动,也许是项目时间太紧,我没有时间去优化,写的过程中也感觉到有些地方还待改进。说的这么多,主要是想说,重构不难,难在开头。

3、重构之后:个人体会

  • 做好准备,把握时机

    相信大多数的项目都被产品经理们一个迭代一个迭代的催着跑,这个时候哪里还有什么时间去做这么大的调整。目前我手上的接的一个新项目大约有一月多,产品经理还总是喜欢要我们做出各种体验版本。这个时候我们的View和Data是搅在一起的,做起来就越来越很吃力。由于对于原来的代码逻辑不够熟悉,发现改一个地方会有许多隐藏的问题出现。那我现在在做什么准备呢?我在分析项目的逻辑,画逻辑框图。必须要说明一点就是:逻辑框图不管之前有没有,一定自己画,画出来的是图,但实际是自身熟悉项目的一个过程。接下来,就可以准备分拆逐步重构了。时机在哪里?就在产品经理们迷茫不烦我们的时候,当然,还有每天的下午7:00之后。如果你对自己项目非常熟悉,那就可以马上开工啦。

  • 不要心急,逐步重构

    毕竟项目还处在迭代的计划中,产品不可能让我一直整代码,所以这是一个逐步完成的工作,也许是我们原代码实在太散了,得花点时间才动的了。不过此次重构也只是完成了大部分的模块。所以重构也不是把代码翻个底朝天,虽然我再做的时候有些地方实在无法忍受,给改造了。但是也得考虑时间已经风险,所以要注意要一步一步的蚕食,别大口吃,最后搞不完留一堆碎渣子。路漫漫其修远兮,哈哈!

  • 疑难bug自解

    可能在重构之前,尤其是动大刀子之前,心里不免有个疑问。项目逻辑很复杂,有很多的疑难BUG都是熬着夜才改过来的。现在这么大的改动,会不会出一堆的BUG。以我的实践经历来回答这个问题,接手的第一个大项目确确实实有许多的bug,而且出的莫名其妙。然而BUG的来源都是逻辑设计的缺陷,然后一步步打补丁,把bug隐藏的越来越深,也就越来越难解,或者说是需要太多的缺陷处理。就像一团乱麻一样,如果我们重新梳理一遍,各种死结活结,也都无处可藏啦。

四、结语

写的很啰嗦,喜欢对你有帮助。哈哈,做为一只猿,码码字也算是本业啦。通过这两次重构经历,认识到以下三点:

1,MVC也好MVP也好,仅仅只是个套路。实际回过头看看java的设计原则,就会顿悟到这一点。所以关键得让你的程序架构变的清晰,代码变的优雅,工作变的高效。

2,从长远出发,重构是我们项目开发中必要的一到工序,产品经理们总是在功能交互上提交需求,但是对于我们的开发者来说。程序的设计同样应该放在版本的迭代之中,这是我们开发者应该重视的。

3,随着程序的不断升级、程序的架构也需要不断的调整,而不是为了赶时间一个劲的打补丁。

对于一些项目,从MVC到MVP是一种进步,也是重新梳理程序逻辑的一次机会。重构确实要花一些精力,但是如果你不动他,那么你永远就是开着拖拉机赛跑,累死也不见效。重构是一个持续的过程,我们要保证与时俱进,毕竟落后就要“挨打”。

编辑于 2016-09-21