面向对象六大规格

2.1 土憋程序员小明

小明设计DiskCache类,将图片缓存到SD内部存款和储蓄器卡中。

public class DiskCache{ static String cacheDir = "sdcard/cache/"; //从本地缓存中获取图片 public Bitmap get(String localUri){...} //将图片缓存到本地 public void put(String localUri, Bitmap bmp){...}}

因为急需将图纸缓存到SD存款和储蓄卡中,所以小明供给修改ImageLoader.java代码:

public class ImageLoader{ ... //SD卡缓存 DiskCache mDiskCache = new DiskCache(); //是否适用SD卡缓存 boolean isUseDiskCache = false; ... //修改加载图片中的缓存策略 public void displayImage(String url, ImageView imageView){ //判读使用哪种缓存 Bitmap bmp = isUseDiskCache?mDiskCache.get:mImageCache.get; ... } //Added:设置是否使用本地缓存加载 public void useDiskCache(boolean useDiskCache){...}}

由此useDiskCache()方法能够方便的让顾客设置缓存格局,小明比较快乐呀。后来意识这种陈设引人注目有标题,那正是顾客只好利用内部存款和储蓄器缓存和本地缓存的一种,所以小明新建三个双缓存类,完毕先内部存储器再本地的加载攻略。

public class DoubleCache{ ImageCache mMenoryCache = new ImageCache(); DiskCache mDiskCache = new DiskCache(); //先从内存中获取图片,获取不到再从SD获取图片 public Bitmap get(String url){...} //将图片缓存到内存和SD中 public void put(String url,Bitmap bmp){...}}

小明需求对ImageLoader更新:

public class ImagerLoader{ ... //双缓存 DoubleCache mDoubleCache = new DoubleCache(); //使用双缓存 boolean isUseDoubleCache = false; ... //Modified:修改加载图片策略 public void dispalyImage()(String url, ImageView imageView){ Bitmap bmp = null; if(isUseDoubleCache){ bmp = mDoubleCache.get; }else if(isUseDiskCache){ bmp = mDiskCache.get; }else{ bmp = mImageCache.get; } ... } //Added:设置是否使用双缓存加载 public void useDoubleCache(boolean useDoubleCache){...}}

小明每一趟投入新的缓存战术都急需修改ImageLoader类,然后通过if-else语句实行逻辑调控。随着类似那几个逻辑的加入,ImageLoader的代码变得愈加臃肿,软弱。假诺一相当大心写错了有个别if-else的条件,则须求成本大批量的时间来解决错误。最红要的是,ImageLoader的可增加性差,顾客不可能本人注入自定义完毕的缓存计策。可扩大性不过框架的最根本的天性。小明的方案很让人烦恼:一扩展,将要修改ImageLoader,一修改就便于出bug。

3、创设扩张性更好的系统——里氏替换原则

里氏替换原则德文全称是Liskov Substitution
Principle,简称LSP。它的第一种概念是:假若对每贰个品类为S的目的o1,都有等级次序为T的靶子o2,使得以T定义的保有程序P在装有的指标o1都代换来o2时,程序P的表现未有爆发变化,那么类型S是类型T的子类型。上面这种描述确实不太好明白,理论家不时候轻巧把难点抽象化,本来挺轻便驾驭的事让他们一归纳就弄得别扭了。我们再看看另一个简直了当的定义。里氏替换原则第三种概念:所有援引基类的地点必须能透明地选取其子类的指标。

咱俩理解,面向对象的语言的三大特色是承继、封装、多态,里氏替换原则正是依赖于继续、多态这两大特征。里氏替换原则简单来说正是,全体引用基类的地方必得能透明地选择其子类的指标。通俗点讲,只要父类能冒出的地点子类就能够出现,并且替换为子类也不会发出任何错误或特别,使用者或许一贯就无需精通是父类依旧子类。然而,反过来就老大了,有子类出现的地点,父类未必就能够适应。说了那么多,其实提起底总计就三个字:抽象。
小民为了深刻地驾驭Android中的Window与View的涉嫌非常写了三个简短示例,为了方便掌握,大家先看如图1-3所示。

图片 1

▲图1-3

咱俩看看实际的代码:

// 窗口类
public class Window {
    public void show(View child){
        child.draw();
    }
}

// 建立视图抽象,测量视图的宽高为公用代码,绘制交给具体的子类
public abstract class  View {
    public abstract void  draw() ;
    public void  measure(int width, int height){
        // 测量视图大小
    }
}

// 按钮类的具体实现
public class Button extends View {
    public void draw(){
        // 绘制按钮
    }
}
// TextView的具体实现
public class TextView extends View {
    public void draw(){
        // 绘制文本
    }
}

上述示范中,Window重视于View,而View定义了一个视图抽象,measure是逐条子类分享的形式,子类通过覆写View的draw方法落成全部各自特点的功效,在这里,这么些功用正是绘制自己的源委。任何承接自View类的子类都能够安装给show方法,也就咱们所说的里氏替换。通过里氏替换,就足以自定义异彩纷呈、变幻不测的View,然后传递给Window,Window肩负协会View,并且将View呈现到显示屏上。
里氏替换原则的主旨原理是抽象,抽象又凭仗于继续那个特点,在OOP个中,承袭的利害都一定醒目。
亮点如下:

  • (1)代码重用,降低创造类的资金财产,每种子类都富有父类的办法和品质;
  • (2)子类与父类基本相似,但又与父类有所区别;
  • (3)升高代码的可扩充性。

延续的劣势:

  • (1)承继是侵入性的,只要继续就非得具有父类的有所属性和措施;
  • (2)只怕产生子类代码冗余、灵活性收缩,因为子类必需持有父类的习性和艺术。

东西总是有着两面性,怎么着权衡利与弊都以内需凭借现实意况来做出取舍并加以管理。里氏替换原则辅导我们营造扩大性越来越好的软件系统,我们依旧接着上面的ImageLoader来做表明。
上文的图1-2也很好地影响了里氏替换原则,即MemoryCache、DiskCache、DoubleCache都能够替换ImageCache的劳作,并且能够确认保障行为的不错。ImageCache建立了获取缓存图片、保存缓存图片的接口标准,MemoryCache等凭借接口标准落实了相应的效用,顾客只必要在动用时钦命具体的缓存对象就足以动态地更迭ImageLoader中的缓存计谋。那就使得ImageLoader的缓存系统具有了有线的或者,也正是保障了可扩大性。

想像贰个场景,当ImageLoader中的setImageCache(ImageCache
cache)中的cache对象不能够被子类所替换,那么客户怎么样设置不一样的缓存对象以及顾客怎样自定义自身的缓存达成,通过1.3节中的useDiskCache方法吧?分明不是的,里氏替换原则就为那类难题提供了指引原则,也等于树立抽象,通过架空建设构造标准,具体的贯彻在运营时替换掉抽象,保险系统的高扩充性、灵活性。开闭原则和里氏替换原则往往是唇揭齿寒、不弃不离的,通过里氏替换成实现对扩充开放,对修改关闭的功力。可是,那四个条件都同一时间强调了贰个OOP的显要特点——抽象,因而,在开辟进度中运用抽象是走向代码优化的要害一步。

6.2 改进

//房子有租金和面积大小两个属性
data class Room(val area: Float, val price: Float)

class Mediator {
    // 中介掌握着所有的房源信息
    val rooms = mutableListOf<Room>()

    init {
        (0..10).forEach {
            rooms.add(Room(10 + it.toFloat(), (10 + it) * 150.toFloat()))
        }
    }


    fun rentRoom(roomArea: Float, roomPrice: Float): List<Room> {
        return rooms.filter { room ->
            room.area == roomArea
                    && room.price <= roomPrice
        }
    }

}

/**
 * 租房者。
 * 要求:15平方米,价格不能大于2000元。
 */
class Tenant(var roomArea: Float = 15f, var roomPrice: Float = 2000f) {

    fun rentRoom(mediator: Mediator) {
        val suitableRooms = mediator.rentRoom(roomArea, roomPrice)
        suitableRooms.forEach { println(it) }
    }
}

只与一直对象实行通讯,那个多少个字,就将我们从严重的耦合中解放了出去。


1、面向对象的六大规格

1、单一职分规范

  • 简简单单的说,二个类应该是一组相关性异常高的函数和多少的包装,约等于二个类二个作用。
  • 例如:二个图片加载器ImageLoader,大概多数代码都是封装成一个类,也正是图形的加载逻辑和图纸的缓存逻辑在一块儿,而采用单一义务规范,是把图纸加载逻辑和图纸缓存逻辑分开成多少个类,一个ImageLoader,二个ImageCache,那样ImageCache只担负图片的缓存,将ImageLoader的代码量收缩了,职务显明了,耦合度也暴跌,修改时分别的逻辑时也不会相互影响。

2、开闭原则

  • 此地深有体会,所谓的开闭原则,便是对增加开垦,对修改关闭,但实际没这么相对,应该是相应尽量通过增添格局来落到实处转换,实际不是通过更动原有代码来实现。

  • 还是ImageLoader为例:
    本来的ImageCache独有内部存款和储蓄器缓存,须要多一种本地缓存,好啊,运用单一义务标准,就分为多个类ImageLoader、MemoryCache、DiskCache,然后ImageLoader代码为:

    public class ImageLoader {
    
          MemoryCache mMemoryCache = new MemoryCache();
          DiskCache mDiskCache = new DiskCache();
          boolean isUseDiskCache = false;
    
          ExecutorService mExecutorService
                  = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
          public void display(String url, ImageView imageView){
              Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mMemoryCache.get(url);
              if (bitmap != null){
                  imageView.setImageBitmap(bitmap);
                  return;
              }
              //没有缓存就网络加载
          }
    
          public void setUseDiskCache(boolean useDiskCache){
              isUseDiskCache = useDiskCache;
          }
      }
    

    下边包车型地铁代码存在的主题材料,多了一个地点缓存,需求去修改ImageLoader的逻辑,须求扩大set方法,当然这里看不出什么,而实际项目中,但成效代码多复杂,很大概更动前面世BUG,那么运用开闭原则效果如下:
    率先定义接口ImageCache,让MemoryCache和DiskCache完成:

    public interface ImageCache {
    
      Bitmap get(String url);
    
      void put(String url, Bitmap bitmap);
    }
    //MemoryCache 
    public class MemoryCache implements ImageCache {
    
          private LruCache<String,Bitmap> mLruCache;
    
          public MemoryCache() {
              //初始化mLruCache
          }
    
          @Override
          public Bitmap get(String url) {
              return mLruCache.get(url);
          }
    
          @Override
          public void put(String url, Bitmap bitmap) {
              mLruCache.put(url,bitmap);
          }
      }
     //DiskCache 
    public class DiskCache implements ImageCache {
        @Override
        public Bitmap get(String url) {
            return null;   //本地获取
        }
    
        @Override
        public void put(String url, Bitmap bitmap) {
                //本地存
        }
    }
    

    接下去看ImageLoader:

    public class ImageLoader {
    
          private ImageCache mImageCache = new MemoryCache();//默认内存缓存
    
          ExecutorService mExecutorService
                  = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
          public void display(String url, ImageView imageView){
              Bitmap bitmap = mImageCache.get(url);
              if (bitmap != null){
                  imageView.setImageBitmap(bitmap);
                  return;
              }
              //没有缓存就网络加载
          }
    
         public void setImageCache(ImageCache imageCache){
             mImageCache = imageCache;
         }
      }
    

    旁观此间有很深的体味,使用就这么:

     ImageLoader imageLoader = new ImageLoader();
     imageLoader.setImageCache(new DiskCache()); 
    

    使用者也足以三番两次ImageCache去扩充,回头想下:对扩张开放,对修改关闭。

3、里氏替换原则

  • 概念:全部引用基类的地方必需能够正确的利用其子类对象,其实正是后续和多态。
  • 地方的ImageCahe正是里氏替换原则,在ImageLoader中采纳了ImageCahe,它的子类都有put和set的效率,它们都能够替代ImageCahe专门的学业。

4、信赖倒置原则

  • 概念的东西很空虚,一句话概略正是面向接口编程,目标正是解耦。
  • 是的,照旧地方的ImageLoader,ImageLoader依赖于ImageCache并不是借助于MemoryCahe或许DiskCache,那样加载模块和缓存模块不直接产生涉及,通过接口直接产生关系。
  • 那样在来看信赖倒置原则的风味就能够清楚:高层模块不应有借助底层模块,两个应该依据抽象,高层模块正是应用钻探端,也正是ImageLoader,底层模块正是兑现类,也正是MemoryCache可能DiskCache,抽象正是接口ImageCache。

5、接口隔断原则

  • 简易,便是让顾客端信赖的接口尽恐怕的小,好吧,依旧很空洞,直接上个例子:
    地点的DiskCache必然要用到OutputSteam,
    它完成了八个叫Closeable的接口:

       try {
              outputStream.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
    

    很鲜明,看到try…catch很不爽,每一回都要写,果断封装下:

     public void close(Closeable closeable){
      if (closeable != null){
          try {
              closeable.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
    }
    

    这么升高了重用性,并且创建在小小的注重原则的基本功上,它只必要领悟那一个指标是可关闭,其余不关怀。通过Closeable接口将可关闭的目的抽象起来,那样客商纠正视于Closeable就足以对顾客端隐敝别的接口消息,客商端只必要领会那些目的可关闭即可。

6、迪米特原则

  • 深入显出的说,一个类应该对自身索要耦合或调用的类知道得最少,类的中间如何达成与调用者只怕注重这没提到,调用者或信赖者只须求掌握它须要的办法即可。

  • 以中介的事例为例,找中介找房:
    假定是如此:

    public class Room {
    
        public String area;
        public String price;
    
        public Room(String area, String price) {
            this.area = area;
            this.price = price;
        }
    }
    //中介
    public class Mediator {
    
         List<Room> mRooms = new ArrayList<>();
    
         public Mediator(){
             for(int i = 0;i < 5; i++){
                 mRooms.add(new Room(14+i,(14+i)*150));
             }
         }
    
         public List<Room> getRooms(){
             return mRooms;
         }
     }
    //租客
    public class Tenant {
    
          private float area = 60;
          private float price = 2000;
    
          public void rentRoom(Mediator mediator){
              for (Room room : mediator.getRooms()) {
                  if (room.area >= area && room.price <= price){
                      System.out.println("找到了");
                      return;
                  }
              }
          }
      }
    

    总的来看叁个难题,租客想找房,他不应当和Room打交道,约等于Tenant
    类不该出现Room类,分清关系,Room应该和Mediator打交道,Tenant只和Mediator打交道,那么如此:

    public class Mediator {
    
        List<Room> mRooms = new ArrayList<>();
    
        public Mediator(){
            for(int i = 0;i < 5; i++){
                mRooms.add(new Room(14+i,(14+i)*150));
            }
        }
    
        public Room rentOut(float area,float price){
            for (Room room : mRooms) {
                if (room.area >= area && room.price <= price){
                    return room;
                }
            }
            return null;
        }
    } 
    
    public class Tenant {
    
          private float area = 60;
          private float price = 2000;
    
          public void rentRoom(Mediator mediator){
             mediator.rentOut(area,price);
          }
      }
    

前言近年同事买了本Android设计方式的书,借来看看,感到还不易,做一下笔记呗。有意思味的同窗能够买原书看看:《Adroid
源码设计方式剖析与实战》 何红辉、关爱民 著
人民邮政和电信出版社
。小说是对本书的有个别列学习笔记,假设有侵略到小编权益,还望小编能联系自个儿,笔者会立马下架。感兴趣的仇敌款待出席上学小组QQ群:
一九三八65960

4、 让项目具有变化的技巧——正视倒置原则

注重倒置原则土耳其共和国(Türkiye Cumhuriyeti)语全称是Dependence Inversion
Principle,简称DIP。正视反转原则代替了一种特定的解耦格局,使得高档次的模块不正视于低档期的顺序的模块的贯彻细节的目标,信赖模块被颠倒了。那个定义有一点点不佳明白,那到底是何等意思呢?
依附倒置原则的多少个关键点:

  • (1)高层模块不应有依附低层模块,两个都应当借助其抽象;
  • (2)抽象不应当依据细节;
  • (3)细节应该依附抽象。

在Java语言中,抽象正是指接口或抽象类,两个都以不可能直接被实例化的;细节便是促成类,达成接口或继续抽象类而发出的类正是细节,其本性正是,能够一向被实例化,相当于能够加多二个主要字
new
产生二个对象。高层模块正是调用端,低层模块正是有血有肉完毕类。正视倒置原则在
Java
语言中的表现正是:模块间的信赖通过架空中爆炸发,达成类之间不发生径直的借助关系,其借助关系是经过接口或抽象类发生的。那又是三个将理论抽象化的实例,其实一句话就能够回顾:面向接口编制程序,大概说是面向抽象编制程序,这里的肤浅指的是接口或然抽象类。面向接口编制程序是面向对象精髓之一,约等于下面两节重申的空洞。

若是在类与类直接重视于细节,那么它们之间就有一贯的耦合,当实际完结内需转移时,意味着在那要同期修改依赖者的代码,并且限定了系统的可扩大性。大家看1.3节的图1-3中,ImageLoader直接正视于MemoryCache,这些MemoryCache是一个现实落实,并不是四个抽象类也许接口。那导致了ImageLoader直接信赖了实际细节,当MemoryCache不能够满意ImageLoader而急需被另外缓存达成替换时,此时就亟须修改ImageLoader的代码,比方:

public class ImageLoader {
    // 内存缓存 ( 直接依赖于细节 )
    MemoryCache mMemoryCache = new MemoryCache();
     // 加载图片到ImageView中
    public void displayImage(String url, ImageView imageView) {
       Bitmap bmp = mMemoryCache.get(url);
        if (bmp == null) {
            downloadImage(url, imageView);
        } else {
            imageView.setImageBitmap(bmp);
        }
    }

    public void setImageCache(MemoryCache cache) {
        mCache = cache ;
    }
    // 代码省略
}

乘势产品的晋升,客户开掘MemoryCache已经无法知足须要,客户须求小民的ImageLoader能够将图纸同一时候缓存到内部存款和储蓄器和icroSD存款和储蓄卡中,或许能够让客户自定义完成缓存。此时,大家的MemoryCache那么些类名不仅仅无法抒发内部存款和储蓄器缓存和SD内部存储器卡缓存的意义,也不可见满意功用。别的,顾客必要自定义缓存完成时还必需继续自MemoryCache,而客户的缓存完结可不必将与内部存款和储蓄器缓存有关,那在命名上的界定也让顾客体验不佳。重构的时候到了!小民的首先种方案是将MemoryCache修改为DoubleCache,然后在DoubleCache中落到实处具体的缓存作用。大家必要将ImageLoader修改如下:

public class ImageLoader {
    // 双缓存 ( 直接依赖于细节 )
    DoubleCache mCache = new DoubleCache();
    // 加载图片到ImageView中
    public void displayImage(String url, ImageView imageView) {
       Bitmap bmp = mCache.get(url);
        if (bmp == null) {
          // 异步下载图片
            downloadImageAsync(url, imageView);
       } else {
            imageView.setImageBitmap(bmp);
       }
    }

    public void setImageCache(DoubleCache cache) {
         mCache = cache ;
    }
    // 代码省略
}

大家将MemoryCache修改成DoubleCache,然后修改了ImageLoader中缓存类的切切实实贯彻,轻轻巧松就满足了顾客要求。等等!那不如故依据于现实的贯彻类(DoubleCache)吗?当客商的必要再一次转移时,大家又要经过改造缓存完成类和ImageLoader代码来完成?修改原有代码不是违反了1.3节中的开闭原则呢?小民忽然清醒了回复,低下头思虑着什么样技能让缓存系统越来越灵活、拥抱变化……

当然,这个都以在主办给出图1-2(1.3节)以及相应的代码此前,小民体验的患难进程。既然是那样,那明确主任给出的技术方案就可见让缓存系统进一步灵活。一句话回顾起来正是:重视抽象,而不借助具体落到实处。针对于图片缓存,高管创建的ImageCache抽象,该抽象中加进了get和put方法用以完毕图片的存取。每个缓存落成都必需贯彻那个接口,并且实现团结的存取方法。当顾客须要利用不一致的缓存达成时,直接通过重视注入就能够,保障了系统的面面俱圆。大家再来简单回看一下相关代码:

ImageCache缓存抽象:

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

ImageLoader类:

public class ImageLoader {
    // 图片缓存类,依赖于抽象,并且有一个默认的实现
    ImageCache mCache = new MemoryCache();

    // 加载图片
    public void displayImage(String url, ImageView imageView) {
       Bitmap bmp = mCache.get(url);
        if (bmp == null) {
        // 异步加载图片
            downloadImageAsync(url, imageView);
       } else {
            imageView.setImageBitmap(bmp);
       }
    }

    /**
     * 设置缓存策略,依赖于抽象
     */
    public void setImageCache(ImageCache cache) {
        mCache = cache;
    }
    // 代码省略
}

在此处,大家创制了ImageCache抽象,并且让ImageLoader正视于肤浅并不是现实性细节。当供给爆发转移时,小民只须求实现ImageCahce类恐怕接续别的已有个别ImageCache子类达成相应的缓存作用,然后将现实的落到实处注入到ImageLoader就可以达成缓存成效的替换,这就有限支撑了缓存系统的高可扩充性,具有了拥抱变化的技术,而这一体的核心指引标准便是大家的依赖倒置原则。从上述几节中大家发掘,要想让我们的系统越来越灵活,抽象就像是成了大家独一的一手。

3.2 继承

LSP的主导是抽象,抽象又依据于继续那几个特点。

承继的长处:
(1)代码重用,缩短创制类的开销,各类子类都享有父类的属性和措施。
(2)多态,子类与父类基本相似,但又与父类有所分化。
(3)提升代码的扩大性。

延续的破绽:
(1)承袭是侵入性的,子类必得怀有父类的脾性和方式。
(2)可能导致代码冗余,灵活性裁减。

Android 设计形式:面向对象的六大规范Android 设计情势:单例形式Android
设计方式:Builder形式Android 设计情势:原型格局Android
设计格局:工厂方法形式Android 设计方式:抽象工厂格局Android
设计方式:计谋方式

5、系统有越来越高的百发百中——接口隔断原则

接口隔绝原则葡萄牙共和国(República Portuguesa)语全称是InterfaceSegregation
Principles,简称ISP。它的概念是:客户端不该依附它不必要的接口。另一种概念是:类间的依附关系应该创设在相当小的接口上。接口隔开分离原则将那多少个变得强大、臃肿的接口拆分成为越来越小的和更具象的接口,那样客户将会只必要精通她们感兴趣的方式。接口隔绝原则的指标是系统解开耦合,进而轻松重构、改变和重新安顿。

接口隔开原则说白了便是,让客户端依赖的接口尽恐怕地小,那样说大概照旧有一点抽象,我们还是以贰个演示来验证一下。在此以前大家来讲八个情景,在Java
6以及在此之前的JDK版本,有一个非常讨厌的标题,这正是在采用了OutputStream或然别的可关闭的指标之后,大家不能不保障它们最后被关门了,我们的miniSD存款和储蓄卡缓存类中就有这么的代码:

// 将图片缓存到内存中
public void put(String url, Bitmap bmp) {
    FileOutputStream fileOutputStream = null;
    try {
        fileOutputStream = new FileOutputStream(cacheDir + url);
        bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (fileOutputStream != null) {
            try {
                fileOutputStream.close();
          } catch (IOException e) {
                e.printStackTrace();
          }
       } // end if
    } // end if finally
}

咱俩看到的这段代码可读性比较糟糕,种种try…catch嵌套,都以些轻易的代码,可是会严重影响代码的可读性,而且多层级的大括号很轻松将代码写到错误的层级中。我们应该对这类代码也不行恶感,那大家看看哪些消除那类难点。
我们兴许精晓Java中有贰个Closeable接口,该接口标志了三个可关闭的靶子,它唯有八个close方法,如图1-4所示。
大家要讲的FileOutputStream类就兑现了那些接口,大家从图1-4中得以看出,还可能有一百多少个类达成了Closeable那么些接口,那意味,在关闭这一百多少个等级次序的指标时,都须求写出像put方法中finally代码段那样的代码。那还了得!你能忍,反正小民是忍不了的!于是小民妄想要表明他的聪明智慧化解这么些难点,既然都以促成了Closeable接口,那假如本身建贰个方式统一来关闭这个目的不就可以了么?说干就干,于是小民写下去如下的工具类:

图片 2

▲图1-4

public final class CloseUtils {

    Private CloseUtils() { }

    /**
     * 关闭Closeable对象
     * @param closeable
     */
    public static void closeQuietly(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
       }
    }
}

我们再看看把这段代码应用到上述的put方法中的效果如何:

public void put(String url, Bitmap bmp) {
    FileOutputStream fileOutputStream = null;
    try {
        fileOutputStream = new FileOutputStream(cacheDir + url);
        bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
   } catch (FileNotFoundException e) {
        e.printStackTrace();
   } final ly {
        CloseUtils.closeQuietly(fileOutputStream);
   }
}

代码简洁了多数!并且以此closeQuietly方法能够利用到各个可关闭的指标中,保证了代码的重用性。CloseUtils的closeQuietly方法的基本原理就是借助于Closeable抽象并不是有血有肉落到实处(那不是1.4节中的信赖倒置原则么),况兼创造在最小化信赖原则的基础,它只要求知道这一个目的是可关闭,其余的一律不爱抚,也便是这里的接口隔开分离原则。

试想一下,如若在只是急需关闭二个目的时,它却暴揭穿了别的的接口函数,比如OutputStream的write方法,那就使得更多的内幕暴光在客商端代码面前,不仅仅未有很好地躲藏完成,还扩充了接口的选择难度。而由此Closeable接口将可关闭的对象抽象起来,那样只须求顾客端依赖于Closeable就足以对客户端掩饰其余的接口音讯,客商端代码只供给通晓那个指标可关闭(只可调用close方法)即可。小民ImageLoader中的ImageCache正是接口隔开原则的运用,ImageLoader只须要精通该缓存对象有存、取缓存图片的接口就能够,其余的一概不管,那就使得缓存效用的切实落实对ImageLoader具体的藏身。那正是用最小化接口隔绝了落到实处类的内部情形,也促使大家将十分大的接口拆分到越来越细粒度的接口当中,那使得大家的种类具有更低的耦合性,更加高的灵活性。

鲍勃大伯(罗Bert C
马丁)在21世纪前期将单纯职分、开闭原则、里氏替换、接口隔断以及借助倒置(也称为正视反转)5个标准定义为SOLID原则,指代了面向对象编程的5个着力尺度。当这个准则被联合行使时,它们使得二个软件系统更清楚、轻松、最大程度地拥抱变化。SOLID被优良地行使在测量试验驱动开拓上,况且是非常的慢开荒以及自适应软件开垦基本规范的主要组成部分。在经过第1.1~1.5节的学习之后,大家开采这几大条件最后就能够形成那多少个重大词:抽象、单一职分、最小化。那么在实际支付进程中怎么着权衡、施行那几个规范,是豪门需求在施行中多动脑筋与精晓,正所谓”学而不思则罔,思而不学则殆”,只有时时随处地读书、施行、思量,才具够在积存的历程有多少个质的飞越。

2.1 增加SD卡缓存

这两天了来了新的须求,大家除了能运用内部存款和储蓄器缓存外,还要能动用icroSD存款和储蓄卡缓存。

第一大家来落到实处SD读取卡缓存类。

class DiskCache {
    private val cacheDir = "sdcard/cache"
    fun put(url: String, bitmap: Bitmap) {
        var fous: FileOutputStream? = null
        try {
            fous = FileOutputStream(cacheDir + url)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fous)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                fous?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
    fun get(url: String): Bitmap? {
        return BitmapFactory.decodeFile(cacheDir + url)
    }
}

再看下ImageLoader怎么着使用DiskCache,省略了非关键代码。

class ImageLoader {

    private var mImageCache: ImageCache = ImageCache()

    private val mDiskCache: DiskCache = DiskCache()
    // 是否使用SD卡缓存
    private var isUseDiskCache = false

    // 设置是否使用SD卡缓存
    fun useDiskCache(useDiskCache: Boolean) {
        isUseDiskCache = useDiskCache
    }


    /**
     * 显示图片
     * @param url 图片url
     * @param imgView 待显示图片的ImageView
     */
    fun displayImage(url: String, imgView: ImageView) {
        // 判断是否使用SD卡缓存
        val bitmap = if (isUseDiskCache) mDiskCache.get(url) else mImageCache.get(url)
        if (bitmap != null) {
            imgView.setImageBitmap(bitmap)
            return
        }

        imgView.tag = url
        mExecutorService.submit {
            val bitmap = downloadImage(url) ?: return@submit
            if (imgView.tag == url) {
                imgView.setImageBitmap(bitmap)
            }
            mImageCache.put(url, bitmap)
            mDiskCache.put(url, bitmap)
        }
    }

}

看起来达成起来依然很简短,修改的代码量亦非相当的大。

我们刚强使用缓存的艺术存在难题的,在其实付出中,大家只是既使用内部存款和储蓄器,又采用CF内存卡缓存的。

4.3 总结

DIP的核心理想就是面向接口或面向抽象编程,为啥要那样做,还索要我们好好的咀嚼。

接口隔开原则:顾客端不应该借助他没有须求的接口,类间的依赖关系应该创设在小小的接口上。接口隔断原则将格外庞大臃肿的接口拆分成更加小的和更具象的接口,那样顾客将会只须求通晓她们感兴趣的接口的艺术。ISP的指标是系统解开耦合,进而轻巧重构、改造和重新安顿。

Bob小叔(罗Bert C
马丁)曾将单纯职分标准、开闭原则、里氏替换原则、接口隔断原则和注重性倒置原则这5个标准称为SOLID原则,作为面向对象开荒的着力条件。

迪米特原则:也变为最少知识标准化。一个目的应对另外对象有最少的摸底。通俗的讲,多个类应该对协和索要耦合大概调用的类知道的最少。还应该有二个解说便是:只与间接对象通讯。什么是一直对象吧?多个对象之间的耦合便是爱人关系,如整合、聚合、重视。

what a fucking
thing!反正小编是明亮不了这些定义,太肤浅了。举例嘛:通过中介找屋企。大家设定的景色为:租客只供给房间的面积和租金,其余一律不管;中介将符合需要的房屋都提须要作者。

6、更加好的可扩大性——迪米特原则

迪米特原则阿尔Barney亚语全称为Law of 德姆eter,简称LOD,也称为最少知识规范化(Least
Knowledge
Principle)。即使名字分化,但描述的是同一个标准:几个目的应当对别的对象有最少的问询。通俗地讲,三个类应该对和谐须求耦合或调用的类知道得最少,类的里边如何落实、如何复杂都与调用者或然注重者不妨,调用者恐怕依赖者只必要通晓她要求的办法就可以,其他的本人一概不关切。类与类之间的关联越细致,耦合度越大,当贰个类产生转移时,对另一个类的熏陶也越大。

迪米特别准予绳还应该有三个日语解释是:Only talk to your immedate
friends,翻译过来正是:只与直接的心上人通讯。什么叫做直接的相恋的人吗?各类对象都自然会与别的对象有耦合关系,多个指标时期的耦合就成为朋友关系,这种关联的品类有成都百货上千,举个例子组合、聚合、依赖等。

光说不练很空虚呐,上面大家就以租房为例来讲讲迪米特原则。
“北漂”的校友相比较明白,在香水之都租房绝大非常多都以透过中介找房。我们设定的境地为:笔者只须求房间的面积和租金,其余的个个不管,中介将适合作者供给的房子提需求本人就足以。上边大家看看那些示例:

/**
 * 房间
 */
public class Room {
    public float area;
    public float price;

    public Room(float  area, float  price) {
        this.area = area;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Room [area=" + area + ", price=" + price + "]";
    }

}

/**
 * 中介
 */
public class Mediator {
    List<Room> mRooms = new ArrayList<Room>();

    public Mediator() {
        for (inti = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 150));
       }
   }

    public List<Room>getAllRooms() {
        return mRooms;
   }
}


/**
 * 租户
 */
public class Tenant {
    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        List<Room>rooms = mediator.getAllRooms();
        for (Room room : rooms) {
            if (isSuitable(room)) {
             System.out.println("租到房间啦! " + room);
                break;
          }
       }
    }

    private boolean isSuitable(Room room) {
        return Math.abs(room.price - roomPrice) < diffPrice
                &&Math.abs(room.area - roomArea) < diffArea;
   }
}

从地方的代码中能够见到,Tenant不唯有凭仗了Mediator类,还亟需频仍地与Room类打交道。租户类的渴求只是透过中介找到一间适合本人的房子罢了,如若把这个检查测量检验标准都位于Tenant类中,那么中介类的功用就被弱化,况且变成Tenant与Room的耦合较高,因为Tenant必需清楚大多关于Room的细节。当Room变化时Tenant也必需随着变化。Tenant又与Mediator耦合,就招致了纠缠不清的关联。那年就须求大家分清何人才是我们确实的“朋友”,在我们所设定的图景下,显著是Mediator(纵然现实生活中不是那般的)。上述代码的结构如图1-5所示。

图片 3

▲图1-5

既然如此是耦合太严重,这大家就只好解耦了,首先要简明地是,大家只和大家的心上人通讯,这里就是指Mediator对象。必得将Room相关的操作从Tenant中移除,而那么些操作案例应该属于Mediator,我们开展如下重构:

/**
 * 中介
 */
public class Mediator {
    List<Room> mRooms = new ArrayList<Room>();

    public Mediator() {
        for (inti = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 150));
       }
    }

    public Room rentOut(float  area, float  price) {
        for (Room room : mRooms) {
            if (isSuitable(area, price, room)) {
                return  room;
          }
       }
        return null;
    }

    private boolean isSuitable(float area, float price, Room room) {
        return Math.abs(room.price - price) < Tenant.diffPrice
            && Math.abs(room.area - area) < Tenant.diffPrice;
    }
}

/**
 * 租户
 */
public class Tenant {

    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        System.out.println("租到房啦 " + mediator.rentOut(roomArea, roomPrice));
     }
}

重构后的布局图如图1-6所示。

图片 4

▲图1-6

只是将对于Room的论断操作移到了Mediator类中,那本应该是Mediator的天职,他们依照租户设定的条件查找符合供给的屋宇,并且将结果提交租户就能够了。租户并无需知道太多关于Room的细节,举个例子与房东签公约、房东的房产证是或不是实在、室内的道具坏驾驭后笔者要找何人维修等,当大家因而我们的“朋友”中介租了房之后,全数的事务大家都通过与中介调换就好了,房东、维修师傅等这一个剧中人物并不是大家平昔的“朋友”。“只与一直的对象通讯”那轻巧的多少个字就可见将大家从一无可取的关系网中抽离出来,使大家的耦合度更低、牢固性更加好。
透过上述示范以及小民的接轨思考,迪米特原则那把利剑在小民的手中一度舞得风生水起。就拿sd卡缓存来讲吧,ImageCache正是客户的直接对象,而TF读取卡缓存内部却是使用了jake
wharton的DiskLruCache完毕,那一个DiskLruCache就不属于客户的直白对象了,由此,客商完全无需驾驭它的留存,客户只要求与ImageCache对象打交道就可以。比如将图纸存到SD内部存款和储蓄器卡中的代码如下。

public void put(String url, Bitmap value) {
    DiskLruCache.Editor editor = null;
    try {
       // 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
        editor = mDiskLruCache.edit(url);
        if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
            if (writeBitmapToDisk(value, outputStream)) {
              // 写入disk缓存
                editor.commit();
          } else {
                editor.abort();
          }
            CloseUtils.closeQuietly(outputStream);
       }
    } catch (IOException e) {
         e.printStackTrace();
    }
}

客户在采取SDXC卡缓存时,根本不知晓DiskLruCache的达成,那就很好地对客户遮蔽了实际贯彻。当小民已经“牛”到能够协和产生SD闪存卡的rul实现时,他就可以随便的交替掉jake
wharton的DiskLruCache。小民的代码大意如下:

@Override
public  void put(String url, Bitmap bmp) {
    // 将Bitmap写入文件中
    FileOutputStream fos = null;
    try {
       // 构建图片的存储路径 ( 省略了对url取md5)
        fos = new FileOutputStream("sdcard/cache/" + imageUrl2MD5(url));
        bmp.compress(CompressFormat.JPEG, 100, fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if ( fos != null ) {
            try {
                fos.close();
          } catch (IOException e) {
                e.printStackTrace();
          }
       }
    } // end if finally
}

SDHC卡缓存的具体落到实处即使被调换了,但客户根本不会感知到。因为客商根本不亮堂DiskLruCache的留存,他们未有与DiskLruCache进行通讯,他们只认得直接“朋友”ImageCache,ImageCache将一切细节隐蔽在了第一手“朋友”的门面之下,使得系统全体更低的耦合性和更加好的可扩张性。

3.1 OOP三大规格

面向对象的三大亚湾原子核能发电站心尺度:封装、继承、多态。

版权归作者全体,如有转载,请表明小说出处:

  • 正文来源《Android源码设计方式解析与实战》中的第一章。

2.3 分析

咱俩地点的修改可能设计适合OCP原则吗?

很醒目不合乎,每一遍大家供给修改缓存完结时,都急需修改ImageLoader,那就违背了对扩充开放对修改密封的口径,何况修改原来的代码,很可能会引入新的Bug,破坏原本的兑现。

很空洞的定义是还是不是?别管他,举个栗子:支付二个图形加载器(ImageLoader),须要能够落到实处图片的下载加载,并能将图纸缓存起来。

2、让程序更安定、越来越灵敏——开闭原则

开闭原则的爱沙尼亚语全称是Open Close
Principle,简称OCP,它是Java世界里最基础的布署条件,它引导大家如何创建三个平静的、灵活的系统。开闭原则的概念是:软件中的对象(类、模块、函数等)应该对此扩充是开放的,不过,对于修改是密封的。在软件的生命周期内,因为变化、晋级和掩护等原因必要对软件原有代码实行更换时,恐怕会将错误引进原本已经因而测验的旧代码中,破坏原有系统。因而,当软件需求转变时,大家应当尽可能通过扩大的措施来落到实处转移,并非通过修改已部分代码来实现。当然,在切实开垦中,只通过持续的办法来升高、维护原有系统只是三个做梦的愿景,因而,在其实的支出进度中,修改原有代码、扩大代码往往是还要设有的。

软件开辟进程中,最不会转换的正是转换本人。产品供给持续地进级、维护,没多少个出品从第一本子开垦完就再未有成形了,除非在下个本子诞生之前它早就被结束。而产品需求进步,修改原来的代码就也许会吸引别的的主题材料。那么怎样保险原有软件模块的不易,以及尽量少地震慑原有模块,答案正是拼命三郎坚守本章要描述的开闭原则。

勃兰特·梅耶在1987年出版的《面向对象软件构造》一书中提议这一规范化。这一设法认为,一旦成功,二个类的落到实处只应该因失实而被修改,新的依然改动的性状应该通过新建不一致的类实现。新建的类能够通过延续的艺术来重用原类的代码。明显,梅耶的定义提倡完结持续,已存在的完结对于修改是密闭的,然而新的贯彻类可以因此覆写父类的接口应对转移。
说了那般多,想必大家要么半懂不懂,依旧让我们以一个归纳示例说澳优下啊。

在对ImageLoader进行了三遍重构之后,小民的那几个开源库得到了一些客户。小民第叁遍感受到自身发明“轮子”的快感,对开源的满腔热情也尤为高涨起来!通过动手达成部分开源库来深刻学习相关技能,不只能够进级本身,也能越来越好地将那个手艺应用到职业中,进而开拓出更平稳、突出的使用,那就是小民的真人真事主张。

小民第2轮重构之后的ImageLoader职务单一、结构清晰,不止得到了主办的一些自然,还拿走了客商的赞颂,算是个不错的启幕。随着顾客的加码,有个别题目也暴流露来了,小民的缓存系统就是豪门“嘲谑”最多的地点。通过内部存款和储蓄器缓存化解了历次从互连网加载图片的难题,然则,Android应用的内部存款和储蓄器很有限,且具备易失性,即当使用重新启航以后,原本已经加载过的图样将会抛弃,那样重启之后就供给再次下载!那又会导致加载缓慢、耗开销户流量的难题。小民牵挂引进TF卡缓存,那样下载过的图纸就能够缓存到本地,尽管重启应用也无需再度下载了!小民在和主办研究了该难点以往就投入了编制程序中,上面就是小民的代码。
DiskCache.java类,将图纸缓存到PCIe闪存卡中:

public class DiskCache {
    // 为了简单起见临时写个路径,在开发中请避免这种写法 !
    static String cacheDir = "sdcard/cache/";
     // 从缓存中获取图片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    // 将图片缓存到内存中
    public  void  put(String url, Bitmap bmp) {
       FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new 
                 FileOutputStream(cacheDir + url);
            bmp.compress(CompressFormat.PNG, 
                 100, fileOutputStream);
      } catch (FileNotFoundException e) {
            e.printStackTrace();
      } final ly {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
              } catch (IOException e) {
                    e.printStackTrace();
             }
          }
      }
    }
}

因为供给将图纸缓存到SD读取卡中,所以,ImageLoader代码有所更新,具体代码如下:

public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 是否使用SD卡缓存
    boolean isUseDiskCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());


    public  void displayImage(final String url, final ImageView imageView) {
        // 判断使用哪种缓存
       Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) 
                : mImageCache.get (url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
       }
        // 没有缓存,则提交给线程池进行下载
    }

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

从上述的代码中能够看出,仅仅新扩张了二个DiskCache类和往ImageLoader类中参与了一些些代码就增多了PCIe闪存卡缓存的法力,客户能够因而useDiskCache方法来对利用哪个种类缓存实行设置,举个例子:

ImageLoader imageLoader = new ImageLoader() ;
 // 使用SD卡缓存
imageLoader.useDiskCache(true);
// 使用内存缓存
imageLoader.useDiskCache(false);

因此useDiskCache方法能够让客商安装区别的缓存,极度平价啊!小民对此很适意,于是提交给主持做代码考察。“小民,你思路是对的,然而有些明显的标题,正是选拔内部存款和储蓄器缓存时顾客就不可能动用SDHC卡缓存,类似的,使用SDHC卡缓存时顾客就不能够应用内存缓存。客商须求那三种政策的汇总,首先缓存优先利用内部存款和储蓄器缓存,假如内部存款和储蓄器缓存未有图片再选拔SDHC卡缓存,假诺TF内存卡中也从没图片最终才从网络上得到,那才是最佳的缓存战术。”主任真是一语说破,小民那时才如梦初醒,刚才还自得其乐的面颊蓦地有一点泛红……
于是乎小民依据老总的引导新建了三个双缓存类DoudleCache,具体代码如下:

/**
 * 双缓存。获取图片时先从内存缓存中获取,如果内存中没有缓存该图片,再从SD卡中获取。
 *  缓存图片也是在内存和SD卡中都缓存一份
 */
public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();

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

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

大家再看看最新的ImageLoader类吧,代码更新也相当的少:

public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 双缓存
    DoubleCache mDoubleCache = new DoubleCache() ;
    // 使用SD卡缓存
    boolean isUseDiskCache = false;
    // 使用双缓存
    boolean isUseDoubleCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());


    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bmp = null;
         if (isUseDoubleCache) {
            bmp = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bmp = mDiskCache.get(url);
        } else {
            bmp = mImageCache.get(url);
        }

         if ( bmp != null ) {
            imageView.setImageBitmap(bmp);
        }
        // 没有缓存,则提交给线程池进行异步下载图片
    }

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

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

经过扩充短短几句代码和几处改变就成功了那般重要的职能。小民已进一步感觉温馨Android开辟已经到了的百步穿杨的地步,不仅仅感觉阵阵春风袭来,他那自然的毛发一下从她的日前拂过,小民感到今每一天空比过去明白多数。

“小民,你每一回加新的缓存方法时都要修改原本的代码,那样很或者会引进Bug,况兼会使原来的代码逻辑变得愈加复杂,遵照你如此的主意达成,客户也不能够自定义缓存实现啊!”到底是主持水平高,一语道出了小民那缓存设计上的标题。

我们仍然来深入分析一下小民的程序,小民每一次在前后相继中投入新的缓存完毕时都必要修改ImageLoader类,然后通过三个布尔变量来让顾客接纳哪类缓存,因而,就使得在ImageLoader中存在各样if-else推断,通过这一个剖断来明显使用哪类缓存。随着这个逻辑的引进,代码变得进一步复杂、软弱,假诺小民一不当心写错了某些if条件(条件太多,这是很轻便并发的),那就必要越来越多的岁月来驱除。整个ImageLoader类也会变得进一步臃肿。最根本的是客商不能够协和完毕缓存注入到ImageLoader中,可扩大性但是框架的最要紧特点之一。

“软件中的对象(类、模块、函数等)应该对此扩充是开放的,不过对于修改是密闭的,那正是开放-关闭原则。也便是说,当软件须求扭转时,我们应当尽也许通过扩充的秘诀来落实转移,并不是透过修改已部分代码来落成。”小民的老总补充到,小民听得云里雾里的。首席推行官看小民那等影响,于是亲自“操刀”,为她画下了如图1-2的UML图。

图片 5

图1-2

小民看到图1-2就像知道些什么,不过又不是太分明什么修改程序。高管看到小民那样模样只能亲自参与比赛,带着小民把ImageLoader程序依据图1-2开展了一回重构。具体代码如下:

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(String imageUrl, 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;
    }
}

经过这一次重构,没有了那么多的if-else语句,未有了五颜六色的缓存实现目的、布尔变量,代码确实清晰、轻易了好多,小民对主办的尊崇之情又“泛滥”了起来。需求留心的是,这里的ImageCache类并非小民原来的不行ImageCache,本次程序重构高管把它提取成三个图片缓存的接口,用来抽象图片缓存的作用。大家看看该接口的扬言:

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

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卡中获取
    public Bitmap get(String url) {
       Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
       }
        return bitmap;
     }

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

精心的心上人恐怕注意到了,ImageLoader类中加进了三个setImageCache(ImageCache
cache)函数,客户能够由此该函数设置缓存达成,约等于家常便饭说的借助注入。下边就看看客商是什么设置缓存完结的:

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/*从缓存中获取图片*/;
       }
    });

在上述代码中,通过setImageCache(ImageCache
cache)方法注入不一样的缓存达成,那样不光能够使ImageLoader更简便、健壮,也使得ImageLoader的可扩展性、灵活性更加高。MemoryCache、DiskCache、DoubleCache缓存图片的具体贯彻完全不平等,可是,它们的贰个特色是都落到实处了ImageCache接口。当客商必要自定义完结缓存攻略时,只需求新建三个落实ImageCache接口的类,然后构造该类的靶子,並且经过setImageCache(ImageCache
cache)注入到ImageLoader中,那样ImageLoader就落到实处了变化万千的缓存战术,而扩展这个缓存计谋并不会促成ImageLoader类的修改。经过本次重构,小民的ImageLoader已经主导算过得去了。咦!那不正是主办说的开闭原则么!“软件中的对象(类、模块、函数等)应该对此扩大是开放的,但是对于修改是密封的。而遵守开闭原则的严重性手腕应该是经过架空……”小民细声细语的唠叨中,陷入了观念中……

开闭原则指点大家,当软件供给转移时,应该尽恐怕通过扩展的法子来落到实处转移,并非透过修改已有个别代码来完结。这里的“应该尽量”4个字表明OCP原则实际不是说相对不得以修改原始类的,当我们嗅到原本的代码“发霉气味”时,应该尽快地重构,以使得代码复苏到健康的“进化”轨道,并不是经过持续等办法增添新的落到实处,那会变成品种的暴涨以及历史遗留代码的冗余。我们的费用进程中也不曾那么理想化的处境,完全地并不是修改原本的代码,因而,在开垦进度中须要和谐组合具体情形举办勘察,是经过改变旧代码依旧通过三番两次使得软件系统更平稳、越来越灵活,在保证去除“代码变质”的同一时候,也准保原有模块的不错。

5.3 关闭Closeable

大家达成了三个工具类,用于关闭流。

object IOCloseUtil {

    /**
     * 关闭流。
     * @param closeable 可关闭对象
     */
    fun closeQuietly(closeable: Closeable?) {
        try {
            closeable?.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

}

单纯职责标准:就八个类来说,应该独有三个挑起他转移的由来。轻巧的话,三个类应该是一组惊人相关的函数,数据的卷入。

7、总结

在使用开拓过程中,最难的不是成功应用的支付工作,而是在承接的升级、维护进程中让使用连串能够拥抱变化。拥抱变化也就意味着在满意急需且不损坏系统牢固的前提下维持高可扩张性、高内聚、低耦合,在经验了各版本的改变之后依旧维持清晰、灵活、稳固的系统框架结构。当然,那是二个比较不错的景况,但大家务须要朝着这些势头去全力,那么遵照面向对象六大口径正是我们走向灵活软件之路所迈出的第一步。

ImageLoader

class ImageLoader {
    //定义线程池来下载图片
    private val mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
    // 并不具体的实现,只依赖抽象
    private var mImageCache: ImageCache  = MemoryCache()

    // 注入我们需要的ImageCache
    // imgCache可以为自定义对象,只要实现ImageCache接口即可
    fun setImageCache(imgCache: ImageCache) {
        this.mImageCache = imgCache
    }


    /**
     * 显示图片
     * @param url 图片url
     * @param imgView 待显示图片的ImageView
     */
    fun displayImage(url: String, imgView: ImageView) {
        val bitmap = mImageCache?.get(url)
        if (bitmap != null) {
            imgView.setImageBitmap(bitmap)
            return
        }

        imgView.tag = url
        mExecutorService.submit {
            val bitmap = downloadImage(url) ?: return@submit
            if (imgView.tag == url) {
                imgView.setImageBitmap(bitmap)
            }
            mImageCache?.put(url, bitmap)
        }
    }


    /**
     * 下载图片。
     * @param url 图片url
     * @return 解析的图片
     */
    private fun downloadImage(url: String): Bitmap? {
        var bitmap: Bitmap? = null
        try {
            val url = URL(url)
            val conn = url.openConnection() as HttpURLConnection
            bitmap = BitmapFactory.decodeStream(conn.inputStream)
            conn.disconnect()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return bitmap
    }
}

透过setImageCache注入差别的缓存达成,使得ImageLoader越发健壮和省略,也加强了ImageLoader的可扩张性、灵活性。


6.2 吹捧技术员小民

/*** 房间*/public class Room{ public float area;//面积 public float price;//租金 public Room(float area,float price){ this.area = area; this.price = price; } ...} /*** 中介*/public class Mediator{ //中介和房间类耦合 List<Room> mRooms = new ArrayList<Room>(); public Mediator(){ //初始化mRooms } public Room rentOut(float area,float price){ Room vRoom = null; for(Room room:mRooms){ if(isSuitable(area,price,room)){ vRoom = room; break; } } return vRoom; } private boolean isSuitable(float area,float price,Room room){ return room.area >= area && room.price <= price; }} /*** 租客*/public class Tenant{ public float roomArea;//租客需要的房子面积 public float roomPrice;//租客承受的租金 //租客和中介耦合 public void rentRoom(Mediator mediator){ //租客和房间耦合 Room room = mediator.rentOut(roomArea,roomPrice); if(null != room){ System.out.println("找到合适的房子啦"+room); }else{ System.out.println("没有找到合适的房子"); } }}

相对来讲小明和小民的代码,能够清楚的看看代码的解耦。只与直接对象通信,分清朋友是至关心注重要。

1、优化代码的首先步——单一职责标准

单一职务标准的斯洛伐克共和国(The Slovak Republic)语名称是Single Responsibility
Principle,简称SRP。它的概念是:就一个类来说,应该只有一个挑起它生成的原委。轻巧的话,两个类中应当是一组相关性非常高的函数、数据的包裹。就如秦小波先生在《设计格局之禅》中说的:“那是叁个碰着纠纷却又伙同关键的尺码。只要你想和人家争论、怄气恐怕是争吵,这几个规格是屡试不爽的”。因为纯粹义务的分开界限并非连接那么明显,比很多时候都以急需靠个人经验来限制。当然,最大的难题便是对职分的定义,什么是类的天职,以及怎么划分类的职责。
对于计算机手艺,常常只单纯地读书理论知识并不可能很好地精晓其深意,独有自身入手实施,并在实际应用中窥见标题、解决问题、思索难点,本事够将文化摄取到温馨的脑海中。上边以我的爱侣小民的事迹说起。

从今Android系统一发布表以来,小民正是Android的铁杆观众,于是在高校时期平昔保持着对Android的关爱,何况动用课余时间做些小项目,操练自身的实战才干。结束学业后,小民如愿地参预了钦慕的同盟社,并且投入到了她挚爱的Android应用开采用实行个中。将欣赏、生活、工作合二为一,小民的率先份职业也究竟顺风顺水,一切尽在左右中。
在经历过八日的适应期以及熟知公司的产品、开荒规范之后,小民的成本工作就正式开班了。小民的掌管是个工作经验丰裕的工夫专家,对于小民的行事并不是很满足,更小民最虚弱的面向对象设计,而Android开辟又是应用Java语言,什么抽象、接口、六大口径、23种设计格局等名词把小民弄得晕头转向。小民本人也发觉到了温馨的难题所在,于是,小民的牵头决定先让小民做多个小项目来磨练磨练那上面包车型大巴力量。正所谓养兵千日用兵不时,磨刀不误砍柴工,小民的费用之路才刚刚伊始。

在经过一番企图之后,主任挑选了利用限制广、难度也正好的ImageLoader(图片加载)作为小民的练习项目。既然要演练小民的面向对象设计,那么就非得思考到可扩充性、灵活性,而检验那总体是或不是适合必要的最佳路子正是开源。顾客不断地建议供给、反馈难点,小民的类型供给不停升级以满意客商要求,并且要保险系统的和煦、灵活性。在主持跟小民说了这一特殊职责之后,小民第三遍认为了压力,“生活不易于呀!”年仅22周岁于今未婚的小民发出了这么深刻的慨叹!

挑战总是要直面包车型地铁,而且是未有服输的小民。首席施行官的渴求很轻松,要小民达成图片加载,况兼要将图纸缓存起来。在深入分析了须求之后,小民一下就放心下来了,“这么轻便,原本自个儿还感到很难吗……”小民胸有成足的喃喃自语。在经验了十分钟的编码之后,小民写下了之类代码:

/**
 * 图片加载类
 */
public class ImageLoader {
    // 图片缓存
    LruCache<String, Bitmap> mImageCache;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());

    public ImageLoader() {
        initImageCache();
    }

    private void initImageCache() {
            // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
            // 取四分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }                   

    public  void displayImage(final String url, final ImageView imageView) {
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {

           @Override
            public  void run() {
              Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
          }
       });
    }

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

        return bitmap;
    }
}

再正是选拔git软件拓宽版本调控,将工程托管到github上,伴随着git
push命令的到位,小民的ImageLoader
0.1本子就标准发表了!如此短的时间内就成功了那么些任务,何况如故一个开源项目,小民暗暗自喜,幻想着待会儿高管的表扬。

在小民给主持告知了ImageLoader的揭穿新闻的几分钟之后,首席奉行官就把小民叫到了会议厅。那下小民纳闷了,怎么夸人还索要到会场。“小民,你的ImageLoader耦合太严重啦!差不离就不曾规划可言,更不要讲扩充性、灵活性了。全部的遵循都写在叁个类里怎么行呢,那样随着作用的加码,ImageLoader类会越来越大,代码也越发复杂,图片加载系统就更是虚弱……”Duang,那俨然正是贰只当头棒喝,小民的脑际里已经听不清老董下边说的剧情了,只是以为自个儿之前从未思量清楚就急匆匆达成任务,并且把职务想得太轻松了。

“你依然把ImageLoader拆分一下,把各类职能独立出来,让它们满意单一义务标准。”COO最终公约。小民是个智者,敏锐地捕捉到了单一任务规范这几个关键词。用Google找出了一部分卓绝资料之后终于是对纯粹职责标准有了部分认知。于是筹划对ImageLoader进行一遍重构。这一次小民不敢过于草率,也是先画了一幅UML图,如图1-1所示。

图片 6

图1-1

ImageLoader代码修改如下所示:

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

    // 加载图片
    public  void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {

            @Override
            public void run() {
            Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, 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;
    }
}   

再者增加了四个ImageCache类用于拍卖图片缓存,具体代码如下:

public class ImageCache {
    // 图片LRU缓存
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
         // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取四分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() *  
                    bitmap.getHeight() / 1024;
           }
        };
     }

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap) ;
    }

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

如图1-1和上述代码所示,小民将ImageLoader一拆为二,ImageLoader只承担图片加载的逻辑,而ImageCache只担负管理图片缓存的逻辑,那样ImageLoader的代码量降少了,职务也显明了,当与缓存相关的逻辑需求改造时,不要求修改ImageLoader类,而图片加载的逻辑必要修改时也不会影响到缓存处理逻辑。CEO在核查了小民的率先次重构之后,对小民的干活予以了赞赏,差十分少意思是布局变得明明白白了无数,可是可扩大性依然相比欠缺,即便尚未得到老板的一心自然,但也是颇有开发进取,再考虑到温馨实在怀有收获,小民原来颓靡的心坎也有个别地革新起来。

从上述的例子中大家能够体会到,单一职务所表达出的盘算正是“单一”二字。正如上文所说,怎样分割三个类、二个函数的天职,各个人都有和好的观点,那须求凭仗个体经验、具体的思想政治工作逻辑而定。不过,它也可以有一部分主导的教导标准,举个例子,七个精光分裂的效力就不应该放在二个类中。三个类中应有是一组相关性相当高的函数、数据的包装。技术员能够不停地审视自个儿的代码,依照现实的事务、功效对类举办相应的拆分,作者想那会是您优化代码迈出的率先步。

1.1 ImageLoader

咱俩来手写三个最简易的图样加载器。(Kotin实现)
虽说简易,但是它仍然含有了三个大致的内部存款和储蓄器缓存。

package core.zs.pattern

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.util.LruCache
import android.widget.ImageView
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Executors

/**
 * @function 图片加载管理器
 * @author ZhangShuai.
 * @created 2018/4/25.
 */
class ImageLoader {

    private lateinit var mImageCache: LruCache<String, Bitmap>

    //定义线程池来下载图片
    private val mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())

    init {
        initImageCache()
    }


    /**
     * 显示图片
     * @param url 图片url
     * @param imgView 待显示图片的ImageView
     */
    fun displayImage(url: String, imgView: ImageView) {
        imgView.tag = url
        mExecutorService.submit {
            val bitmap = downloadImage(url) ?: return@submit
            if (imgView.tag == url) {
                imgView.setImageBitmap(bitmap)
            }
            mImageCache.put(url, bitmap)
        }
    }

   private fun initImageCache() {
        // 计算可以使用的最大内存
        val maxMemory = Runtime.getRuntime().maxMemory() / 1024
        // 取四分之一作为内存缓存大小
        val cacheSize = (maxMemory / 4).toInt()
        mImageCache = object : LruCache<String, Bitmap>(cacheSize) {
            override fun sizeOf(key: String, bitmap: Bitmap): Int {
                return bitmap.rowBytes * bitmap.height / 1024
            }
        }
    }


    /**
     * 下载图片。
     * @param url 图片url
     * @return 解析的图片
     */
    private fun downloadImage(url: String): Bitmap? {
        var bitmap: Bitmap? = null
        try {
            val url = URL(url)
            val conn = url.openConnection() as HttpURLConnection
            bitmap = BitmapFactory.decodeStream(conn.inputStream)
            conn.disconnect()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return bitmap
    }
}

咱俩来分析上边的加载器,是或不是满意SRP原则?

图表加载器不仅仅蕴涵了图片的显示和加载,还带有了缓存的管理和设置,很显眼它是不吻合SRP的尺码。

怎么管理?

将图片的缓存完毕独立拆分出来。

发表评论

电子邮件地址不会被公开。 必填项已用*标注