面向对象六大原则(二):开闭原则

时间:2022-01-15 14:41:15
一、概述
开闭原则(Open Close Principle,缩写:OCP),软件中的对象(类、模块、函数等)应该对于扩展是“开放”的,但是对于修改是“封闭”的。通俗点讲就是软件系统中包含的各种组件应该在不修改现有代码的基础上引入新功能。“开”是对于组件功能的扩展是开放的,是允许对其进行功能扩展的;“闭”是对于原有代码的修改是封闭的,即不应该修改原有代码。
二、特征
让程序更稳定、更灵活:
1.对于扩展是开放的:当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。 2.对于修改是封闭的:对模块行为进行扩展时,不必改动模块的源代码或者二进制码。
三、实现方法
实现开闭原则的关键在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。下面引用《Android源码设计模式解析与实战》一书中图片加载器(ImageLoader)的代码例子(是在我上一篇博客“面向对象六大原则(一):单一职责原则”代码例子根据开闭原则进行新功能的添加及优化):
1.图片缓存接口,用来抽象图片缓存的功能:
public interface ImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bmp);
}

2.ImageCache接口简单定义了获取、缓存图片两个函数,缓存的key是图片的url,值是图片本身。内存缓存、SD卡缓存、双缓存都实现了该接口,以下是缓存类的实现:

/**
* 内存缓存MemoryCache类
*/
public class MemoryCache implements ImageCache{
private LruCache<String, Bitmap> mMemeryCache;

public MemoryCache(){
//初始化LRU缓存
}

@Override
public Bitmap get(String url) {
return mMemeryCache.get(url);
}

@Override
public void put(String url, Bitmap bmp) {
mMemeryCache.put(url, bmp);
}
}

/**
* SD卡缓存DiskCache类
*/
public class DiskCache implements ImageCache {
@Override
public Bitmap get(String url) {
return null;/*从本地文件中获取该图片*/
}

@Override
public void put(String url, Bitmap bmp) {
//讲Bitmap写入文件中
}
}

/**
*双缓存DoubleCache类
*/
public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();

//先从内存缓存中获取图片,如果没有,再从SD卡中获取
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if(bitmap == null){
bitmap = mDiskCache.get(url);
}
return bitmap;
}

//将图片缓存到内存和SD卡中
@Override
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
}


3.ImageLoader主程序代码重构
/**
* 图片加载类
*/
public class ImageLoader {
//图片缓存
ImageCache mImageCache = new MemoryCache();
//线程池,线程数量为CPU的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

//注入缓存实现
public void setImageCache(ImageCache cache){
mImageCache = cache;
}

public void displayImage(final String imageUrl, final 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.submit(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);
}
});
}

public Bitmap downloadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
}catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
}

4.用户可通过setImageCache(ImageCache cache)方法注入不同的缓存实现,不仅能使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高。

ImageLoader imageLoader = new ImageLoader();
//使用内存缓存
imageLoader.setImageCache(new MemoryCache());
//使用SD卡缓存
imageLoader.setImageCache(new DiskCache());
//使用双缓存
imageLoader.setImageCache(new DoubleCache());
//使用自定义的图片缓存实现
imageLoader.setImageCache(new ImageCache(){

@Override
public void put(String url, Bitmap bmp){
//缓存图片
}

@Override
public Bitmap get(String url){
return null;/*从缓存中获取图片*/
}
});

通过以上 代码 可知MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是都实现了ImageCache接口。当用户需要自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)注入到ImageLoader中,这样ImageLoader就实现了千变万化的缓存策略,且扩展这些缓存策略并不会导致ImageLoader类的修改。

四、分析          开闭原则指导我们,当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。这里强调“应该尽量”是说明OCP原则并不是说绝对不可以修改原始类的。
当我们嗅到原来的代码“腐化气味”时,应该尽早的重构,以便使代码恢复到正常的“进化”过程,而不是通过继承等方式添加新的实现,这会导致类型的膨胀以及历史遗留代码的冗余。
我们的开发过程并不是理想化的(达到完全不用修改原来的代码),要结合实际情况进行考量,看是否通过修改旧代码或者是继承使得软件系统更稳定、更灵活,在保证去除“代码腐化”的同时,也保证原有模块的正确性。