1. 单一职责原则(Single Responsibility Principle)
对一个类而言,应该仅有一个引起它变化的原因,……,一个类应该是一组相关性很高的函数、数据的封装。……。这是一个备受争议却有极其重要的原则,……,需要靠个人的经验来界定。
1.1. 违反单一职责原则的例子
public class ImageLoader {
// 图片缓存
LruCache<String, Bitmap> mImageCache;
public ImageLoader() {
initImageCache();
}
/**
* 初始化图片缓存
*/
private void initImageCache() {
// ……
}
/**
* 显示图片
* @param url 图片地址
* @param imageView ImageView
*/
public void displayImage(final String url, final ImageView imageView) {
// ……
}
/**
* 下载图片
* @param imageUrl 图片地址
* @return 图片 Bitmap
*/
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
// ……
return bitmap;
}
}
这是一个简单的 ImageLoader 实现,其中包含了缓存相关的逻辑,根据单一职责原则的要求,这是不合理的,应该讲缓存初始化等逻辑独立至一个图片缓存类中,在 ImageLoader 中,只保留与图片加载相关的逻辑代码。
1.2. 修改之后的代码
/**
* 图片加载类
*/
public class ImageLoader {
// 图片缓存
ImageCache mImageCache = new ImageCache();
public ImageLoader() {
}
/**
* 显示图片
* @param url 图片地址
* @param imageView ImageView
*/
public void displayImage(final String url, final ImageView imageView) {
// ……
}
/**
* 下载图片
* @param imageUrl 图片地址
* @return 图片 Bitmap
*/
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
// ……
return bitmap;
}
}
/**
* 图片缓存类
*/
public class ImageCache {
// LRU 缓存
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
/**
* 初始化图片缓存
*/
private void initImageCache() {
// ……
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
}
修改后的代码将上面例子中的类拆分为了两个类 ImageLoader 和 ImageCache,ImageLoader 只负责图片加载相关的逻辑,ImageCache 则负责图片缓存,使得类职责分明。
2. 开闭原则(Open Close Principle)
软件中的对象(类、模块、函数等)应该对扩展是开放的,但是,对于修改是封闭的。……。当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
2.1. 违反开闭原则的例子
继续拿上面的 ImageLoader 举例,根据上述的代码,我们目前只进行了内存缓存,如果我们需要添加一种缓存方式,支持缓存至 SD 卡中,那我们就需要添加一个 DiskCache 类,实现 SD 卡缓存逻辑,并且修改 ImageLoader 的代码,将其中的 ImageCache 替换为 DiskCache,如果两个缓存类提供的方法不同的话,我们还需要去修改方法调用处,这就违反了开闭原则,对于这种后期的需求更改,我们不应该通过修改已有代码达到目的,而是应该在不修改原有代码的情况下进行扩展,而在现有的代码结构下,我们无法做到不修改原有代码来实现新需求。
2.2. 修改代码使其符合开闭原则
public interface ImageCache {
Bitmap get(String url);
void put(String url, Bitmap bmp);
}
public class MemoryCache implements ImageCache {
private LruCache<String, Bitmap> mMemoryCache;
public MemoryCache() {
}
@Override
public Bitmap get(String url) {
return mMemoryCache.get(url);
}
@Override
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
}
}
public class ImageLoader {
/** 图片缓存 */
private ImageCache mImageCache = new MemoryCache();
/** 线程池,线程数量为 CPU 的数量 */
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = mImageCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 图片没有缓存,提交到线程池中进行下载
submitLoadRequest(imageUrl, imageView);
}
private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
imageView.setTag(imageUrl);
mExecutorService.execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(imageUrl, bitmap);
}
});
}
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try{
URL url = new URL(imageUrl);
final URLConnection conn = url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
在修改代码之后,我们主要关注 ImageLoader 中的 mImageCache 属性,它是一个 ImageCache 引用,而 ImageCache 是一个 Interface,引入接口是实现开闭原则的关键之一,根据多态的特性,mImageCache 可以指向任何一个实现了 ImageCache 的类的对象,这里指向了一个 MemoryCache 对象。在 ImageLoader 中同时提供了一个 setter 方法 setImageCache,可以通过该方法来注入 ImageCache,当我们完成这两点,ImageLoader 类也就符合了开闭原则,当我们需要实现上述的需求,即更换缓存方式为缓存至 SD 卡或者同时使用两种缓存方式时,我们不需要对 ImageLoader 类做任何修改,仅仅需要创建一个新的实现了 ImageCache 接口的类,再通过 ImageLoader 的 setImageCache 方法注入即可。
/**
* SD 卡缓存类,实现了 ImageCache 接口
*/
public class DiskCache implements ImageCache {
@Override
public Bitmap get(String url) {
return null; // TODO: 2017/10/25 从本地文件中获取图片
}
@Override
public void put(String url, Bitmap bmp) {
// TODO: 2017/10/25 将 Bitmap 写入文件
}
}
/**
* 双缓存类,同样实现了 ImageCache 接口
*/
public class DoubleCache implements ImageCache {
private ImageCache mMemoryCache = new MemoryCache();
private ImageCache mDiskCache = new DiskCache();
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
@Override
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
}
// 换用不同缓存方式的方法
imageLoader.setImageCache(new DiskCache());
imageLoader.setImageCache(new DoubleCache());
或者这样
imageLoader.setImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
return null;
}
@Override
public void put(String url, Bitmap bmp) {
}
});
3. 里氏替换原则(Liskov Substitution Principle)
所有引用基类的地方必须能透明地使用其子类的对象。……。开闭原则和里氏替换原则往往是生死相依、不弃不离的,通过里氏替换达到对扩展开放,对修改关闭的效果。这两个原则都同时强调了一个 OOP 的重要特性——封装。
4. 依赖倒置原则(Dependence Inversion Principle)
依赖倒置原则指代了一种特定的解耦方式,使得高层次的模块不依赖于低层次的模块的实现细节。
有以下几个关键点:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
模块间到的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
面向接口编程或者说是面向抽象编程,也就是上两个原则中说到的抽象。
依赖倒置原则说的其实就是上面提到的 ImageLoader 不应该去依赖 ImageCache 的具体实现类,比如 MemoryCache 或者 DiskCache,而应该是通过接口或抽象来发生依赖关系,也就是后来加的 ImageCache 接口。
5. 接口隔离原则(Interface Segregation Principles)
客户端不应该依赖它不需要的接口。另一种定义是:类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小和更具体的接口,这样客户将会只需要知道他们感兴趣的接口。
接口隔离原则的含义其实就是将接口拆分的尽可能小,这样以便于重构。书中以 JDK 中的 Closable 接口举例,这个接口仅定义了一个 close 方法,所有 Closable 接口的实现类,例如 FileOutputStream 在调用 close 方法时都需要 catch IOException,于是书中写了一个工具类来解决这一问题。工具类代码如下:
public class CloseUtils {
private CloseUtils() {}
/**
* 关闭 Closable 对象
* @param closeable
*/
public static void closeQuietly(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
讲这个的目的其实是更好的帮助我们理解接口隔离原则,当把接口变得更小更具体之后,系统就有了更高的灵活性。
6. 迪米特原则(Law of Demeter,也称为最少知识原则 Least Knowledge Principle)
一个对象应该对其他对象有最少的了解。通俗的讲,一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者或者依赖者没关系,只需要知道它需要的方法即可。
其实迪米特原则说的和上面的原则也有点类似,首先就是要依赖抽象,作为依赖者,不需要知道方法是如何实现的,只需要知道依赖的对象有这个方法就好了,实现上的改变对它来说其实是不可见的。另一点就是尽可能减少依赖,书中以通过中介租房举例,作为租户,只需要依赖于中介即可,至于房东的房产证是不是真的这些细节不需要租户知道太多,所有的事情通过与中介沟通。











网友评论