使用Dagger2前你必须了解的一些设计原则
2016-08-22 01:14
513 查看
可能很多人并不知道Dagger2是什么,有什么用,为什么这个开源库会这么的热门。
所以,在使用Dagger2之前,我们先要了解一些设计模式,看完之后想必你会喜欢上这个库。
B. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
对于依赖倒置原则,百度百科已经做了很详细的讲解 百科–依赖倒置原则
这里我会再用一个更符合我们Android开发者的例子来说明。
产品经理要求我们写一个下拉刷新功能,要和QQ的一模一样。
goole百度无果(假设)之后,我们只能自己动手。我们可能会这么写:
问题1:谁依赖谁?
RefreshListView依赖于QQHeader
因为RefreshListView持有QQHeader的引用,并且需要QQHeader才能实现下拉刷新的功能。
可能有人不是很明白,那我再举个例子。
小车和汽油,谁依赖谁?
很清晰的可以得出,小车依赖于汽油,而不是汽油依赖于小车。汽油还可以用到摩托车,飞机上……
而QQHeader同样可以用到RecyclerView或者ScrollView上……
那么我么继续往下说
下拉刷新功能写的很不错,产品经理觉得很满意。
但是过了两天,产品经理跟我们说:我还是觉得像美团那种带动画效果的比较好看。
然后我们又默默的去修改代码, RefreshListView改成了这样的:
产品经理觉得很好看,兴高采烈的找老板去了,留下你怨念的眼神。
我们作为一个有经验的程序员,为了防止产品经理又需要改需求,我们需要提高一下控件的扩展性了。
然后我们分析如下:
RefreshListView已经完成了必要事件分发逻辑,功能上是没问题的。
但是由于RefreshListView依赖于具体的Header编程,因此在更换新的Header时都不得不修改RefreshListView的源码,我们可以通过引入抽象的Header来解决该问题。
修改后如下:
可以看到,引入接口之后,RefreshListView针对RefreshHeader编程。在更换Header的时候我们只需要修改构造中
RefreshListView不依赖于QQHeader或者MeituanHeader,而是依赖于RefreshHeader这个抽象(接口)。
而QQHeader和MeituanHeader同样依赖于RefreshHeader这个抽象。
这就是依赖倒置原则
抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
抽象不应该依赖于具体,具体应该依赖于抽象。
既然了解了依赖倒置原则,那么这里就再说一点,为什么MVP模式中,View需要使用接口,而Presenter则不需要,或者说没有必要?
根据依赖倒置原则,高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象
MVP中,Presenter属于高层次模块,而View是低层次,所以View就需要实现接口了,就是这么简单……
想象一下这种情况:
如果Presenter中拿到的是Activity对象,而不是一个接IView口。
那么Presenter就可以随意调用Activity中的方法了,如果真调用了activity的方法,Presenter和Activity就会产生很强的耦合,那么你就无法单纯的使用junit进行单元测试了。
还有就是,如果你想将Activity换成Fragment或者View时,你也需要大量的修改Presenter了。
首先我们看RefreshListVIew, 我们在它初始化的时候一起初始化了RefreshHeader。
这样使得RefreshListView不仅依赖于RefreshHeader这个接口,还依赖于MeituanHeader这个实现,当我们想更换header的时候就必须去修改RefreshListView的这段源码了。
那么我们可以换另外一种方式实现:
不在RefreshListView初始化的时候一起初始化Header,而是通过setRefreshHeader的方式提供依赖。
当我们想更换Header的时候就不需要修改RefreshListView了。也可以更加方便的定制各个不同的RefreshListView,比如产品说:这个页面用QQHeader 那个页面用meituanHeader……
而这就是我们说的依赖注入了,其实我们开发中经常用到这样的依赖注入,但是我们从来没有注意到这件事。因为代码敲多了,总会自然而然的贴近原则
以简单的话来说就是
依赖注入是不在类中实例化其他依赖的类,而是先把依赖的类实例化了,然后以参数的方式传入构造函数中,
让上层模块和依赖进一步解耦。
以上我们用的是setter注入
依赖注入的方式还有另外两种,一是构造器注入。
比如:
但是很明显这里不能使用……因为RefreshListView是在Xml文件中配置,由Android系统自动生成。所以我们采用Setter注入的方式。
另外还有一种方式就是接口注入,我个人并不认同这是一种方式。
不多解释,看看维基百科吧,里面都是代码,不会英文估计都是能看懂的。
https://en.wikipedia.org/wiki/Dependency_injection
另外还有一点就是Injector
故名思意,就是注入器,负责初始化依赖,并且将依赖注入到上层模块中。
很明显上面的下拉刷新例子中,Activity或者Fragment扮演着这个角色。
那么,Dagger2是一个依赖注入的框架,使用它有什么好处呢?
可以看看这篇文章
使用Dagger 2进行依赖注入
如果觉得我对于依赖注入解释不够明朗的话,也可以再看看这篇文章
依赖注入原理
所以,在使用Dagger2之前,我们先要了解一些设计模式,看完之后想必你会喜欢上这个库。
一、依赖倒置原则
A. 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。B. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
对于依赖倒置原则,百度百科已经做了很详细的讲解 百科–依赖倒置原则
这里我会再用一个更符合我们Android开发者的例子来说明。
产品经理要求我们写一个下拉刷新功能,要和QQ的一模一样。
goole百度无果(假设)之后,我们只能自己动手。我们可能会这么写:
Sample1:
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private QQHeader qqHeader; public RefreshListView(Context context) { this(context, null); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); qqHeader = new QQHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: qqHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: qqHeader.onMove(y); break; case MotionEvent.ACTION_UP: qqHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
/** * Created by 13797 on 2016/8/19. * <p/> * 高仿QQ的下拉刷新头部 */ public class QQHeader extends LinearLayout { private static final int REFRESH_HEIGHT = 200; private int downY; public QQHeader(Context context) { super(context); } public void onPress(int y) { // 手指按下,记录当前手指位置 downY = y; } public void onMove(int y) { // 手指移动,改变header的高度 ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = y - downY; setLayoutParams(lp); } public void onRelease() { // 手指抬起,判断header高度是否大于刷新高度,开始刷新或者回到顶部 if (getHeight() > REFRESH_HEIGHT) { // 开始刷新 } else { // 回到顶部 } } }
问题1:谁依赖谁?
RefreshListView依赖于QQHeader
因为RefreshListView持有QQHeader的引用,并且需要QQHeader才能实现下拉刷新的功能。
可能有人不是很明白,那我再举个例子。
小车和汽油,谁依赖谁?
很清晰的可以得出,小车依赖于汽油,而不是汽油依赖于小车。汽油还可以用到摩托车,飞机上……
而QQHeader同样可以用到RecyclerView或者ScrollView上……
那么我么继续往下说
下拉刷新功能写的很不错,产品经理觉得很满意。
但是过了两天,产品经理跟我们说:我还是觉得像美团那种带动画效果的比较好看。
然后我们又默默的去修改代码, RefreshListView改成了这样的:
Sample 2
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private MeituanHeader meituanHeader; public RefreshListView(Context context) { super(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); meituanHeader = new MeituanHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: meituanHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: meituanHeader.onMove(y); break; case MotionEvent.ACTION_UP: meituanHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
产品经理觉得很好看,兴高采烈的找老板去了,留下你怨念的眼神。
我们作为一个有经验的程序员,为了防止产品经理又需要改需求,我们需要提高一下控件的扩展性了。
然后我们分析如下:
RefreshListView已经完成了必要事件分发逻辑,功能上是没问题的。
但是由于RefreshListView依赖于具体的Header编程,因此在更换新的Header时都不得不修改RefreshListView的源码,我们可以通过引入抽象的Header来解决该问题。
修改后如下:
Sample 3
/** * Created by 13797 on 2016/8/19. * 可下拉刷新的ListView */ public class RefreshListView extends ListView { private RefreshHeader refreshHeader; public RefreshListView(Context context) { super(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); refreshHeader = new MeituanHeader(context); // refreshHeader = new QQHeader(context); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); int y = (int) ev.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: refreshHeader.onPress(y); break; case MotionEvent.ACTION_MOVE: refreshHeader.onMove(y); break; case MotionEvent.ACTION_UP: refreshHeader.onRelease(); break; } return super.onTouchEvent(ev); } }
/** * Created by 13797 on 2016/8/20. */ public interface RefreshHeader { void onPress(int y); void onMove(int y); void onRelease(); }
/** * Created by 13797 on 2016/8/19. * 下拉刷新头部 */ public class QQHeader extends LinearLayout implements RefreshHeader { private static final int REFRESH_HEIGHT = 200; private int downY; public QQHeader(Context context) { super(context); } @Override public void onPress(int y) { // 手指按下,记录当前手指位置 downY = y; } @Override public void onMove(int y) { // 手指移动,改变header的高度 ViewGroup.LayoutParams lp = getLayoutParams(); lp.height = y - downY; setLayoutParams(lp); } @Override public void onRelease() { // 手指抬起,判断header高度是否大于刷新高度,开始刷新或者回到顶部 if (getHeight() > REFRESH_HEIGHT) { // 开始刷新 } else { // 回到顶部 } } }
// 美团Header的就不贴出来了,都是伪代码 public class MeituanHeader extends LinearLayout implements RefreshHeader { }
可以看到,引入接口之后,RefreshListView针对RefreshHeader编程。在更换Header的时候我们只需要修改构造中
refreshHeader = new MeituanHeader(context);这行代码即可。
RefreshListView不依赖于QQHeader或者MeituanHeader,而是依赖于RefreshHeader这个抽象(接口)。
而QQHeader和MeituanHeader同样依赖于RefreshHeader这个抽象。
这就是依赖倒置原则
抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
抽象不应该依赖于具体,具体应该依赖于抽象。
既然了解了依赖倒置原则,那么这里就再说一点,为什么MVP模式中,View需要使用接口,而Presenter则不需要,或者说没有必要?
根据依赖倒置原则,高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象
MVP中,Presenter属于高层次模块,而View是低层次,所以View就需要实现接口了,就是这么简单……
想象一下这种情况:
如果Presenter中拿到的是Activity对象,而不是一个接IView口。
那么Presenter就可以随意调用Activity中的方法了,如果真调用了activity的方法,Presenter和Activity就会产生很强的耦合,那么你就无法单纯的使用junit进行单元测试了。
还有就是,如果你想将Activity换成Fragment或者View时,你也需要大量的修改Presenter了。
二、依赖注入
大篇幅的说完依赖倒置之后,那么什么是依赖注入呢?首先我们看RefreshListVIew, 我们在它初始化的时候一起初始化了RefreshHeader。
public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); refreshHeader = new MeituanHeader(context); // refreshHeader = new QQHeader(context); }
这样使得RefreshListView不仅依赖于RefreshHeader这个接口,还依赖于MeituanHeader这个实现,当我们想更换header的时候就必须去修改RefreshListView的这段源码了。
那么我们可以换另外一种方式实现:
不在RefreshListView初始化的时候一起初始化Header,而是通过setRefreshHeader的方式提供依赖。
当我们想更换Header的时候就不需要修改RefreshListView了。也可以更加方便的定制各个不同的RefreshListView,比如产品说:这个页面用QQHeader 那个页面用meituanHeader……
public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); // refreshHeader = new MeituanHeader(context); // refreshHeader = new QQHeader(context); } public void setRefreshHeader(RefreshHeader refreshHeader) { this.refreshHeader = refreshHeader; }
而这就是我们说的依赖注入了,其实我们开发中经常用到这样的依赖注入,但是我们从来没有注意到这件事。因为代码敲多了,总会自然而然的贴近原则
以简单的话来说就是
依赖注入是不在类中实例化其他依赖的类,而是先把依赖的类实例化了,然后以参数的方式传入构造函数中,
让上层模块和依赖进一步解耦。
以上我们用的是setter注入
依赖注入的方式还有另外两种,一是构造器注入。
比如:
public RefreshListView(Context context, AttributeSet attrs, RefreshHeader refreshHeader ) { super(context, attrs); this.refreshHeader = refreshHeader; // refreshHeader = new MeituanHeader(context); // refreshHeader = new QQHeader(context); }
但是很明显这里不能使用……因为RefreshListView是在Xml文件中配置,由Android系统自动生成。所以我们采用Setter注入的方式。
另外还有一种方式就是接口注入,我个人并不认同这是一种方式。
不多解释,看看维基百科吧,里面都是代码,不会英文估计都是能看懂的。
https://en.wikipedia.org/wiki/Dependency_injection
另外还有一点就是Injector
故名思意,就是注入器,负责初始化依赖,并且将依赖注入到上层模块中。
很明显上面的下拉刷新例子中,Activity或者Fragment扮演着这个角色。
三、关于原则我已经说完了……
dagger2并不是我要介绍的重点,虽然我有心要写,但是我发现有很多文章已经写的很好很好了。那么,Dagger2是一个依赖注入的框架,使用它有什么好处呢?
可以看看这篇文章
使用Dagger 2进行依赖注入
如果觉得我对于依赖注入解释不够明朗的话,也可以再看看这篇文章
依赖注入原理
相关文章推荐
- 使用 Dagger2 前你必须了解的一些设计原则
- 使用Dagger2前你必须了解的一些设计原则
- 使用Dagger2前你必须了解的一些设计原则
- 设计模式中必须知道的一些原则
- net控件中数据导到Excel的格式 首先,我们了解一下excel从web页面上导出的原理。当我们把这些数据发送到客户端时,我们想让客户端程序(浏览器)以excel的格式读取它,所以把mime类型设为:application/vnd.ms-excel,当excel读取文件时会以每个cell的格式呈现数据,如果cell没有规定的格式,则excel会以默认的格式去呈现该cell的数据。这样就给我们提供了自定义数据格式的空间,当然我们必须使用excel支持的格式。下面就列出常用的一些格式: 1) 文本
- 从门禁系统的使用体验看良好的交互设计原则
- 系统设计的一些原则(转)
- 软件操作界面设计须遵循的一些原则
- 我的设计模式之旅(1)——学习的原则和一些笔记
- 系统设计的一些原则
- 关于web2.0网站易用性设计的一些原则
- 从门禁系统的使用体验看良好的交互设计原则
- 系统设计的一些原则
- 面向对象设计三大原则(封装变化点,对接口进行编程,多使用组合而不是继承)
- 从门禁系统的使用体验看良好的交互设计原则
- 系统设计的一些原则
- 软件操作界面设计须遵循的一些原则
- 系统设计的一些原则
- 面向对象类设计的一些原则
- 面向对象设计需要遵循的一些基本原则