版权声明:本文为博主原创文章,转载请注明作者和链接。更多请继续关注 KunMinX
https://www.jianshu.com/p/9ef813d5c1af
前言
你准备好了吗?本次列车开往 “重构” 之巅,时速 900km/s。风太大听不见,但我就是可以很简单很直的,给你讲述事物本质和解决方案!⚡
项目常用架构比对
以下,对常见的 MVC、MVP、Clean、AAC 架构做个比对。
首先,一张表格展示各架构的类冗余情况:
需求是,写三个页面,ListFragment、DetailFragment、PreviewFragment,每个页面至少用到 3个 Note 业务、3个 User 业务。问:上述架构分别需编写多少类?
架构 | 涉及类 | 类总数 |
---|---|---|
MVC | Fragment:3个,Controller:3个,Model:2个 | 8个 |
MVP | Fragment:3个,Presenter:3个,Model:3个,Contract:1个 | 10个 |
Clean | Fragment:3个,ViewModel:3个,Usecase:18个,Model:3个 | 27个 |
AAC | Fragment:3个,ViewModel:3个,Model:3个 | 9个 |
MVC 架构的缺陷
- View、Controller、Model 相互依赖,造成代码耦合。
- 难以分工,难以将 View、Controller、Model 分给不同的人写。
- 难以维护,没有中间件接口做缓冲,难以替换底层的实现。
public class NoteListFragment extends BaseFragment {
...
public void refreshList() {
new Thread(new Runnable() {
@Override
public void run() {
//view 中直接依赖 model。那么 view 须等 model 编写好才能开工。
mNoteList = mDataManager.getNoteList();
mHandler.sendMessage(REFRESH_LIST, mNoteList);
}
}).start();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg) {
case REFRESH_LIST:
mAdapter.setList(mNoteList);
mAdapter.notifyDataSetChanged();
break;
default:
}
}
};
...
}
MVP 架构的特点与局限
- MVP 架构的特点是 面向接口编程。在 View、Presenter、Model 之间分别用 中间件接口 做衔接,当有新的底层实现时,能够无缝替换。
- 此外,MVP 的 View 和 Model 并不相互依赖,因此可以说是对 View 和 Model 做了代码解耦。
public class NoteListContract {
interface INoteListView {
void showDialog(String msg);
void showTip(String tip);
void refreshList(List beans);
}
interface INoteListPresenter {
void requestNotes(String type);
void updateNotes(NoteBean... beans);
void deleteNotes(NoteBean... beans);
}
interface INoteListModel {
List getNoteList();
int updateNote(NoteBean bean);
int deleteNote(NoteBean bean);
}
}
但 MVP 架构有其局限性。按我的理解,MVP 设计的初衷是,“让天下没有难替换的 View 和 Model” 。该初衷背后所基于的假设是,“上层逻辑稳定,但底层实现更替频繁” 。在这个假设的引导下,Presenter 除了承担业务逻辑的责任,还越界接管了 UI逻辑,这使得 UI 和 业务发生了耦合。
如此,一旦 UI 需求变化,UI 和 业务的编写者都会受到牵连,占据 2 份甚至更多工时。
且由于 UI 编写者的“权力上缴”,使得在 UI 编写完成后 无法独立完成单元测试。(因为此处缺少了 UI 逻辑 —— 总不能为了测试,而背着 Presenter 自己另外写一波吧。所以除了那种“上层逻辑稳定”的组件,无论从哪个角度来看,用 MVP 来架构项目,都是不合适的)
public class NoteListPresenter implements NoteListContract.INoteListPresenter {
private NoteListContract.INoteListModel mDataManager;
private NoteListContract.INoteListView mView;
@Override
public void requestNotes(String type) {
Observable.create(new ObservableOnSubscribe>() {
@Override
public void subscribe(ObservableEmitter> e) throws Exception {
List noteBeans = mDataManager.getNoteList();
e.onNext(noteBeans);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(List beans) throws Exception {
//presenter 直接干预了 UI 在拿到数据后做什么,使得职责上没有发生解耦。
//正常来说,解耦意味着,presenter 的职能边界仅限返回结果数据,
//由 UI 来依据响应码处理 UI 逻辑。
mView.refreshList(beans);
}
});
}
...
}
Clean 架构的特点和不足
为解决 Presenter 职能边界不明确 的问题,在 Clean 架构中,业务逻辑的职能被转移到领域层,由 Usecase 专职管理。Presenter 则弱化为 ViewModel ,作为代理数据请求,和衔接数据回调的缓冲区。
Clean 架构的特点是 单向依赖、数据驱动编程。 View -> ViewModel -> Usecase -> Model
。
View 对 ViewModel 的单向依赖,是通过 DataBinding 特性实现的。ViewModel 只负责代理数据请求,在 Usecase 处理完业务返回结果数据时,结果数据被赋值给可观察的 DataBinding 数据,而 View 则依据数据的变化而变化。
public class NoteListViewModel {
private ObservableList mListObservable = new ObservableArrayList<>();
private void requestNotes(String type) {
if (null == mRequestNotesUsecase) {
mRequestNotesUsecase = new ProveListInitUseCase();
}
mUseCaseHandler.execute(mRequestNotesUsecase, new RequestNotesUsecase.RequestValues(type),
new UseCase.UseCaseCallback() {
@Override
public void onSuccess(RequestNotesUsecase.ResponseValue response) {
//viewModel 的可观察数据发生变化后,databinding 会自动更新 UI 展示。
mListObservable.clear();
mListObservable.addAll(response.getNotes());
}
@Override
public void onError() {
}
});
}
...
}
但 Clean 架构也有不足:粒度太细 。一个 UseCase 受限于请求参数,因而只能处理一类请求。View 请求的数据包含几种类型,就至少需要准备几个 UseCase 。UseCase 是依据当前 View 对数据的需求量身定制的,因此 UseCase 的复用率极低,项目会因而急剧的增加类和重复代码。
public class RequestNotesUseCase extends UseCase {
private DataManager mDataManager;
@Override
protected void executeUseCase(final RequestValues values) {
List noteBeans = mDataManager.getNotes();
...
getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans));
}
//每新建一个 UseCase 类,都需要手动为其配置 请求参数列表 和 响应参数列表。
public static final class RequestValues implements UseCase.RequestValues {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public static final class ResponseValue implements UseCase.ResponseValue {
public List mBeans;
public ResponseValue(List beans) {
mBeans = beans;
}
}
}
AAC 架构的特点
AAC 也是数据驱动编程。只不过它并不完全依赖于 DataBinding 特性,而是直接在 View 中写个观察者回调,以接收结果数据并处理 UI 逻辑。
public class NoteListFragment extends BaseFragment {
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel.getNote().observe(this, new Observer() {
@Override
public void onChanged(@Nullable NoteBean bean) {
//update UI
}
});
}
...
}
你完全可以将其理解为 B/S 架构:由 Web 前端向 Web 后端发送了数据请求,后端在处理完毕后响应结果数据给前端,前端再依据需求处理 UI 逻辑。等于说, AAC 将业务完全压到了 “数据仓库层”。
某“解耦分离”架构的由来及特点
公司项目的第三轮重构采用的是 Clean 架构,而我在业余时间又设计了一款“实现 UI 和业务分离”的某架构,于是向主管申请了 1 周时间,利用“某架构”将 60 个类的核心模块重构了一遍。(不要慌,该架构已开源,文末链接给出)。
该架构属于“消息驱动编程”,即借助“总线”来转发数据的请求和响应,实现 UI 和 业务的分离。
不同于以往的架构,该架构明确界定了什么是 UI,什么是业务。
UI 的作用是视觉交互,UI 的职责范围仅限于请求数据和处理 UI 逻辑 。业务的作用是供应数据,业务的职责范围仅限于接收请求、处理数据、返回结果数据 。
你看,就像前后端分离一样,UI 和业务,可以分别交由不同的人分工合作:
1.独立自主。 谁先完成了,就可以先独立的单元测试一把。
2.可持续发展。 由于相互独立,人们可以在各自的领域里深耕。比如写业务的人:
第一轮,他可以简单的写写业务,目标是把数据给到位。
第二轮,他可以考虑改进业务逻辑、改进算法和数据结构,不仅将数据给到位,还优化了性能。
第三轮,他可以考虑安全性,将业务逻辑全部写进 C 层,这样反编译后除了 findViewById,啥 if else 也看不见,更别说篡改和破解了。
你看,解耦分离后,要想迭代和发展,就是这么轻松畅快。这要是放到代码耦合的项目中,是怎么也不敢想。
总结
综上,本文介绍了 4 个架构以及它们的优缺点。在实际项目开发中,可根据项目规模的大小、项目上层逻辑的稳定程度,来选择最合适的架构。
看完这篇文章,如你觉得有所收获和启发,请不吝点赞,你的点赞就是对我最大的支持!
更多访问
Github : KunMinX / android-viabus-architecture
1分钟掌握 ViaBus 架构的使用
Viabus - 年轻人的第一款架构
Android:你还在等那个,手把手带你重构的人出现吗?