模块化开发思路

摘要

移动应用开发早期很少涉及一些复杂的大型项目和复杂项目需求(Android还有一部分原因eclipse开发IDE限制),然而随着移动应用发展迫使我们要对项目进行一些结构上的优化。组件化就是这样的一种优化。

个人感觉优点如下:

  1. 解放生产力,提高效率。

  2. 项目重用性和可维护性提高。

  3. 独立编译,独立测试,节约时间。

  4. 分工更明确细致,人员成本降低。

  5. 插拔性极高,结构清晰规范。

组件化和模块化

概念理解

简单来说,模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容。模块我们相对熟悉,比如登录功能可以是一个模块,搜索功能可以是一个模块。

模块化和组件化本质思想是一样的,都是“大化小”,两者的目的都是为了重用和解耦,只是叫法不一样.如果非要说区别,那么可以认为模块化粒度更小,更侧重于重用,而组件化粒度稍大于模块,更侧重于业务解耦。

模型区别

简单开发模型

不难发现,往往是在一个界面中存在着大量的业务逻辑,而业务逻辑中充斥着各种各种网络请求,数据操作等行为,整个项目中没有所谓的模块的概念,项目组成的基本单位不是模块,而是方法级的。

关于这种开发模型没什么需要介绍的,我们早期都经历过,现在除了很少非常古老的项目以及初学者练手之作,已经很少见到。

在这种模型上充其量最多也就是抽出几个公共的工具类,所以复用性极差。耦合性很高也导致维护和重构及其复杂。

单工程开发模型

该种开发模型已经有了明确的模块划分,并且通过逻辑上的分层呈现出较好结构,该模型最为我们所熟悉,通常用于早期产品的快速开发,项目规模和团队规模较小没有其他特殊需求的情况下采用。

组件化开发模型

借助组件化这一思想,我们在“单工程”模型的基础上,将业务层中的各业务抽取出来,封装成相应的业务组件,将基础库中各部分抽取出来,封装成基础组件,而主工程是一个可运行的app,作为各组件的入口(主工程也被称之为壳程序)。这些组件或以jar的形式呈现,或以aar的形式呈现。主工程通过依赖的方式使用组件所提供的功能。

多工程组件化开发模型

抽象及划分

组件化首要做的事情就是划分组件,如何划分并没有一个确切的标准。

划分组建的粒度不要太细,要根据实际业务变化而定,在业务逻辑独立的前提下尽可能的大一些。

图中绿色是基础组建部分,红色部分是业务组件部分,黄色是视图部分demo和anchor是两个可运行的app.

组件化的本质

组件化的本质其实就是降低耦合实现最大化分离,实现一种软件上的可插拔(就像电脑主板和显卡,当然插线越少越好)

降低耦合的手段:

  1. 暴漏接口而不是具体实现。

    public interface ILoginView extends IView {
    
    void loginSuccess();
    }
    
  2. 避免依赖,可采用注入的方式,最好采用Java注解。

  3. 使用路由和数据总线。

驱动方式

UI驱动

UI驱动是我们最熟悉的开发流程,按照设计图实现界面,根据界面的交互和事件实现具体逻辑,根据逻辑需要实现数据模型。

数据模型驱动

数据模型驱动是在已经定义好了数据库模型的前提下采取的一种开发方式,前端开发很少见。

业务驱动

在实际情况中我们了解业务逻辑可能要比UI更早,所以如果在没有UI界面的情况下就可以去实现具体的业务逻辑这样就可以消除等待时间差。

业务逻辑驱动的核心是UI和Model接口的定义,难点也在于接口定义。

private AccountModelImpl mAccountModel;  //Model接口
private ILoginView mLoginView;           //UI接口

public LoginPresenterImpl(){
    mAccountModel = new AccountModelImpl();
}

@Override
public void requestAccountLogin(String account, String pwd) {
    //TODO 业务逻辑实现(比如判断账号和密码是否合法)

    mAccountModel.requestAccoutLogin(account, pwd, new Response.OnResult<UserInfo>() {
        @Override
        public void result(UserInfo response) {
            //TODO 业务逻辑实现
            mLoginView.loginSuccess();
        }
    });
}

模块化测试

我发现很多公司在实际开发中很大一部分时间花在了接口调试上面,个人感觉这个真的是很愚蠢,很没有必要,模块化可以帮我们大大提高业务逻辑和接口的测试效率,准确找到相关问题。

/**
 * 接口测试
 *
 */
public class PresenterTest{

    static{
        BaseInit.mJunitTest = true;
    }

    @Test
    public void loginPresenterTest() throws Exception {
        Assert.assertTrue(new LoginPresenterImpl().test());
    }

    @Test
    public void accountPresenterTest() throws Exception{
        Assert.assertTrue(new AccountPresenterImpl().test());
    }
}

重用

重用我们这里考虑到组件之间的重用,而且还需要考虑接口的重用和UI的重用,组件的重用在于降低耦合,不再分析,我们来看看接口的重用。

接口重用

任何复用的前提条件都是遵循一定的规则,否则没法实现。我们的接口也一样。

**返回状态**
{status: 0, content: "xxxxxx"}

**返回对象**
{status: 0, content: "xxxxxx", data:{}}

**返回对象集合**
{status: 0, content: "xxxxxx", data:[{}, {}]}

**返回字符串集合**
{status: 0, content: "xxxxxx", data:["xxx", "aaa", "xxxxxx"]}

除了最基本的遵循定义规则,还需要约定一些业务划分和抽象规则。

例如:在我们的APP内有两处Banner, 一个在首页含有图片标题和链接。我们这样定义

public class Banner{
    private String imgUrl;
    private String title;
    private String linkUrl;
}

而另一个地方的Banner在社区,有app内跳转类型和播放时长,没有链接。

public class Banner{
    private String imgUrl;
    private String title;
    private int type;
    private long time;
}

根据业务需求上面的接口定义没有任何问题,但是如果我们这样定义就相当于从界面驱动了,而没有从业务逻辑驱动。

如果从业务逻辑思考这个Banner,它就是一个展示广告,和界面的位置无关也不分类型,应该划分为公共接口。

java public class Banner{ private String imgUrl; private String title; private String linkUrl; private int type; private long time; } ``` 这样定义就可以在任意地方调用同一个Banner接口,因为这件事情本身就和界面位置无关。

这是一个最简单的例子,实际类似的地方很多,重要的是思维的转变。

UI重用

UI的重用分为两方面:

1.是为了实现多个APP共用逻辑组件。

实现共用逻辑组件的前提是业务逻辑的可重用性,只要界面的设计不要产生业务逻辑实现上的冲突一般不会出现问题。

2.是多个APP之间公共部分的重用。

我们在实现app差异化的同时其实要遵守一些Google或者Apple的设计规范,或者一些用户约定的行为规范,这样就可以达到设计公共部分的重用。