关于clean架构其实很多人都应该听说过,但是由于clean架构的项目会显得比较复杂,而且在实际使用总其实真的要用到的很少,所以真的了解这个架构的人也比较少
其实我自己是实际在项目中使用了clean架构的,所以其实对于clean架构的一些思想是有了一些了解,不过为了加深理解以及看一下是否理解的有所偏差,所以决定对google的demo看一遍并理解一遍
首先找到google的架构demo的地址clean架构demo
然后,在看代码之前,最好还是先了解一下clean架构的一个大致的说明
嗯,还是直接上图吧,这张图很抽象,对吧,好吧,其实我现在看这个图也觉得很蛋疼
Enterprise Business Rules:业务对象
Application Business Rules:用于处理我们的业务对象,业务逻辑所在,也称为Interactor
Interface Adapters: 接口转换,拿到我们需要的数据,表现层(Presenters)和控制层(Controllers)就在这一层
Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架等等。
加了这些说明呢?还是很抽象啊,面对这种坑爹的图,我觉得我有比较重新画一张我们比较好理解的比较好
嗯,这张图就清晰多了
1、在图中,除了UI、Web、Devices层会有平台相关的api引用之外,其他的层级,都是纯java的,这样就可以让代码可以在多个java开发的平台进行复用
2、在层与层之间需要进行隔离,在Google的demo中因为数据结构比较简单所以可能看不出来,但是比较明显的一点就是,在出了UseCase层之后,就完全看不到Task这个类对象了,这是基本的层级隔离的思维,同时也是兼容复杂需求时,下层获取到的数据在经过转换之后返回为Presenter层或Control层直接便于显示、交互的数据类型,在需求很复杂的项目时,会需要这样做
具体表现为,UseCase层到Presenter层时,将NoteEntity、FruitEntity等,或者其他各种数据揉合为新的数据类型时使用,由于面向接口是Clean的基本思维方式,所以在中间有一个Convert层将接口Entity实体集合(各种Entity)转化为上层需要的UserEntity之后进行使用
3、以层级为单位的单元测试,在层级分清楚,并且层级之间都是一个接口通信时,进行层级测试、单元测试将非常方便
4、依赖注入的方式导致灵活修改逻辑、实现,由于实际的数据访问、平台代码都是在UI、Web、Devices层来实体并注入,导致当我们想替换数据的获取来源、替换业务逻辑时,不会影响其他层级以及实现的代码,只需要将新的实现类(新的实现类也只需要专注自己的业务,比如业务逻辑、数据访问等)在注入的地方注入新的实现类即可,并且新的实现类的一些所需要的类等都可以在外层构造器中直接实体化并传入,最小代价实现新的需求
目录说明
在大体的结构有了之后,可以来看一下谷歌的demo的结构
这其中:
1、BaseView、Baseresenter、UseCase是公共基类,UseCaseHandler、UseCaseScheduler、UseCaseThreadPoolScheduler是线程控制作用
2、util包中是工具类
3、data.source是实际数据加载的部分,从本地以及网络加载对应数据,TasksRespository就是在这个包中,在demo中它是实际加载数据的中心,不管是网络数据还是本地数据
剩余的addedittask、statistics、taskdetail等都是以功能为模块的划分
代码分析
我们以tasks这个模块下的代码为例子开始分析,因为这个额模块的代码结构上来说比较齐全一点
从项目结构来看的话,是一个标准的mvp,但是对p层进行了分离,分理处很多歌usecase
图中有说明的我就不说了,没有标明的大概说一下,TaskFragment是View层,TasksActivity可以看一下
public class TasksActivity extends AppCompatActivity {
private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";
private DrawerLayout mDrawerLayout;
private TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
// Set up the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);
// Set up the navigation drawer.
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
if (navigationView != null) {
setupDrawerContent(navigationView);
}
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);
// Load previously saved state, if available.
if (savedInstanceState != null) {
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable(CURRENT_FILTERING_KEY, mTasksPresenter.getFiltering());
super.onSaveInstanceState(outState);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// Open the navigation drawer when the home icon is selected from the toolbar.
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.list_navigation_menu_item:
// Do nothing, we're already on that screen
break;
case R.id.statistics_navigation_menu_item:
Intent intent =
new Intent(TasksActivity.this, StatisticsActivity.class);
startActivity(intent);
break;
default:
break;
}
// Close the navigation drawer when an item is selected.
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
return true;
}
});
}
@VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
}
这个类很简单,就做类两件事
1、多个View的组织,一个侧边栏,一个主页面
2、依赖注入
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);
Presenter的实体化、View的实体化,以及GetTask等UseCase的实体化都在这里,这里有一个Injection.providegetTasks(...)要注意
先来看GetTask,这是最小功能点,看他内部的代码
public class GetTasks extends UseCase<GetTasks.RequestValues, GetTasks.ResponseValue> {
private final TasksRepository mTasksRepository;
private final FilterFactory mFilterFactory;
public GetTasks(@NonNull TasksRepository tasksRepository, @NonNull FilterFactory filterFactory) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
mFilterFactory = checkNotNull(filterFactory, "filterFactory cannot be null!");
}
@Override
protected void executeUseCase(final RequestValues values) {
if (values.isForceUpdate()) {
mTasksRepository.refreshTasks();
}
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
TasksFilterType currentFiltering = values.getCurrentFiltering();
TaskFilter taskFilter = mFilterFactory.create(currentFiltering);
List<Task> tasksFiltered = taskFilter.filter(tasks);
ResponseValue responseValue = new ResponseValue(tasksFiltered);
getUseCaseCallback().onSuccess(responseValue);
}
@Override
public void onDataNotAvailable() {
getUseCaseCallback().onError();
}
});
}
public static final class RequestValues implements UseCase.RequestValues {
private final TasksFilterType mCurrentFiltering;
private final boolean mForceUpdate;
public RequestValues(boolean forceUpdate, @NonNull TasksFilterType currentFiltering) {
mForceUpdate = forceUpdate;
mCurrentFiltering = checkNotNull(currentFiltering, "currentFiltering cannot be null!");
}
public boolean isForceUpdate() {
return mForceUpdate;
}
public TasksFilterType getCurrentFiltering() {
return mCurrentFiltering;
}
}
public static final class ResponseValue implements UseCase.ResponseValue {
private final List<Task> mTasks;
public ResponseValue(@NonNull List<Task> tasks) {
mTasks = checkNotNull(tasks, "tasks cannot be null!");
}
public List<Task> getTasks() {
return mTasks;
}
}
}
可以看到他这里面实质性的访问数据是用TasksRespository来进行的,这个对象哪里来呢?就是Injection.providegetTasks(...)
其实写的大一点的话TasksRespository这边在其他人的demo里面也是一个接口,然后所有实质性执行、类的实例化都是在Activity层注入实现的,这就是:面向接口+依赖注入
然后这时候回过头来理解一下的话:
TasksFragment是View层
TasksPresenter是P层
GetTasks是UseCase层
在Presenter层和UseCase层是实际的使用逻辑,View是使用的表现
在UseCase层是针对实际需求点,内部数据访问是通过Respository层
而这里面所有的实现,原则上是可以全部用接口、抽象类来实现的,而真正的实现,都可以在Activity层实例化之后注入
这样就可以做到
1、无缝修改,不会影响其他部分
2、跨平台制作,因为所有跟平台有关的代码都是注入进去的,在最外层
3、测试方便,实例化时可以直接注入测试数据生成类,而不需要修改代码来mock数据
....