设计模式六大原则(2):开闭原则

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

场景:在软件的生命周期内,产品需要不断的升级、维护,没有一个产品从第一个版本开发完就不需要再变化了,除非在下个版本诞生之前它已经终止了。而产品的升级需要修改原来的代码,这可能会给原来的代码中引入错误,影响原有的软件模块。

解决方案:遵守开闭原则。程序一旦开发完成,程序中一个类的实现只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类实现,新建的类可以通过继承的方式来重用原类的代码。简言之,当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

开闭原则指导我们,当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。这里的 “应该尽量” 4个字说明开闭原则并不是说绝对不可以修改原始类的,它只是一种指导思想,当我们嗅到原来的代码“腐化气味”时,应该尽早地重构,以使得代码恢复到正常的“进化”轨道,而不是通过继承等方式添加新的实现,这会导致冗余。我们的开发过程中也没有那么理想化的状况,完全地不用修改原来的代码,因此,在开发过程中需要自己结合具体情况进行考量,是通过修改旧代码还是通过继承使得软件系统更稳定、更灵活,在保证去除“代码腐化”的同时,也保证原有模块的正确性。

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

下面还用前一篇文章的例子来做说明(例子用的代码参考于《Android设计模式》一书)。现在有个新的需求:在图片加载类中,有内存缓存、磁盘缓存和双缓存,用户根据需要选择不同的缓存机制。

1.内存缓存类:

//内存缓存
public class MemoryCache {

    LruCache<String, Bitmap> mLruCache;

    public MemoryCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 4;
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    public Bitmap get(String url) {
        return return mLruCache.get(url);
    }

    public void put(String url, Bitmap bmp) {
        // do something
    }
}

2.磁盘缓存类:

//磁盘缓存
public class DiskCache {
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(url);
    }

    public void put(String url, Bitmap bmp) {
       //do something
    }
}

3.双缓存类:

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

    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap==null){
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    public void put(String url, Bitmap bmp) {
        //do something
    }
}

如果不考虑开闭原则,图片加载类中可能会这么写:

public class ImageLoader {

    MemoryCache mMemoryCache = new MemoryCache();
    DiskCache mDiskCache = new DiskCache();
    DoubleCache mDoubleCache = new DoubleCache();

    boolean isDiskCache = false; //使用磁盘缓存
    boolean isDoubleCache = false; //使用双缓存

    public ImageLoader() {
    }

    public void displayImage(String url, ImageView mImageView) {
        Bitmap bitmap = null;
        if (isDoubleCache) {
            bitmap = mDoubleCache.get(url);
        } else if (isDiskCache) {
            bitmap = mDiskCache.get(url);
        } else {
            bitmap = mMemoryCache.get(url);
        }
        if (bitmap != null) {
            mImageView.setImageBitmap(bitmap);
        } else {
            //没有缓存,下载图片
        }
    }

    public void setDiskCache(boolean diskCache) {
        isDiskCache = diskCache;
    }

    public void setDoubleCache(boolean doubleCache) {
        isDoubleCache = doubleCache;
    }
}

这样写可能会出现的问题:

1.每次加入新的缓存实现,都要修改ImageLoader类。

2.随着if-else的增多,代码变得越来约复杂、脆弱,一不小心还容易写错。

3.用户不能够实现自己的缓存,可扩展性差。

下面通过遵循开闭原则,来对上面的代码进行重构:

首先创建接口 ImageCache,用来抽象图片缓存的功能:

public interface ImageCache {
    void put(String url, Bitmap bmp);
    Bitmap get(String url);
}

ImageCache接口定义了获取、缓存图片的功能,让内存缓存、磁盘缓存和双缓存都实现该接口:

1.内存缓存类:

//内存缓存
public class MemoryCache implements ImageCache {

    LruCache<String, Bitmap> mLruCache;

    public MemoryCache() {
        //初始化LruCache
    }

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

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

2.磁盘缓存类:

//磁盘缓存
public class DiskCache implements ImageCache{

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

    @Override
    public Bitmap get(String url) {
       return BitmapFactory.decodeFile(url); //从文件中获取Bitmap
    }
}

3.双缓存类:

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

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

    @Override
    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }
}

ImageLoader 类就可以改写成:

public class ImageLoader {

    ImageCache mImageCache = new MemoryCache();

    public void setImageCache(ImageCache imageCache) {
        mImageCache = imageCache;
    }

    public ImageLoader() {
    }

    public void displayImage(String url, ImageView mImageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            mImageView.setImageBitmap(bitmap);
        }
        //图片没缓存,下载
        // do something
    }
}

ImageLoader 类中增加了一个setImageCache方法,用户可以通过此方法来设置缓存实现。用户端可以这样写:

ImageLoader imageLoader = new ImageLoader();
//选择使用内存缓存
imageLoader.setImageCache(new MemoryCache());
//选择使用磁盘缓存
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;
    }
});

在上述代码中,通过setImageCache(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高。MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是,它们的一个特点是都实现了ImageCache接口。当用户需要自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)注入到ImageLoader中,这样ImageLoader就实现了变化万千的缓存策略,而扩展这些缓存策略并不会导致ImageLoader类的修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值