继前篇博文对MVP架构做了简单解析之后遗留的问题还是挺多的,所以接下来的几篇我会慢慢重构它,尽量避免由于梯度过大而使初学者产生疑惑。关于架构设计我没有比较深刻的理解,经过几个项目的实战后,我总结了一句话:所谓设计,即在变与不变间斟酌;您是否从这句话看出了面向对象设计的理念—抽象,我觉得你是可以领悟到的,否则你就不会阅读本文了,那我们今天就先从变与不变开始吧。上篇文章中有段代码,不知你还否记得:
/**
* Created by 小雨 on 2015/11/15.
*/
public class FeedPresenter {
private FeedView mFeedView;
public FeedPresenter(FeedView feedView) {
this.mFeedView = feedView;
}
public void loadFeedList() {
this.mFeedView.showFeedList(FeedDataStoreFactory.getInstance().getFeedStoreData());
}
}
我们为View创建一个接口是毋容置疑的,但是Presenter拿到View句柄的方式是可以优化的,既然是面向接口编程,那么当前Presenter的依赖对象(View)注入形式是有问题的,已具体到功能模块,这是我们最不愿看到的。插入一段似乎不太合适的话,项目的包组织结构问题,有人喜欢按组件分,有人喜欢按功能分,不管最终以什么维度来划分,意向都是一样的,我们都希望代码结构看着整洁,那就从包结构着手吧,就像重构后的demo,我对包结构做了下调整,上层按组件分,下层按功能分,别问我为什么这么分,因为我喜欢…… 我还是稍作解释一下吧,不然有些同学肯定会打我的! 数据层(data)向表示层(presentation)提供数据,presentation并不关注data从哪里拿到的数据,DataStoreFactory负责具体执行,cache 、net or disk,这些都是data的职责,作为表示层我只负责展示就好了,不该我管的事我不管。presentation按功能模块划分包组织结构,基层构建放在根包下,其它按具体功能划分。
回到刚才Presenter依赖注入方式的问题。问题的本质就是我们把实现耦合了—多么痛的领悟… 应该依赖于抽象而不是依赖于具体! 所以先从接口抽象重构。 View的接口抽象定义:
/**
* Created by 小雨 on 2015/11/15.
*/
public interface MvpView {
}
如你所见,MvpView接口并没有定义任何行为方法,它的作用就是把View从具体功能中抽象出来。
像刚才说的那样,Presenter的依赖应该被抽象出来,得到和失去本是天生一对,Presenter接口的抽象定义:
/**
* Created by 小雨 on 2015/11/15.
*/
public interface MvpPresenter<V extends MvpView> {
void attachView(V view);
void detachView(boolean retainInstance);
}
Presenter接口定义好了,该如何管理View的句柄呢(何时注入? 何时销毁?),我们不可能把这些交给具体业务实现中去做吧,这样就违背了设计的初衷了Presenter持有View句柄的方式我们依然需要抽象出来,所以就有了下面这段代码:
/**
* Created by 小雨 on 2015/11/15.
*/
public abstract class MvpBasePresenter<V extends MvpView> implements MvpPresenter<V> {
private V mView;
@Override
public void attachView(V view) {
this.mView = view;
}
protected V getView() {
return this.mView;
}
protected boolean isViewAttached() {
return this.mView != null;
}
@Override
public void detachView(boolean retainInstance) {
this.mView = null;
}
}
需要注意一点的是必须在onDestroy()中调用detachView释放与view解绑。
Presenter管理View句柄的问题已经解决,那View如何持有Presenter的句柄呢,代码如下:
/**
* Created by 小雨 on 2015/11/15.
*/
public abstract class MvpBaseActivity<P extends MvpPresenter> extends AppCompatActivity implements MvpView {
protected P presenter;
protected abstract P createPresenter();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = createPresenter();
if (presenter == null) {
throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?");
}
presenter.attachView(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView(false);
}
}
MvpBaseActivity管理着View何时被注入到Presenter中,又是何时从Presenter中移除的。createPresenter()作为一个钩子延迟到子类去实现,多态的表现。
重构后的抽象基础构件已经定义好,现在时候是时候派上用场了,重构后的FeedPresenterImpl需要继承MvpBasePresenter,同时实现FeedPresenter接口即可
/**
* Created by 小雨 on 2015/11/15.
*/
public class FeedPresenterImpl extends MvpBasePresenter<FeedView> implements FeedPresenter {
public FeedPresenterImpl() {
}
@Override
public void loadFeedList() {
if (isViewAttached()) {
getView().showFeedList(FeedDataStoreFactory.getInstance().getFeedStoreData());
}
}
}
因为Presenter仅以弱引用形式持有View句柄,所以每次使用前View需要判定View是否已经被销毁掉。
和FeedPresenterImpl类似,FeedActivity需要继承MvpBaseActivity,同时实现FeedView接口,具体实现代码:
/**
* Created by 小雨 on 2015/11/15.
*/
public class FeedActivity extends MvpBaseActivity<FeedPresenter> implements FeedView {
private ListView vFeedListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vFeedListView = (ListView) findViewById(R.id.feed_list);
init();
}
private void init() {
presenter.loadFeedList();
}
@Override
public void showFeedList(List<Feed> feedList) {
vFeedListView.setAdapter(new FeedAdapter(feedList));
}
@Override
protected FeedPresenter createPresenter() {
return new FeedPresenterImpl();
}
}
至此,我们的重构告一段落了,也许你对本次重构稍有疑惑,但我相信你多看一遍就会理解。如一开始所说,本文只是重构的一环,所以你的持续关注是我前进的动力哈。
实例源码github地址:https://github.com/Tiny-Times/android-mvp2