如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现。依赖倒转原则是Robert C. Martin在1996年为“C++Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作“Agile Software Development, Principles, Patterns, and Practices”一书中。
依赖倒转原则定义如下:
依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
这一原则在Android源码中随处可见,前面几张博客中所举的例子中例如:ListView的setAdapter方法、ViewGroup的addView方法等等,都体现了依赖倒置原则,它们都是依赖抽象编程的,而不是依赖具体的实现类。依赖倒置原则在Android源码中随处可见,故这里就不在举例说明了。
我们通过ImageLoader的例子来演示一下依赖倒置原则在日常开发中的应用,代码如下所示:
/** * 缓存接口 */
public interface ICache {
/** * 缓存接口方法 * * @param key 缓存内容的key,缓存内容的唯一标识 * @param value 缓存的Bitmap对象 */
void put(String key, Bitmap value);
/** * 获取key所对应的Bitmap * * @param key * @return 返回key所对应的Bitmap,如果没有则返回null */
Bitmap get(String key);
}
/** * 内存缓存实现类 */
public class MemoryCache implements ICache {
@Override
public void put(String key, Bitmap value) {
// 内存缓存逻辑
}
@Override
public Bitmap get(String key) {
// 从内存中获取key所对应的Bitmap
return null;
}
}
/** * SDCard缓存实现类 */
public class DiskCache implements ICache {
@Override
public void put(String key, Bitmap value) {
// SDCard 缓存实现
}
@Override
public Bitmap get(String key) {
// 从SDCard中获取key所对应的Bitmap
return null;
}
}
/** * 图片下载工具类 */
public class ImageLoader {
private ICache mCache;
/** * Setter方法注入方式,针对ICache接口编程 * @param cache 具体的缓存对象 */
public void setCache(ICache cache) {
this.mCache = cache;
}
/** * 下载url所对应的图片 * @param url */
public Bitmap load(String url) {
return downloadImage(url);
}
/** * 下载图片 * @param url */
private Bitmap downloadImage(String url) {
// 如果缓存中有次图片则直接返回缓存中的图片,不进行网络下载
if(mCache.get(url) != null) {
return mCache.get(url);
}
// 如果缓存中没有,则进行网络下载,此处省略下载图片的代码
// 处理下载的结果,进行缓存
if(bitmap != null) {
mCache.put(url, bitmap);
}
return bitmap;
}
}
上面使用Setter方法注入方式实现的ImageLoader,我们先来看看如何使用ImageLoader来加载图片,代码如下:
// 创建ImageLoader实例
ImageLoader imageLoader = new ImageLoader();
Bitmap bitmap = imageLoader.load(imageUrl);
上述代码只是为了演示依赖倒置的应用,省却了一些具体的实现代码、如果有兴趣的童鞋可以尝试一下完善这个小工具。
我们可以看到调用ImageLoader来加载图片非常方便,重点是我们要看ImageLoader中的setCache(ICache cache)方法,这个方法面向的是ICache接口编程的,而不是具体的缓存实现类,同时ImageLoader中也是针对ICache编程的,在下载完成是我们代码中是调用ICache接口中的put和get方法进行实现的,这就是面向抽象编程、也是多态的体现,在运行时系统会将mCache替换成具体实现类的实例,从而不会影响整个程序的运行。当ImageLoader的缓存策略不能满足我们的要求时,我们可以通过set不同的缓存实现类来更改ImageLoader的缓存策略,即符合开闭原则,又体现了依赖导致原则。我们也省时省力了。是不是一举多得呢?
怎么样,大家在体会一下依赖倒置原则的精髓和好处吧,同时也欢迎大家留言、拍砖。