关于重构的一些话
为什么我们需要重构?
重构改进软件设计
只为了短期目的或者在完全理解整体设计之前编写出来的代码,会导致程序逐渐失去自己的结构。这时如果没有重构,程序的设计会逐渐腐败变质,程序员愈来愈难通过阅读源码而理解原本设计。代码结构的流失是累积性的,经常性的重构可以帮助代码维持自己该有的形态。
重构提高编程速度
拥有良好设计才可能做到快速开发。如果没有良好设计,或许某一段时间内你的进展迅速,但恶劣的设计很快就让你的速度慢下来。你会把时间花在调试上面,难以或者甚至无法添加新功能,修改时间愈来愈长,扩展性与可维护性越来越差。
重构使单元测试成为可能
Android作为对单元测试最不友好的系统环境,而单元测试对界面的测试非常乏力,也不值得花大量时间在这上面,同时因为逻辑的过度耦合,每一个类里面有非常多的私有依赖无法进行mock,从而无法达到尽可能全面测试的目的。
早期APP中MVC的应用
在早期版本中,整体项目架构是基于原始的MVC框架,MVC框架本身无需赘述,简要的介绍一下MVC框架在早期中的应用:
视图层(View):
一般采用XML文件进行界面的描述,这些XML可以理解为Android App的View。使用的时候可以非常方便的引入。同时便于后期界面的修改。逻辑中与界面对应的id不变化则代码不用修改,大大增强了代码的可维护性 。
控制层(Controller):
Android的控制层的重任通常落在了众多的Activity的肩上,要通过Activity交割给Model业务逻辑层处理,随着页面中业务越来越多,逻辑越来越复杂,大量的处理业务逻辑的代码充斥在Activity中,Activity变的越来越臃肿,代码可读性越来越差。
以APP中刷卡页面为例,每当新增加一种新刷卡设备,在刷卡页面就要增加新设备的刷卡处理代码,在集成进3.8设备之后,刷卡页面的代码量已经达到三千行之多,如果按照这种方式写下去,后来的开发人员对于刷卡流程会越来越难看懂,解决bug也要花费大量的时间来处理。
模型层(Model)
我们针对业务模型,建立的数据结构和相关的类,就可以理解为Android App的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理。Android上的MVC模式,view层和model层是相互可知的,这意味着两层之间存在耦合,开发,测试,维护都需要花大量的精力。
MVC框架存在的问题
xml作为view层,控制能力实在太弱了,在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受和处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。
MVP
一般来说,对于有图形界面的客户端软件来说,我们可以简单地分为三层
MVP分层结构
分层
我们所用的图形界面,看到的是什么?本质上来说就是数据的可视化呈现。这里就正式引入 MVP 概念了,它是 Model View Presenter 的简称,Model 提供数据,View 负责展示,Presenter 负责处理逻辑,它的结构图如下。
调用关系和上面的分层一摸一样!在 MVP 里,Presenter 完全把 Model 和 View 进行了分离,主要的程序逻辑在 Presenter 里实现。而且,Presenter 与具体的 View 是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更 View 时候可以保持 Presenter 的不变,即重用!
从上到下是直接调用,用实箭头表示。从下到上是回调,用虚箭头表示。依赖关系是上层对下层是直接依赖,下层不能依赖上层。
MVP 很好地将 View 与 Model 做了分离,同时 Presenter 也是可以复用的,假设有有两个页面,一个显示列表大纲,一个显示列表详情,如果操作大致一样,那可以复用同一个 Presenter。将 Presenter 的功能做一个最小集的拆分,有利于 Presenter 的复用,同一个视图里面可以同时存在多个 Presenter,每个 Presenter 实现不同的功能,更新不同的区域。总之,在 MVP 架构中,每一层均可以拆分成独立的可复用的组件,因为彼此都可以只是接口依赖。
下面给出一个APP中MVP简化代码实现的例子。
View
这里和MVC一样,用Activity来充当View层,不同的是需要实现View接口,同时要在代码中声明Presenter层的引用。
//HomeActivity.java
public class HomeActivity extends AppCompatActivity implements HomeContract.View {
private ListView mList;
private HomeListAdapter mListAdapter;
private HomeContract.Presenter mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
...
mList = (ListView)findViewById(R.id.list);
Context context = getApplicationContext();
BaseScheduler scheduler = ThreadPoolScheduler.getInstance();
BaseThread thread = new HandlerThread();
TaskRepository taskRepository = TaskRepository.getInstance(
Injection.provideLocalProxy(),
Injection.provideRemoteProxy());
mPresenter = new HomePresenter(this, scheduler, thread, taskRepository);
mPresenter.start();
mListAdapter = new HomeListAdapter(context, mPresenter);
mList.setAdapter(mListAdapter);
}
@Override
public void onResume() {
super.onResume();
mPresenter.loadTasks(true);
}
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.stop();
}
@Override
public void onTasksLoaded(List<Task> tasks) {
mListAdapter.setTasks(tasks);
}
}
Presenter
这里创建了一个 HomePresenter 的实例,HomeActivity 里面只是调用了 HomePresenter 的 loadTasks 方法,以及 start,stop 方法,这里 View 只是对 Presenter 有依赖,对 Model 层是没有依赖的。
public interface BasePresenter {
void start();
void stop();
}
public interface BaseView<T extends BasePresenter> {
void setPresenter(T presenter);//一个 Presenter 属于一个 View
}
协议类,Presenter层与View层在此关联。
//协议类
public class HomeContract {
public interface View extends BaseView<Presenter> {
void onTasksLoaded(List<Task> tasks);
void onTasksNotAvailable();
void onError(int code, String message);
}
public interface Presenter extends BasePresenter {
void loadTasks(boolean refresh);
}
}
Presenter层中发起网络请求或者调用其他业务逻辑,同时对结果进行处理,反馈到view层中。
public class HomePresenter implements HomeContract.Presenter {
private HomeContract.View mView;
private Repository mRepository;//Model
private BaseScheduler mScheduler;
private BaseThread mMainThread;
private LoadTask mLoadTask;
public HomePresenter(HomeContract.View view, BaseScheduler scheduler, BaseThread thread, Repository repository) {
this.mView = view;//这里传进了view
this.mScheduler = scheduler;
this.mMainThread = thread;
this.mRepository = repository;
mLoadTask = new LoadTask(mScheduler);
}
@Override
public void loadTasks(boolean refresh) {
LoadTask.RequestValues requestValues = new LoadTask.RequestValues();
requestValues.setRefresh(refresh);
requestValues.setRepository(mRepository);
mLoadTask.setRequestValues(requestValues);
mLoadTask.execute(new LoadTask.Callback<LoadTask.ResponseValues>() {//定义了一个callback来接受Model处理的回调
@Override
public void onSuccess(final LoadTask.ResponseValues response) {
mMainThread.post(new Runnable() {
@Override
public void run() {
List<Task> tasks = response.getTasks();
if (tasks != null && tasks.size() > 0) {
mView.onTasksLoaded(tasks);//通知给view
} else {
mView.onTasksNotAvailable();
}
}
});
}
@Override
public void onError(final int code, final String msg) {
mMainThread.post(new Runnable() {
@Override
public void run() {
mView.onError(code, msg);
}
});
}
}, true);
}
}
HomePresenter 是一个具体的实现,它实现了具体的业务逻辑即 loadTasks,并且将结果通过接口 onTasksLoaded 返回给 View 层去做展示。对于 Presenter 来说,View 是它的上一层,只能通过这种回调的方式返回数据,或者做数据更新。
Model
TaskRepository.java 与 MVC 中一致。
//BaseTask.java
public abstract class BaseTask<T extends BaseTask.RequestValues, R extends BaseTask.ResponseValues> implements Runnable {
public interface Callback<P> {//回调接口
void onSuccess(P response);
void onError(int code, String msg);
}
public void execute(Callback<R> callback, boolean schedule) {...}
}
//LoadTask.java
public class LoadTask extends BaseTask<LoadTask.RequestValues, LoadTask.ResponseValues> {
public static final class RequestValues implements BaseTask.RequestValues {...}
public static final class ResponseValues implements BaseTask.ResponseValues {...}
}
}
//再使用 refreshTasks 获取服务端数据,传回给 Presenter,它对上层的数据返回也是通过定义的回调函数完成的,即上面的 LoadTask.Callback。
Clean思想
Clean Architecture是著名软件大师Bob大叔在2012年对web应用提出的建议, Clean Architecture,其在Android端上的参考demo Android Clean Architecture Demo,主要思想描述如下:
Independent of Frameworks.(框架独立)
Testable.(容易测试)
Independent of UI.(UI独立)
Independent of Database.(数据库独立)
Independent of any external agency.(外部机制独立)
使用Clean Architecture的目的是实现软件的分层,是项目代码模块间能够高内聚,低耦合。如上图中所示,同心圆代表各种不同领域的软件。一般来说,越深入代表你的软件层次越高。外圆是战术实现机制,内圆的是战略核心策略。 Android clean architecture 设计图
结合上图详细说明一下Clean Architecture在APP中的实现思路:
1 Data layer 数据层
集合Retrofit框架从服务端获取Restful API,根据不同的接口分别创建RxJAVA的被观察者对象(这个对象提供给Domain层,进行流式操作)。service实现方式如下:
@GET("getMerchantInfoBySYB")
Flowable<MyInfoResponse> getMerchantInfoBySYB(@Query("merchantNo") String merchantNo);
@GET("changeReviewCardBySYB")
Flowable<ChangeNumResponse> changeReviewCardBySYB(@Query("merchantNo") String merchantNo);
@GET("submitReviewBySYB")
Flowable<SettleSureResponse> submitReviewBySYB(@QueryMap HashMap<String, String> params);
然后通过Repository暴露service给domain层,repository实现如下:
Observable<UsersModel> getMerchantInfoBySYB(String merchantNo);
Observable<UsersModel> changeReviewCardBySYB(String merchantNo);
Observable<UsersModel> submitReviewBySYB(HashMap<String, String> params);
APP数据层包结构如下图所示:
data层包结构
所有数据相关的类,包括从远程HTTP请求获取的数据、本地数据库、枚举类、preferences中得到数据都在data层,包括VO、BO层类转换工具,这个层作为最底层,完全不知道有DomainLayer,PresentationLayer的存在,因此非常便于用junit、mockito、robolectric 、espresso等开发工具进行测试。
2 Domain layer 逻辑层
利用RxJAVA实现data层返回的Observable订阅Presentation层传入的的Subscriber对象(该对象在persenter层创建)实现业务逻辑,以登录模块的逻辑控制为例,实现代码如下:
@Singleton
public class LoginInteractor {
private DataRepository dataRepository;
private MemoryCache memoryCache;
private Preferences preferences;
@Inject
public LoginInteractor(DataRepository dataRepository, MemoryCache memoryCache, Preferences preferences) {
this.dataRepository = dataRepository;
this.memoryCache = memoryCache;
this.preferences = preferences;
}
public Flowable<LoginResponse> login(HashMap<String, String> param) {
return dataRepository
.login(param)
.doOnNext(loginResponse -> {
if (loginResponse.getCode().equals(Const.SUCCESS)) {
preferences.setUserLoggedIn();
saveUserInfo(loginResponse);
} else {
throw new ApiException(loginResponse.getCode(), loginResponse.getMsg());
}
});
}
/**
* 存储用户信息
* @param respone 登录返回信息
*/
private void saveUserInfo(LoginResponse respone) {
//业务逻辑
}
}
逻辑层完全不知道有一个PresentationLayer存在,他只知道有DataLayer,基于这些数据,根据业务逻辑,对这些数据进行处理,比如缓存到本地或者存储到数据库,因此他的主要职责是:
1、控制DataLayer对数据做增删改查,就这么简单,然后就没有然后了。
2、如果这一层出现 anroid.os***,就说明已经偏离了Android-CleanArchitecture,因为跟Android UI没有什么相关的代码,所以也可以很轻松的用junit、mockito、robolectric 、espresso等开发工具进行测试。
逻辑层包结构如下图所示:
逻辑层包结构
可以看到各个模块的代码都有各自的逻辑处理相关类,当产品需求变更时,可以根据产品需求,在interactor中修改对应模块的逻辑代码,同时因为逻辑层代码独立于模块页面部分代码,降低了代码的耦合度。
3 Presentation Layer
这里创建Subscriber对象传入到domain层观察Observable对象发送的事件。由于Model已经在Data层声明,所以这一层去掉MVP中的ModeL层,定义view接口(Activity实现该接口),定义presenter类创建subsciber对象,获取逻辑层传递来的服务端数据对象,下面代码以登录模块的Presentation层为例,说明一下。
@PerActivity
public class LoginPresenter extends BasePresenter<LoginContract.View> implements LoginContract.Presenter {
LoginInteractor loginInteractor;
@Inject
public LoginPresenter(LoginInteractor loginInteractor) {
this.loginInteractor = loginInteractor;
}
@Override
public void login(String phoneValue, String md5pwd, String time, String operatorValue, String platform, String appVersion, String mac) {
getView().showProgressDialog();
HashMap<String, String> paramList = new HashMap<>(2);
paramList.put("phone", phoneValue);
disposable.add(loginInteractor.login(paramList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(loginBean -> {
getView().hideProgressDialog();
getView().onLogin(loginBean);
}, e -> {
e.printStackTrace();
getView().hideProgressDialog();
if (e instanceof NoNetworkException) {
getView().showErrorMsg(Application.instance.getResources().getString(R.string.emptyview_error_connection_not_found));
} else if (e instanceof ApiException) {
getView().showErrorMsg(e.getMessage());
} else {
getView().showErrorMsg(e.getMessage());
}
}));
}
}
登录模块协议类。
//定义view接口
public interface LoginContract {
interface View extends BaseContract.View {
String getName();
String getPassword();
void onLogin(LoginResponse loginBean);
void savePhoneAndPwd();
void onUpdateChecked(LoginResponse respone);
}
interface Presenter extends BaseContract.Presenter<View> {
void login(String phoneValue, String md5pwd, String time, String operatorValue, String platform, String appVersion, String mac);
}
}
Presentation 层包结构如下图所示:
Presentation层包结构
可以看到UI层的代码已经按照模块进行了划分,收款模块、登录模块、二维码支付模块、用户信息模块等众多模块功能组成了APP整个应用,模块化的好处除了使代码看起来清晰易懂,容易分配开发任务,新来的开发人员也可以快速熟悉项目代码以外,同时也为代码的组件化拆分做了一些准备。
APP整体架构图如下:
响应式clean architecture架构图改造前与改造后的APP结构图对比:
改造前 改造后
可以看到改造之前的包结构按照既按照页面划分,同时又按照功能模块进行了划分,整体APP结构紊乱,耦合度大,当对APP的功能进行改动时,需要改动的目录很多,而改动之后的代码,首先按照数据层、逻辑层以及展示层进行了分层,同时对于展示层基于MVP进行重构,对页面代码按照功能进行了模块划分,使得APP端人员在开发产品功能时,只需改动对应模块的数据、逻辑以及页面代码,降低程序的耦合度,同时为APP的组件化拆分做了准备。
在完成以上MVP+Clean Architecture重构的基础上,还可以在以下方面对APP进行优化;
1、基于谷歌官方框架Android Architecture Components重构;
2、进行组件化拆分;
3、用kotlin进行重写,目前POS直营 Anrdoid端基于kotlin语言开发,使用kotlin语言能够使代码量减少30%以上,增加开发速度;
4、对APP部分模块用H5实现,目前POS直营APP有将近50%的页面是用H5开发实现的,H5能够快速上线,减少前端人员开发量,移动端人员需要保证JS与原生交互的稳定性,解决好各个品牌手机的兼容性;
网友评论