为了账号安全,请及时绑定邮箱和手机立即绑定

设计模式之六大原则解析

标签:
Android

原文链接:http://www.apkbus.com/blog-705730-62838.html

列举出设计模式中的六大原则应该如何实现

单一职责原则 SRP

Single Responsibility Principle

场景: 如果让你写出一个图片缓存类, 要求内部实现缓存策略, 并提供方法只需要传递控件和图片地址就可以自动设置背景的类.

先给出最简单直接的写法.

public class ImageLoader {

      /**

       * 图片的缓存

       */

      LruCache<String , Bitmap> mImageCache;

      /**

       * 线程池

       */

      ExecutorService mExecutorService =   Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public ImageLoader(){

          // 初始化内存缓存策略

          initImageCache();

      }

      private void initImageCache() {

          // 获得可使用的最大内存

          final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

          // 设置 1/4 的最大内存为作为缓存

          final int cacheSize = maxMemory / 4;

          mImageCache = new   LruCache<String,Bitmap>(cacheSize){

            @Override

            protected int sizeOf(String key, Bitmap value) {

                //    返回缓存的bitmap大小

                return value.getRowBytes() * value.getHeight()   /1024 ;

            }

          };

      }

      public void displayImage(final ImageView iv, final String imgUrl){

          // 获取缓存

          Bitmap bitmap = mImageCache.get(imgUrl);

          if(null != bitmap){

             iv.setImageBitmap(bitmap);

             return;

          }

          // 网络加载

          iv.setTag(imgUrl);

          mExecutorService.submit(new Runnable() {

            @Override

            public void run() {

                Bitmap bitmap =   downloadImage(imgUrl);

                if (null == bitmap)

                    return ;

                if (iv.getTag().equals(imgUrl)) {

                      iv.setImageBitmap(bitmap);

                }

                mImageCache.put(imgUrl,bitmap);

            }

          });

      }

      /**

       * 根据图片的url下载图片并转换成bitmap对象返回

       */

      public Bitmap downloadImage(String url){

          Bitmap bitmap = null;

          try {

            URL url1 = new URL(url);

            HttpURLConnection conn = (HttpURLConnection)   url1.openConnection();

            bitmap =   BitmapFactory.decodeStream(conn.getInputStream());

            conn.disconnect();

          } catch (Exception e) {

            e.printStackTrace();

          }

          return bitmap;

      }

}

```

对于功能来说这是没有问题的. 但是接下来分析一下. 这样写会有什么弊端.

1. 首先这个类的职责包括了两个`控件设置背景的逻辑`和`图片下载的逻辑`. 耦合性太强,

2. `类的职责过多`那么就会造成类中的代码更多,两个逻辑的代码会交叉分布. 不易被阅读.

3. `扩展性`随着后续的实现`磁盘缓存`修改`加载图片`逻辑等会让代码越来越复杂难懂.

那么重构一下, 让两个职责分离开来.

```java

/**

 * 处理图片的加载

 */

public class ImageLoader {

      /**

       * 图片的缓存

       */

      ImageCache mImageCache = new ImageCache();

      /**

       * 线程池

       */

      ExecutorService mExecutorService =   Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

      public void displayImage(final ImageView iv, final String imgUrl){

          // 获取缓存

           Bitmap bitmap = mImageCache.getCache(imgUrl);

          if(null != bitmap){

            iv.setImageBitmap(bitmap);

            return;

          }

          // 网络加载

          iv.setTag(imgUrl);

          mExecutorService.submit(new Runnable() {

            @Override

            public void run() {

                Bitmap bitmap =   downloadImage(imgUrl);

                if (null == bitmap)

                    return ;

                if (iv.getTag().equals(imgUrl)) {

                      iv.setImageBitmap(bitmap);

                }

                  mImageCache.putCache(imgUrl,bitmap);

            }

          });

      }

      /**

       * 根据图片的url下载图片并转换成bitmap对象返回

       */

      public Bitmap downloadImage(String url){

          Bitmap bitmap = null;

          try {

            URL url1 = new URL(url);

            HttpURLConnection conn =   (HttpURLConnection) url1.openConnection();

            bitmap =   BitmapFactory.decodeStream(conn.getInputStream());

            conn.disconnect();

          } catch (Exception e) {

            e.printStackTrace();

          }

          return bitmap;

      }

}

/**

 * 图片缓存逻辑处理类

 */

public class ImageCache {

      /**

       * 图片的缓存

       */

      LruCache<String , Bitmap> mImageCache;

    public ImageCache(){

          // 初始化内存缓存策略

          initImageCache();

      }

      private void initImageCache() {

          // 获得可使用的最大内存

          final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

          // 设置 1/4 的最大内存为作为缓存

          final int cacheSize = maxMemory / 4;

          mImageCache = new   LruCache<String,Bitmap>(cacheSize){

            @Override

            protected int sizeOf(String key, Bitmap value) {

                //    返回缓存的bitmap大小

                return value.getRowBytes() * value.getHeight()   /1024 ;

            }

          };

      }

      /**

       * 提供一个对 bitmap 进行缓存的方法

       */

      public void putCache(String imgUrl, Bitmap bitmap){

          mImageCache.put(imgUrl, bitmap);

      }

      /**

       * 对外提供一个 获取缓存的方法

       */

      public Bitmap getCache(String imgUrl){

          return mImageCache.get(imgUrl);

      }

}

将原类拆分为两个类之后, 每个类的职责变得清晰. 如果需要更改缓存策略那么只需要修改ImageCache类总逻辑. 如果需要替换HttpURLConnection为OKHttp那么只需要在ImageLoader类中修改. 这样充分了体现了一个类单一职责SRP的好处, 清晰, 排除了修改时的多余干扰代码.

开闭原则 OCP

全称Open Close Principle

定义: 对于对象应该对于扩展是开放的, 对于修改是封闭的. 就是在后续的功能添加的时候要尽可能做到不修改已存在的代码! 就是通过继承``接口的特性来实现的. 就是我们口中常说的面向接口编程

不多大理论, 代码中会有真相.

还是上面的代码, 这个时候我们如果想增添一个二级缓存,添加一个磁盘的缓存那么代码就如下了

/**

 * 磁盘缓存

 */

public class DiskCache {

      public static final String cacheDir = "sdcard/cache/";

      // 从磁盘中获取缓存

      public Bitmap getCache(String imgUrl){

          return   BitmapFactory.decodeFile(cacheDir+imgUrl);

      }

      // 向磁盘中缓存

      public void putCache(String imgUrl, Bitmap bitmap){

          try(FileOutputStream fileOutputStream = new FileOutputStream(cacheDir+imgUrl)){

            // bitmap压缩到本地文件

              bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);

          } catch (IOException e) {

            e.printStackTrace();

          }

      }

}

// 还需要对ImageLoader类进行部分修改 只贴出添加的代码

/**

 * 处理图片的加载

 */

public class ImageLoader {

      /**

       * 磁盘缓存

       */

      DiskCache mDiskCache = new DiskCache();

      /**

       * 设置一个标记表示是否开启磁盘缓存

       */

      public boolean isUseDiskCache = false;

     

      public void displayImage(final ImageView iv, final String imgUrl){

          // 获取缓存

           Bitmap bitmap = mImageCache.getCache(imgUrl);

          if(null != bitmap){

            iv.setImageBitmap(bitmap);

            return;

          }

          // 判断磁盘缓存

          if (isUseDiskCache){

            bitmap =   mDiskCache.getCache(imgUrl);

            if (null != bitmap)

                return;

          }

         

          // 省略相同的网络下载代码...     

      }

      public void setUseDiskCache(boolean useDiskCache) {

          isUseDiskCache = useDiskCache;

      }

}

至此初步的添加功能需求已经完成, 增加了一个DiskCache类实现磁盘缓存, 并在ImageLoader类中进行判断使用的代码.

分析: 现在的加载可以有两种.一种是只使用LruCache缓存, 另一种是实现两种缓存同时实现. 那么这样的代码会有怎样的问题?

·         问题1: 如果想实现单磁盘缓存? 那么必须再对ImageLoader进行修改. 添加判断条件. 并且三种缓存策略if的判断也就比较多.

·         问题2: 如果用户想实现自定义缓存? 呵呵哒. 目前的代码没这么厉害的扩展性…

那么看一下UML类图


可以看出只要添加一个缓存策略就要建立一个依赖关系.

那么如果通过接口的方式来修改代码呢. 看一下…

/**

 * 缓存接口

 */

public interface BaseCache {

      // 添加缓存的抽象方法

      void putCache(String imgUrl, Bitmap bitmap);

      // 获取缓存的抽象方法

      Bitmap getCache(String   imgUrl);

}

/**

 * 双缓存

 */

public class DoubleCache implements BaseCache {

      BaseCache mMemoryCache = new ImageCache();

      BaseCache mDiskCache = new DiskCache();

      @Override

      public void putCache(String imgUrl, Bitmap bitmap) {

          // 缓存

          mMemoryCache.putCache(imgUrl, bitmap);

          mDiskCache.putCache(imgUrl, bitmap);

      }

      @Override

      public Bitmap getCache(String imgUrl) {

          // 先从缓存取没有再从sd

          Bitmap cache = mMemoryCache.getCache(imgUrl);

          if (null == cache){

            cache =   mDiskCache.getCache(imgUrl);

          }

          return cache;

      }

}

/**

 * 图片缓存逻辑处理类

 */

public class ImageCache  implements BaseCache{

      /**

       * 图片的缓存

       */

      LruCache<String , Bitmap> mImageCache;

      public ImageCache(){

          // 初始化内存缓存策略

          initImageCache();

      }

      private void initImageCache() {

          // 获得可使用的最大内存

          final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

          // 设置 1/4 的最大内存为作为缓存

          final int cacheSize = maxMemory / 4;

          mImageCache = new LruCache<String,Bitmap>(cacheSize){

            @Override

            protected int sizeOf(String key, Bitmap value) {

                //    返回缓存的bitmap大小

                return value.getRowBytes() * value.getHeight()   /1024 ;

            }

          };

      }

      /**

     * 提供一个对 bitmap 进行缓存的方法

       */

      @Override

      public void putCache(String imgUrl, Bitmap bitmap){

          mImageCache.put(imgUrl, bitmap);

      }

      /**

       * 对外提供一个 获取缓存的方法

       */

      @Override

      public Bitmap getCache(String imgUrl){

          return mImageCache.get(imgUrl);

      }

}

/**

 * 处理图片的加载

 */

public class ImageLoader {

      /**

       * 图片的缓存 默认只是内存缓存

       */

      BaseCache mImageCache = new ImageCache();

      /**

       * 注入缓存策略

       */

      public void setmImageCache(BaseCache mImageCache) {

        this.mImageCache = mImageCache;

      }

     

      /**

       * 线程池

       */

      ExecutorService mExecutorService =   Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

      public void displayImage(final ImageView iv, final String imgUrl){

          // 获取缓存 具体的缓存策略实现了依赖注入. 有调用者后续决定, 默认内存缓存

           Bitmap bitmap = mImageCache.getCache(imgUrl);

          if(null != bitmap){

            iv.setImageBitmap(bitmap);

            return;

          }

         

          // 网络加载

          iv.setTag(imgUrl);

          mExecutorService.submit(new Runnable() {

            @Override

            public void run() {

                Bitmap bitmap =   downloadImage(imgUrl);

                if (null == bitmap)

                    return ;

                if (iv.getTag().equals(imgUrl)) {

                      iv.setImageBitmap(bitmap);

                }

                  mImageCache.putCache(imgUrl,bitmap);

            }

          });

      }

      /**

       * 根据图片的url下载图片并转换成bitmap对象返回

       */

      public Bitmap downloadImage(String url){

          Bitmap bitmap = null;

          try {

            URL url1 = new URL(url);

            HttpURLConnection conn =   (HttpURLConnection) url1.openConnection();

            bitmap =   BitmapFactory.decodeStream(conn.getInputStream());

            conn.disconnect();

          } catch (Exception e) {

            e.printStackTrace();

          }

          return bitmap;

      }

}

上面代码没有单磁盘缓存的策略, 但是如果现在想要实现了这个功能, 怎么做? 只需要创建一个类实现BaseCache. 在创建ImageLoader的时候通过setmImageCache()来注入不同的实现. 不需要修改源代码, 并且ImageLoader中的if语句判断和布尔标记也完全不需要, 代码更加简洁.

看一下类图:


可以看到ImageLoader依赖了接口编程. 接口定义了缓存的共性方法. 在后续只要是其子类就可以使用.这不是满足了开闭原则的定义, 对修改封闭, 对于扩展开放了.

里氏替换原则 LSP

全称Liskov Substitution Principle

定义: 所有引用基类的地方必须能透明的使用其子类.

说白了就是Java中的继承和多态的特性. 父类可以直接引用子类类型. 比如Object可以引用任何类型的概念.

其实在上面的开闭原则中就已经存在了里氏替换

/**

* 注入缓存策略

*/

public void setmImageCache(BaseCache mImageCache) {

   this.mImageCache = mImageCache;

}

参数接收的BaseCache类型. 可以透明的引用任何的子类. 通过抽象实现了多种可能.

一般来说开闭原则和里氏替换是不离不弃, 生死相依的. 通过里氏替换达到了对扩展的开发, 对修改封闭的效果. 这两个原则同时实现了一个OOP的一个重要特性抽象

依赖倒置原则 DIP

全称:Dependence Inversion Principle

定义: 指代了一种特定的解耦方式, 是的高层次的模块不依赖于低层次的模块实现细节.

·         高层次模块不应该依赖低层次, 两者都应该依赖其抽象

·         抽象不应该依赖细节

·         细节应该依赖抽象

而在Java中的表现就是: 模块间的依赖通过抽象发生, 实现类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象产生的

直接看代码, 还是上面的ImageLoader类中

// 依赖于接口抽象,

BaseCache mImageCache = new ImageCache();

// 依赖于细节, 内存缓存

ImageCache mImageCache = new ImageCache();

// 依赖于细节, 双缓存

DoubleCache mImageCache = new DoubleCache();

如果依赖了内存缓存细节, 那么注入的缓存策略必须是ImageCache的子类. 但是这个子类已经具备一个细节的实现, 我们再去做其他细节的实现. 岂不是很怪异. 这个类中的出现的方法也是匪夷所思. 并且可能用户实现的具体策略也不一定是内存方法的缓存. 在命名上的限制也是很不友好.

总结一句话: 依赖抽象, 而不依赖具体实现

接口隔离原则 ISP

全称: Interface Segregation Principle

定义: 类间的依赖关系应该建立在最小的接口上.

接口隔离的原则就是让系统接口耦合, 从而更容易重构, 更改和重新部署.

对于操作IO或者网络我们总是需要在finally中确保资源的释放. 如下

FileOutputStream fileOutputStream = null;

try{

    fileOutputStream = new   FileOutputStream(cacheDir+imgUrl);

    bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);

}catch (IOException e) {

    e.printStackTrace();

}finally {

  try {

        fileOutputStream.close();

  }   catch (IOException e) {

        e.printStackTrace();

  }

}

这是多么蛋疼的代码… 一堆堆的花括号.

对于可关闭的对象, 都具备一个接口Closeable. 这个接口一个空接口, 也就是标识接口. 作用? 就是标识这个对象可以调用close()被关闭. 体现了一类对象的某一个特性. 而且这个特性建立在了最小接口的原则上.

那么编写这么一段代码

public class CloseUtils {

     

      // 整个方法通过最小接口的特性, 实现了隔离其他无用的属性. 只关心

      // Closeable接口即可. 接口隔离

     

      // 参数 对应了里氏替换的原则

      public static void fastClose(Closeable closeObj){

          if (null != closeObj){

            try {

                // close()方法的调用, 对应了 依赖倒置原则

                  closeObj.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

          }

      }

}

之前的代码调用就变成了

FileOutputStream fileOutputStream = null;

try{

    fileOutputStream = new   FileOutputStream(cacheDir+imgUrl);

    bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);

}catch (IOException e) {

    e.printStackTrace();

}finally {

    CloseUtils.fastClose(fileOutputStream);

}

不仅简单, 而且方便到处调用, 看起来也舒服多了.

迪米特原则 LOD

Law of Demeter

就是一个对象应该尽可能少的关联其他对象.

通俗讲, 一个类应该对自己需要耦合或调用的类知道的最少, 类的内部如何实现与调用者或者依赖者没有关系, 调用者或者依赖者只需要知道他需要的方法即可, 其他的一概不关心. 因为类与类之间的关系越密切, 耦合度也就越大, 当一个类发生改变时, 对另一个类的影响也会变大.


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消