模拟数据

模拟数据一般存在于App的界面版本中. 由于公司项目流程的特殊性, 在App整体开发环节中会存在一个界面版的交付物. 界面版主要是实现App的UI, 以及一些逻辑跳转, 而很少会涉及业务逻辑这一块.

也因为业务逻辑主要是存在于Server接口中, 而界面版一般接口都是处于开发中的, 所以此时业务逻辑也基本没法实现. 但是我们可以通过模拟数据的方式来实现整个App的逻辑.

为什么要模拟数据呢? 我们都知道, 需求不可能做到完全100%清晰, 开发人员也不可能100%理解需求. 我们在进入开发前会有一段时间去熟悉需求, 但是, 这样肯定是不够的, 很多不清晰的地方要我们实际去做才会发现. 那么界面版本就成为了我们真正完全梳理需求的一个途径. 如果在界面版只是画界面与跳转, 完全不关注业务逻辑的话, 在下一版本 - 含有接口的beta版本就可能存在很多因为理解不清晰导致的返工现象. 如果能在界面版就发现问题并沟通好的话, 可以在很大程度上提高效率. 虽然模拟数据会耗费不少时间, 可能开发起来时间会比较紧凑, 但是我认为这是完全值得的. 况且如果模拟数据的方式正确, 且App内部数据设计得当, 在联调接口时, 会非常简单, 通常只需要修改几个接口和几个类就可以将模拟数据直接替换为网络数据.

说了这么多, 接下来看看在CoreLibs中如何去模拟数据, 如何通过修改几个类来将模拟数据直接替换为网络数据.

模拟

由于CoreLibs使用的是Retrofit REST库, 网络接口都是通过Java接口的形式去描述的. 因此, 我们可以从Java接口入手, 通过Java接口的实现类去模拟数据. 又因为我们使用的是RxJava来订阅网络接口的结果, 理所当然, 在Java接口的实现类中, 应该使用RxJava去模拟数据. 举个栗子:

假设现在登录接口服务端已经写好了, 并且有了接口文档给我们参考, 那么此时我们可以这么描述登录接口:

@POST(Urls.LOGIN)
Observable<BaseData<User>> login(@Query("cellphone") String phone, 
        @Query("password") String password);

如果服务端的接口还在开发中, 我们怎么办呢? 由于接口还不存在, 所以url地址肯定不存在, 接口的参数名也可能不确定, 因此, 我们可以省略这些描述, 如下:

public interface UserApi {
    Observable<BaseData<User>> login(String phone, String password);
}

这里需要保持login方法返回Observable类型. 然后我们新建一个UserApiImpl类:

public class UserApiImpl implements UserApi {
    @Override
    public Observable<BaseData<User>> login(String phone, String password) {
        return null;
    }
}

默认情况下, 覆写的login返回的是null, 此时我们可以借助Observable.create()方法来生成一个Observable对象, 并返回.

@Override
public Observable<BaseData<User>> login(String phone, String password) {
return Observable.create(new Observable.OnSubscribe<BaseData<User>>() {
        @Override
        public void call(Subscriber<? super BaseData<User>> subscriber) {
            BaseData baseData = DataEmulator.createEmptyBaseData();

            subscriber.onNext(baseData);
            subscriber.onCompleted();
        }
    });
}
public class DataEmulator {
    public static BaseData createEmptyBaseData() {
        BaseData baseData = new BaseData();
        baseData.status = 1;

        baseData.data = new ObjectData();
        return baseData;
    }
}

这样的话, 我们就能通过RxJava来模拟网络返回的结果. 当然这里只会有一种结果, 就是所有的合法输入都是登录成功. 再来看看Presenter中怎么使用模拟的数据:

UserApi api = new UserApiImpl();

public void login(String phone, String password) {
    if (!checkUserInput(phone, password)) return;

    view.showLoading();
    api.login(phone, password)
            .compose(new ResponseTransformer<>(this.<BaseData<User>> bindLifeCycle()))
            .subscribe(new ResponseSubscriber<BaseData<User>>(view) {
                @Override public void success(BaseData<User> baseData) {
                    AuthorityContext.getContext().loggedIn();
                    RxBus.getDefault().send(new LoginCompletedEvent());
                    view.loginComplete();
                }
            });
}

可以看到, 我们只需要在初始化UserApi的时候使用UserApiImpl即可, 其他地方和真正的有网络接口的情况是没有区别的, 这也是为什么login要同样返回Observable的原因.

替换

如何替换成网络接口? 首先为UserApi里的login方法需要添加一些描述信息:

public interface UserApi {
    @POST(Urls.LOGIN)
    Observable<BaseData<User>> login(@Query("cellphone") String phone, 
        @Query("password") String password);
}

接着在Presenter中, 初始化UserApi的时候使用ApiFactory去初始化:

UserApi api = ApiFactory.getFactory().create(UserApi.class);

这样就ok了!

总结

首先, 想要做到模拟数据就必须定义一些网络请求结果的实体类, 有了实体类我们就会去写Model层的网络java接口. 有了接口我们就会去建立Presenter和View接口, 然后在Presenter里编写一些业务或者程序逻辑, 有了数据来源我们就可以在Activity/Fragment中编写数据绑定的代码. 通过上述步骤可以发现, 除了数据是假的, 其他的所有逻辑所有代码我们都可以提前写好! 一旦接口联调, 我们仅仅需要修改网络java的接口, 以及更改Presenter中api的初始化方式, 其他完全不用修改.

但是, 上述情况仅仅是理想情况, 有时候实体类定义的属性名字和服务器返回的结果的键名不一致的时候, 我们还需要修改属性名或者通过GSON的注解来转换属性名. 如果我们请求的参数与网络接口的参数定义的不一致, 我们还需要修改Presenter中的方法. 这是一个矛盾的地方. 我们很有可能将实体类和请求的参数定义的与网络接口不一致, 因为毕竟我们是先于网络接口的. 这样的话也会导致一定程度上的返工.

如果权衡利弊呢? 我认为, 只要实体类和请求定义的大部分一致, 就是利大于弊! 因为在这个模拟数据的过程中, 我们完整的将需求梳理了一遍. 最关键的就是一些需求不清晰, 或者逻辑不通的地方我们能提前发现! 属性的更改和接口参数的更改都是小改, 只涉及那么一两个类, 但是需求的更改是大改, 可能涉及很多个类.

想要尽可能的达到理想效果, 需要尽量满足以下几个条件:

  1. 网络接口的接口文档最好能提前定义出来. 这样数据结构, 请求参数就差不多定了, 不会有太大改变.

  2. 如果没有接口文档, 则需要服务器的数据库设计以及实体类设计.

  3. 与后台开发人员约定好网络请求结果的数据结构.

  4. 尽量考虑全面

  5. 多与后台开发人员沟通.

这样的话, 整个App的开发过程就是前紧后松, 前面考虑周全, 后面就会非常轻松, 妈妈再也不担心我交不出版本了!

Last updated