Flux架构初探
引用facebook官网的一段介绍:
Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.
Google翻译一下:
Flux是Facebook用于构建客户端Web应用程序的应用程序体系结构。 它通过利用单向数据流来补充React的可组合视图组件。 它更像是一种模式,而不是一个正式的框架,您可以立即开始使用Flux,而无需使用大量新代码。
Flux本身是facebook推出来的一个前端设计框架,但是虽然领域不一样,代码设计思想还是相同的,对于里面的一些优秀的设计思想我们还是可以借鉴一下。
在Flux中,单项数据流是flux的核心设计思想,我们可以看一下官方给到的flux模型图:


我们先介绍一下图中这几个成员:
- Action
<font color='gray'>action类似于一个事件,包括但不仅限于一个用户交互操作,还包括一个后台推送,或者是一个定时器触发的操作,这都是一个action,每个action都有一个属于自己的事件id,通过这个id标明自己的身份,这样在整个流程中能够被传递到正确的位置。action在整个流程中扮演着一个信息传递者,内部携带着相关信息,推动流程的前进</font>
- Dispatcher
<font color='gray'>Dispatcher是一个事件分发器,向外暴露注册接口,Store可以通过接口注册到Dispatcher中。Dispatcher内部包含着将Action发送到需要的Store中的逻辑</font>
- Store
<font color='gray'>数据中心,我把它看成一个内存中的数据库,内部存储着相关的应用数据和一些状态,对外只暴露get接口而不暴露set接口,数据的改变只能通过action的驱动,以保持内部数据的准确性。当Store中的数据发生了改变,Store将会通知相对应的ui页面进行修改内容,从而减少ui界面的工作</font>
- View
<font color='gray'>用户交互页面,在flux中view的工作只包括通过用户交互产生相对应的事件,以及当Store数据发生变化时收到通知修改自己的ui和状态。这样view层的工作就十分简单,只有和用户交互以及刷新ui界面</font>
在这些类中间还有一个类:ActionCreator
ActionCreator的工作就如同它的名字,创建action,只不过在创建action之前可能需要先进行网络请求等操作。
下面我们来对比一下使用MVP和使用Flux的一个登陆界面的逻辑:
public class FluxLoginActivity extends AppCompatActivity implements View.OnClickListener,IDataChangedListener {
private EditText mEtAuthCode;
private EditText mEtAccount;
private LoginActionsCreator mCreator;
private LoginStore mLoginStore;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//将Store注册到Dispatcher中
mLoginStore = new LoginStore();
Dispatcher.getSingleton().register(mLoginStore);
//设置回调接口
mLoginStore.setDataChangedListener(this);
mCreator = new LoginActionsCreator();
Button btnLogin = findViewById(R.id.btn_login);
mEtAccount = findViewById(R.id.et_account);
mEtAuthCode = findViewById(R.id.et_authcode);
Button btnGetCode = findViewById(R.id.btn_get_code);
btnLogin.setOnClickListener(this);
btnGetCode.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_get_code:
mCreator.createGetAuthCodeAction(mEtAccount.getText().toString());
break;
case R.id.btn_login:
//登陆之前确保已经成功获取验证码
if (mLoginStore.isGetAuthCodeSuccess()){
mCreator.createLoginAction(mEtAccount.getText().toString(), mEtAuthCode.getText().toString());
}
break;
default:
break;
}
}
@Override
public void onDataChanged(String label) {
switch (label) {
case "AUTH_CODE":
onAuthCodeResponse();
break;
case "LOGIN":
onLoginResponse();
break;
default:
break;
}
}
private void onLoginResponse() {
Exception exception = mLoginStore.getException();
if (exception != null){
//登录失败
//弹窗或者吐司等提示用户
return;
}
//登陆成功
LoginResult loginResult = mLoginStore.getLoginResult();
//****登陆成功之后的操作
}
/**
* 获取验证码结果
*/
private void onAuthCodeResponse() {
Exception exception = mLoginStore.getException();
if (exception != null){
//获取验证码失败
//弹窗或者吐司等提示用户
}
}
}
在flux的整个流程中,Action这个类一直是贯穿整个流程的,每个环节的交互打都离不开这个Action,View通过Creator创建Aciton,Dispatcher将Action发送给Store,Store关注自己感兴趣的事件ID,并在收到Aciton的时候修改自己的数据然后通知View,View再根据事件ID更新ui。
这个过程中,每个类都只做自己的事情,逻辑非常清晰。View负责和用户交互,用户通过点击按钮产生两个事件,“LOGIN”和“GET_AUTH_CODE”,然后Creator负责创建Action,里面包括获取Action应该携带的信息,在这里的体现就是网络请求。,然后Action将携带的数据通过Dispatcher发出去给到相对应的Store,Store负责修改状态和数据,然后再通知View更新ui。每个人都是做自己的事情,各司其职。
flux登录流程涉及到的类:
Dispatcher
public class Dispatcher {
private static volatile Dispatcher dispatcher;
/**
* key: 事件id
* List<BaseStore> 对该事件感兴趣的Store集合
*/
private Map<String, List<BaseStore>> mStoreMap;
public static Dispatcher getSingleton() {
if (dispatcher == null) {
synchronized (Dispatcher.class) {
if (dispatcher == null) {
dispatcher = new Dispatcher();
}
}
}
return dispatcher;
}
private Dispatcher() {
mStoreMap = new HashMap<>();
}
public void register(BaseStore store) {
if (store == null) {
return;
}
List<String> labels = store.getActionLabels();
for (String label : labels) {
List<BaseStore> stores = mStoreMap.get(label);
if (stores == null) {
stores = new ArrayList<>();
mStoreMap.put(label, stores);
}
//防止重复注册
if (stores.contains(store)) {
continue;
}
stores.add(store);
}
}
public void unregister(BaseStore store) {
if (store == null) {
return;
}
List<String> labels = store.getActionLabels();
for (String label : labels) {
if (!mStoreMap.containsKey(label)) {
continue;
}
List<BaseStore> baseStores = mStoreMap.get(label);
if (baseStores.remove(store)) {
if (baseStores.isEmpty()) {
mStoreMap.remove(label);
}
}
}
}
public void dispatch(BaseAction action) {
final String label = action.getLabel();
final List<BaseStore> stores = mStoreMap.get(label);
if (stores == null) {
return;
}
for (BaseStore store : stores) {
store.changeData(action);
store.notifyDataChanged(label);
}
}
private void mainDispatch(final BaseAction action) {
final String label = action.getLabel();
final List<BaseStore> stores = mStoreMap.get(label);
if (stores == null) {
return;
}
for (BaseStore store : stores) {
store.changeData(action);
store.notifyDataChanged(label);
}
}
}
Dispatcher内部提供注册和解除注册的功能,内部维护一个Map,以事件Id为key,List<? extends BaseStore>为Value,之所以这么设计是因为对于一个事件可能不止一个Store关注,而且一个Store也可能关注很多事件。
loginAction & AuthCodeAction
class LoginAction extends BaseAction {
//登录结果
private LoginResult mResult;
public LoginAction(LoginResult result) {
super("LOGIN");
mResult = result;
}
public LoginAction(Exception e) {
super("LOGIN");
setException(e);
}
public LoginResult getResult() {
return mResult;
}
}
public class AuthCodeAction extends BaseAction {
//是否获取验证码成功
private boolean isSuccess;
public AuthCodeAction(boolean result) {
super("AUTH_CODE");
isSuccess = result;
}
public AuthCodeAction(Exception e){
super("AUTH_CODE");
setException(e);
}
public boolean isSuccess() {
return isSuccess;
}
}
Action中除了时间ID,还有就是携带的数据,包括isSuccess和loginResult。
LoginActionsCreator
class LoginActionsCreator {
public void createGetAuthCodeAction(String account){
/*
* 这里是网络请求,这里直接模拟数据就不做请求了
*/
AuthCodeAction authCodeAction = new AuthCodeAction(true);
Dispatcher.getSingleton().dispatch(authCodeAction);
/*
* 如果请求失败则
* AuthCodeAction action = new AuthCodeAction(codeException)
*/
}
public void createLoginAction(String account, String authCode){
//********此处省略网络请求
LoginAction action = new LoginAction(new LoginResult());
/*
* 如果请求失败则
* LoginAction action = new LoginAction(loginException)
*/
Dispatcher.getSingleton().dispatch(action);
}
}
LoginStore
public class LoginStore extends BaseStore {
private boolean getAuthCodeSuccess;
private LoginResult mLoginResult;
private Exception mException;
@Override
public void changeSelfData(BaseAction action) {
String label = action.getLabel();
switch (label) {
case "AUTH_CODE":
onAuthCodeResponse(action);
break;
case "LOGIN":
onLoginResponse(action);
break;
default:
break;
}
}
private void onLoginResponse(BaseAction action) {
LoginAction realAction = (LoginAction) action;
mLoginResult = realAction.getResult();
mException = realAction.getException();
}
private void onAuthCodeResponse(BaseAction action) {
AuthCodeAction realAction = (AuthCodeAction) action;
getAuthCodeSuccess = realAction.isSuccess();
mException = realAction.getException();
}
public boolean isGetAuthCodeSuccess() {
return getAuthCodeSuccess;
}
public LoginResult getLoginResult() {
return mLoginResult;
}
@Override
public Exception getException() {
return mException;
}
}
Store中保存了LoginActivity中所有需要的状态,包括获取验证码的结果,以及登录结果。Exception是过程中发生的异常,比如网络请求返回的异常等。
然后我们再看下在MVP模式中这样一个登录逻辑是怎么实现的,再根据Mvp模式下的LoginActivity对比一下Flux,可以看出两个框架的优缺点:
public class MvpLoginActivity extends AppCompatActivity implements View.OnClickListener, LoginView {
private LoginResult mLoginResult;
private EditText mEtAccount;
private EditText mEtAuthCode;
private boolean getAuthCodeSuccess;
private LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mLoginPresenter = new LoginPresenter(new LoginModel(), this);
Button btnLogin = findViewById(R.id.btn_login);
mEtAccount = findViewById(R.id.et_account);
mEtAuthCode = findViewById(R.id.et_authcode);
Button btnGetCode = findViewById(R.id.btn_get_code);
btnLogin.setOnClickListener(this);
btnGetCode.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_get_code:
mLoginPresenter.getAuthCode(mEtAccount.getText().toString());
break;
case R.id.btn_login:
mLoginPresenter.login(mEtAccount.getText().toString(), mEtAuthCode.getText().toString());
break;
default:
break;
}
}
@Override
public void loginSuccess(LoginResult result) {
//登陆成功
}
@Override
public void loginFailed(Exception e) {
//登录失败
}
@Override
public void getAuthCodeSuccess(boolean isSuccess) {
//获取验证码成功
}
@Override
public void getAuthCodeFailed(Exception e) {
//获取验证码失败
}
}
在Mvp中View层通过Presenter向Model层获取数据并保存在自身内部,然后再调用自己的更新逻辑。这其中View层除了负责交互以及刷新ui的事情,还负责存储Model返回的数据。
这里我们假设一下后台返回的是一个集合,然后本地需要根据某些条件对这些数据进行过滤并且排序,那我们就必须将数据处理的逻辑也放在View层中,如果这种数据处理逻辑比较多而且复杂的话,那整个View层还是会显得很庞大。
我们看看Mvp模式下涉及到的其他类:
LoginPresenter
public class LoginPresenter {
private LoginModel mLoginModel;
private LoginView mLoginView;
public LoginPresenter(LoginModel model, LoginView view){
mLoginModel = model;
mLoginView = view;
}
public void login(String account, String authCode){
mLoginModel.login(account, authCode, new Callback<LoginResult>() {
@Override
public void success(LoginResult result) {
mLoginView.loginSuccess(result);
}
@Override
public void failed(Exception e) {
mLoginView.loginFailed(e);
}
});
}
public void getAuthCode(String account){
mLoginModel.getAuthCode(account, new Callback<Boolean>() {
@Override
public void success(Boolean aBoolean) {
mLoginView.getAuthCodeSuccess(aBoolean);
}
@Override
public void failed(Exception e) {
mLoginView.getAuthCodeFailed(e);
}
});
}
}
LoginModel
class LoginModel {
public void login(String account, String authCode, Callback<LoginResult> callback){
//********此处省略网络请求
callback.success(new LoginResult());
}
public void getAuthCode(String account, Callback<Boolean> callback){
//********此处省略网络请求
callback.success(true);
}
}
LoginView
interface LoginView {
void loginSuccess(LoginResult result);
void loginFailed(Exception e);
void getAuthCodeSuccess(boolean isSuccess);
void getAuthCodeFailed(Exception e);
}
对比一下可以发现Flux中的类是比Mvp要多一点的,这个就多在了Flux中每一个事件都会有一个新的Action类。但是也是因为这个原因,所以在flux中的逻辑更加清晰。
而且当我们在登陆的时候需要这个数据的不仅是登录界面,其他页面也是需要的,包括我们的一下主界面等,这个时候如果需要通知到我们的主界面更新ui的话就只能通过使用一些架构以外的方式进行,比如说广播或者eventbus, 但是如果使用Flux的话只需要在MainActivity的Store中关注“LOGIN”这个事件id,那么MainActivity就不需要什么其他额外的操作去完成。这也是Flux的优势之一。
flux的劣势我们也说到了,就是会导致类比较多,所以在小项目里面不推荐使用,这会使类的结构变得复杂。但是在大项目中,逻辑普遍会比较复杂,如果我们的view中存放的数据过多,会使得本身View还需要去维护自己内部的数据,使整个类变得庞大,这个时候使用flux就能有效的解决这个问题。
flux框架基础类封装的项目相关地址已上传github,感兴趣的朋友可以看看。
网友评论