开闭原则

原文链接:https://www.zybuluo.com/Tyhj/note/1192305

定义

软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的

在软件的生命周期内,因为变化、升级和维护等原因需要对软件源码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有的系统。因此当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。当然,在现实开发中只通过继承的方式来升级、维护原有的系统只是理想化的愿景,在实际的开发中修改原有代码、扩展代码往往是同时存在的。

开闭原则认为:程序一旦开发完成,程序中的一个类的实现只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类实现,新建的类可以通过继承的方式来重用原类的代码

举个栗子

在之前的图片加载框架,新增本地缓存功能,tag 1.2

public class DiskCache {

    //保存位置
    String cacheDir = "sdcard/cache/";

    //获取图片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    //保存图片到本地
    public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

新增了图片缓存到SD卡中,所以ImageLoad代码有更新

public class ImageLoader {


    //内存缓存
    ImageCache mImageCache = new ImageCache();

    //SD卡缓存
    DiskCache mDiskCache = new DiskCache();

    //是否使用本地缓存
    boolean isUseDiskCache = false;

    public void setUseDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }

    //线程池,线程数量为cpu的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView, final Activity context) {

        final Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
        if (bitmap != null) {
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
            return;
        }
        imageView.setTag(url);

        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                final Bitmap bitmap = downLoadImage(url);
                if (isUseDiskCache) {
                    mDiskCache.put(url, bitmap);
                } else {
                    mImageCache.put(url, bitmap);
                }

                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }

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

}

代码完成,但是功能不是很合理,因为两种缓存不能同时使用,实际上,先从内存获取没有找到再从本地读取,本地也没有再从网络获取,才是最好的策略。

新增一个双重缓存的类

public class DoubleCache {

    ImageCache imageCache = new ImageCache();
    DiskCache diskCache = new DiskCache();


    /**
     * 双缓存,获取图片的时候先从内存中读取,如果没有再从SD卡中读取
     *
     * @param url
     * @return
     */
    public Bitmap get(String url) {
        Bitmap bitmap = imageCache.get(url);
        if (bitmap == null) {
            bitmap = diskCache.get(url);
            imageCache.put(url, bitmap);
        }
        return bitmap;
    }

    //将图片缓存到内存和SD卡中
    public void put(String url, Bitmap bitmap) {
        imageCache.put(url, bitmap);
        diskCache.put(url, bitmap);
    }

}

再次修改ImageLoad代码

public class ImageLoader {


    //内存缓存
    ImageCache mImageCache = new ImageCache();

    //SD卡缓存
    DiskCache mDiskCache = new DiskCache();

    //双重缓存
    DoubleCache mDoubleCache = new DoubleCache();

    //是否使用本地缓存
    boolean isUseDiskCache = false;

    //双缓存
    boolean isUseDoubleCache = false;

    public void setUseDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }

    public void setUseDoubleCache(boolean useDoubleCache) {
        isUseDoubleCache = useDoubleCache;
    }

    //线程池,线程数量为cpu的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView, final Activity context) {

        Bitmap bitmap = null;

        if (isUseDoubleCache) {
            bitmap = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bitmap = mDiskCache.get(url);
        } else {
            bitmap = mImageCache.get(url);
        }

        if (bitmap != null) {
            final Bitmap finalBitmap = bitmap;
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(finalBitmap);
                }
            });
            return;
        }
        imageView.setTag(url);

        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                final Bitmap bitmap = downLoadImage(url);
                if (isUseDoubleCache) {
                    mDoubleCache.put(url, bitmap);
                } else if (isUseDiskCache) {
                    mDiskCache.put(url, bitmap);
                } else {
                    mImageCache.put(url, bitmap);
                }
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }

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

}

双缓存完成了,但是存在一些问题,每次添加新功能的时候都要去修改ImageLoader源码,很可能引入bug,而且使得原来的代码越来越复杂、脆弱,不小心写错一个if条件就需要花费时间来排查,而且用户使用我们的框架,不能自己实现缓存注入到ImageLoader中,可扩展性差

image_1cgr988tvpcp1lj2j3thlg1moo22.png-45.7kB
image_1cgr988tvpcp1lj2j3thlg1moo22.png-45.7kB

开闭原则重构代码,ImageLoader

public class ImageLoader {


    //内存缓存
    ImageCache mImageCache = new MemoryCache();

    public void setmImageCache(ImageCache mImageCache) {
        this.mImageCache = mImageCache;
    }


    //线程池,线程数量为cpu的数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView, final Activity context) {

        Bitmap bitmap = mImageCache.get(url);

        if (bitmap != null) {
            final Bitmap finalBitmap = bitmap;
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(finalBitmap);
                }
            });
            return;
        }
        imageView.setTag(url);

        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                final Bitmap bitmap = downLoadImage(url);
                mImageCache.put(url, bitmap);
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }


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

}

缓存继承ImageCache接口

public interface ImageCache {
    public Bitmap get(String url);

    public void put(String url, Bitmap bmp);
}

使用方法:

//双重缓存
imageLoader = new ImageLoader();
imageLoader.setmImageCache(new DoubleCache());
imageLoader.displayImage(url, iv_test1, this);


//自定义缓存栗子
 imageLoader.setmImageCache(new ImageCache() {
            @Override
            public Bitmap get(String url) {
                return null;
            }

            @Override
            public void put(String url, Bitmap bmp) {

            }
        });

重构完成

通过setImageCache(IamgeCache iamgeCache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得Imageloader的灵活性、可扩展性更高。只需要新建一个实现ImageCache接口的类,注入到ImageLoader中,ImageLoader就可以实现各种各样的缓存策略,而且这些缓存策略不会导致ImageLoader类的修改。

开闭原则的理解

用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

该图片加载框架集成方法:

//Step 1. Add the JitPack repository to your build file
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

//Step 2. Add the dependency
dependencies {
            implementation 'com.github.tyhjh:DesignMode:v1.3'
    }

参考:《Android源码设计模式解析与实战》一书

推荐阅读更多精彩内容