设计原则之旅(二):开闭原则

来源:互联网 发布:unity3d工程师简历 编辑:程序博客网 时间:2024/06/05 16:54

这里写图片描述

定义:

Softeware entities like classes,modules and functions should be open for extension but closed for modifications.
(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)

问题由来:

在产品更新迭代阶段,随着需求的不断变化,原有的代码不能满足新的需求,需要对原有代码进行修改,在修改的过程中,可能会对原有代码引入新的错误,从而增加系统风险与维护成本。

解决方案:

遵循开闭原则:在实体类的设计阶段,对可能变化的需求行为提取抽象接口,实体类只依赖于抽象接口,具体行为方式的实现对外开放,由调用方去实现。当需求变化的时候,只需扩展新的功能,而不需要修改原有的代码,做到对 扩展开放,对修改关闭。

举例说明:

需求: 实现一个图片加载器,用于加载网络图片显示在 ImageView 中,并且具备缓存功能。

示例代码:

图片加载器:

/** * 图片加载器 * Created by Speedy on 2017/4/10. */public class ImageLoader {    private static final String TAG = "ImageLoader";    private ExecutorService mExecutorService;    //图片缓存    private LruCache<String,Bitmap> mImageCache;    private static ImageLoader mImageLoader;    public static synchronized ImageLoader getImageLoader(){        if(mImageLoader == null){            mImageLoader = new ImageLoader();        }        return mImageLoader;    }    private ImageLoader(){        initThreadPool();        initCache();    }    //初始化线程池    private void initThreadPool() {        //线程池,线程数目为 CPU 的数目        mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());    }    //初始化缓存    private void initCache() {        //计算可使用最大的内存        final int maxMenory = (int) (Runtime.getRuntime().maxMemory()/1024);        //取四分之一作为缓存        final int cacheSize = maxMenory / 4;        mImageCache = new LruCache<String,Bitmap>(cacheSize){            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getRowBytes()* value.getHeight() / 1024;            }        };    }    //加入缓存    public void put(String url,Bitmap bitmap){        mImageCache.put(url,bitmap);    }    //获取缓存图片    public Bitmap get(String url){        return mImageCache.get(url);    }    //显示图片    public void diaplayImage(@NonNull final String url,@NonNull final ImageView imageView){        imageView.setTag(url);        mExecutorService.submit(new Runnable() {            @Override            public void run() {                Bitmap bitmap = get(url);//获取缓存图片                if(bitmap == null){                    bitmap = downloadImage(url);                    if(bitmap == null){                        Log.e(TAG, "图片下载失败: url = "+ url );                        return;//图片下载失败,                    }                }                if(url.equals(imageView.getTag())){                    bindToImageView(bitmap,imageView);//显示在控件上                }                //缓存图片                put(url,bitmap);            }        });    }    //下载网络图片    private 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;    }    //将 Bitmap 显示在 ImageView 中    private void bindToImageView(final Bitmap bitmap,final ImageView imageView){        imageView.post(new Runnable() {            @Override            public void run() {                imageView.setImageBitmap(bitmap);            }        });    }}

测试 Activity

public class ImageTestActivity extends Activity {    private ImageView imageView;    private Button button;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_image_test);        imageView = (ImageView) findViewById(R.id.imageView);        button = (Button) findViewById(R.id.button);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String url = "http://avatar.csdn.net/6/B/9/1_easyer2012.jpg";                ImageLoader loader = ImageLoader.getImageLoader();                loader.diaplayImage(url,imageView);            }        });    }}

布局文件

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:gravity="center"    >    <ImageView        android:id="@+id/imageView"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@drawable/ic_launcher"        />    <Button        android:id="@+id/button"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="50dp"        android:text="开始加载网络图片"/></LinearLayout>

示例代码分析

ImageLoader 类的设计,初看起来,好像没什么太多问题,“ 完全 ”满足需求。 但是如果需求变化,就会发现,ImageLoader 类的设计的扩展性非常差,假如用户需要修改缓存策略,则 ImageLoader 类就需要修改源代码,或者,用户需要对加载后的图片做圆角处理,同样需要修改 ImageLoader 类。

问题分析

ImageLoader 类扩展性差的主要原因是,ImageLoader类的设计违背的两大设计原则:

  • 违背 单一职责原则
    ImageLoader 类既负责了图片的加载功能,又负责图片缓存功能的具体实现。

  • 违背 开闭原则
    ImageLoader 类的图片缓存功能功能是属于变化的行为,不应该将具体实现写在ImageLoader 类, 应该提取抽象行为,ImageLoader 类只依赖抽象接口,具体实现交给外界实现,便于扩展,做到对扩展开放,对修改关闭。

优化后代码:

说明:ImageLoader 类的设计存在许多需要待优化的地方,本文讲解重点是开闭原则,所以,只是以缓存的优化作为一个切入点作为例子讲解。

1 , 提取图片缓存接口

public interface ImageCache {    //加入缓存    public void put(String url,Bitmap bitmap);    //获取缓存图片    public Bitmap get(String url);}

2 , 实现具体缓存方式

/** * 内存缓存图片 * Created by Speedy on 2017/4/10. */public class MenoryCache implements ImageCache {    private LruCache<String,Bitmap> mImageCache;    public MenoryCache(){        //计算可使用最大的内存        final int maxMenory = (int) (Runtime.getRuntime().maxMemory()/1024);        //取四分之一作为缓存        final int cacheSize = maxMenory / 4;        mImageCache = new LruCache<String,Bitmap>(cacheSize){            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getRowBytes()* value.getHeight() / 1024;            }        };    }    //加入缓存    public void put(String url,Bitmap bitmap){        mImageCache.put(url,bitmap);    }    //获取缓存图片    public Bitmap get(String url){        return mImageCache.get(url);    }}

3 , ImageLoader 与 ImageCache 建立依赖关系

/** * 图片加载器 * Created by Speedy on 2017/4/10. */public class ImageLoader {    private static final String TAG = "ImageLoader";    private ExecutorService mExecutorService;    //缓存接口    private ImageCache mImageCache;    private static ImageLoader mImageLoader;    public static synchronized ImageLoader getImageLoader(){        if(mImageLoader == null){            mImageLoader = new ImageLoader();        }        return mImageLoader;    }    private ImageLoader(){        initThreadPool();        initCache();    }    //初始化线程池    private void initThreadPool() {        //线程池,线程数目为 CPU 的数目        mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());    }    //初始化缓存    private void initCache() {        mImageCache = new MenoryCache();    }    //提供扩展    public void setImageCache(ImageCache imageCache) {        this.mImageCache = imageCache;    }    //显示图片    public void diaplayImage(@NonNull final String url,@NonNull final ImageView imageView){        imageView.setTag(url);        mExecutorService.submit(new Runnable() {            @Override            public void run() {                Bitmap bitmap = mImageCache.get(url);//获取缓存图片                if(bitmap == null){                    bitmap = downloadImage(url);                    if(bitmap == null){                        Log.e(TAG, "图片下载失败: url = "+ url );                        return;//图片下载失败,                    }                }                if(url.equals(imageView.getTag())){                    bindToImageView(bitmap,imageView);//显示在控件上                }                //缓存图片                mImageCache.put(url,bitmap);            }        });    }    //下载网络图片    private 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;    }    //将 Bitmap 显示在 ImageView 中    private void bindToImageView(final Bitmap bitmap,final ImageView imageView){        imageView.post(new Runnable() {            @Override            public void run() {                imageView.setImageBitmap(bitmap);            }        });    }}

4,后期如果客户缓存策略有变,只需要实现 ImageCache 接口扩展功能,而 ImageLoader 这无须做任何修改,从而做到了对扩展开放,对修改关闭,及满足了开闭原则。例如:加入后期客户需要实现 SD 卡缓存功能 。

/** * Created by Speedy on 2017/4/10. */public class DiskCache implements ImageCache {    ...    @Override    public void put(String url, Bitmap bitmap) {        //讲图片保持只SD 卡中    }    @Override    public Bitmap get(String url) {        //从SD卡中获取缓存图片        return null;    }}

5,动态修改缓存策略

  ImageLoader loader = ImageLoader.getImageLoader();  loader.setImageCache(new DiskCache());

欢迎关注微信公众号:“ Android 之旅 ”,一起学习 、交流 。

这里写图片描述