在此之前,有一点需要大家知道,熟悉这些原则并不是说你写出的程序就一定灵活、清晰,只是为你的优秀代码之路铺上了一层栅栏,在这些原则的指导下你才能避免陷入一些常见的代码泥沼,从而让你专心写出优秀的东西。
下面我们就以Android网络框架SimpleNet为例来学习这六大面向对象的基本原则,体会这些原则在开发过程中带来的强大能量。

1 单一职责原则

单一职责原则的英文名称是Single Responsibility Principle,简称是SRP,简单地说就是一个类只做一件事。这个设计原则备受争议却又极其重要。只要你想和别人争执、怄气或者是吵架,这个原则是屡试不爽的。因为单一职责的划分界限并不是如马路上的行车道那么清晰,很多时候都是需要靠个人经验来界定。当然,最大的问题就是对职责的定义,什么是类的职责,以及怎么划分类的职责。
试想一下,如果你遵守了这个原则,那么你的类就会划分得很细,每个类都有比较单一的职责,这不就是高内聚、低耦合么!当然,如何界定类的职责这需要你的个人经验了。
在SimpleNet中,我觉得很能够体现SRP原则的就是HttpStack这个类族了。HttpStack定义了一个执行网络请求的接口,代码如下:

public interface HttpStack {
  /**
   * 执行Http请求,并且返回一个Response
   */ 
  public Response performRequest(Request<?> request);
}


从上述程序中可以看到,HttpStack只有一个performRequest函数,它的职责就是执行网络请求并且返回一个Response。它的职责很单一,这样在需要修改执行网络请求的相关代码时,只需要修改实现HttpStack接口的类,而不会影响其他的类的代码。如果某个类的职责包含有执行网络请求、解析网络请求、进行gzip压缩、封装请求参数等,那么在你修改某处代码时就必须谨慎,以免修改的代码影响了其他的功能。当你修改的代码能够基本上不影响其他的功能。这就在一定程度上保证了代码的可维护性。注意,单一职责原则并不是说一个类只有一个函数,而是说这个类中的函数所做的工作是高度相关的,也就是高内聚。HttpStack抽象了执行网络请求的具体过程,接口简单清晰,也便于扩展。

优点


(1)类的复杂性降低,实现什么职责都有清晰明确的定义。
(2)可读性提高,复杂性降低,那当然可读性提高了。
(3)可维护性提高,可读性提高,那当然更容易维护了。
(4)变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

 

2 里氏替换原则

面向对象的语言的三大特点是继承、封装、多态,里氏替换原则就是依赖于继承、多态这两大特性。里氏替换原则简单来说就是所有引用基类、接口的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
还是以HttpStack为例,SimpleNet定义了HttpStack来表示执行网络请求这个抽象概念。在执行网络请求时,只需要定义一个HttpStack对象,然后调用performRequest即可,至于HttpStack的具体实现由更高层的调用者指定。这部分代码在RequestQueue类中,示例如下:

 /**
   * @paramcoreNums线程核心数
   * @paramhttpStack http执行器
   */
  protected RequestQueue(intcoreNums, HttpStackhttpStack) {
    mDispatcherNums = coreNums;
    mHttpStack = httpStack != null ? httpStack : HttpStackFactory.createHttpStack();
  }

 

HttpStackFactory类的createHttpStack函数负责根据API版本创建不同的HttpStack,实现代码如下:

// 根据API版本选择HttpClient或者HttpURLConnection
public final class HttpStackFactory {
  // API 9
  private static final int GINGERBREAD_SDK_NUM = 9;

  /**
   * 根据SDK版本号来创建不同的Http执行器,即SDK 9之前使用HttpClient,之后则使用HttlUrlConnection
   * @return
   */
  public static HttpStackcreateHttpStack() {
     intruntimeSDKApi = Build.VERSION.SDK_INT;
     if (runtimeSDKApi>= GINGERBREAD_SDK_NUM) {
      return new HttpUrlConnStack();
    }
    return new HttpClientStack();
  }
}


上述代码中,RequestQueue类中依赖的是HttpStack接口,而通过HttpStackFactory的createHttpStack函数返回的是HttpStack的实现类HttpClientStack或HttlUrlConnStack。这就是所谓的里氏替换原则,任何父类、父接口出现的地方子类都可以出现,这不就保证了可扩展性吗!
任何实现HttpStack接口的类的对象都可以传递给RequestQueue实现网络请求的功能,这样SimpleNet执行网络请求的方法就有很多种可能性,而不是只有HttpClient和HttpURLConnection。例如,用户想使用OkHttp作为SimpleNet的执行引擎,那么创建一个实现了HttpStack接口的OkHttpStack类,然后在该类的performRequest函数中执行网络请求,最终将OkHttpStack对象注入RequestQueue即可。
细想一下,很多应用框架不就是这样实现吗?框架定义一系列相关的逻辑骨架与抽象,使得用户可以将自己的实现注入到框架中,从而实现变化万千的功能。

优点


(1)代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。
(2)提高代码的重用性。
(3)提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,很多开源框架的扩展接口都是通过继承父类来完成的。
(4)提高产品或项目的开放性。

 

 

缺点


(1)继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
(2)降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束。
(3)增强了耦合性。当父类的常量、变量和方法被修改时,必需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大片的代码需要重构。

 

 

3 依赖倒置原则

依赖倒置原则这个名字看着有点不好理解,“依赖”还要“倒置”,这到底是什么意思?依赖倒置原则的几个关键点如下:
(1)高层模块不应该依赖低层模块,两者都应该依赖其抽象。
(2)抽象不应该依赖细节。
(3)细节应该依赖抽象。
在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的。细节就是实现类、实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字 new 产生一个对象。依赖倒置原则在 Java 语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。软件先驱们总是喜欢将一些理论定义得很抽象,弄得不是那么容易理解,其实就是一句话:面向接口编程,或者说是面向抽象编程,这里的抽象指的是接口或者抽象类。面向接口编程是面向对象精髓之一。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

优点


(1)可扩展性好。
(2)耦合度低。

 

 

4 开闭原则

开闭原则是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。开闭原则的定义是:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,
可能会给旧代码引入错误。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
在软件开发过程中,永远不变的就是变化。开闭原则是使我们的软件系统拥抱变化的核心原则之一。对扩展开放,对修改关闭这样的高层次的概括,即在需要对软件进行升级、变化时应该通过扩展的形式来实现,而非修改原有代码。当然这只是一种比较理想的状态,是通过扩展还是通过修改旧代码需要根据代码自身来定。
在SimpleNet中,开闭原则体现得比较好的是Request类族的设计。我们知道,在开发C/S应用时,服务器返回的数据格式多种多样,有字符串类型、xml、Json等。而解析服务器返回的Response的原始数据类型则是通过Request类来实现的,这样就使得Request类对于服务器返回的数据格式有良好的扩展性,即Request的可变性太大。
例如,返回的数据格式是Json,那么,使用JsonRequest请求来获取数据,它会将结果转成JsonObject对象,我们看看JsonRequest的核心实现:

// 返回的数据类型为Json的请求, Json对应的对象类型为JSONObject
public class JsonRequest extends Request<JSONObject> {

  public JsonRequest(HttpMethod method, String url,
     RequestListener<JSONObject> listener) {
     super(method, url, listener);
}

// 将Response的结果转换为JSONObject
@Override
public JSONObjectparseResponse(Response response) {
    String jsonString = new String(response.getRawData());
    try {
      return new JSONObject(jsonString);
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return null;
  }
}


JsonRequest通过实现Request抽象类的parseResponse解析服务器返回的结果,这里将结果转换为JSONObject,并且封装到Response类中。
例如,SimpleNet添加对图片请求的支持,即要实现类似ImageLoader的功能。这个时候我的请求返回的数据是Bitmap图片。因此,我需要在该类型的Request中得到的结果是Request,但支持一种新的数据格式不能通过修改源码的形式,这样可能会为旧代码引入错误,但是,你又必须实现功能扩展。这就是开闭原则的定义:对扩展开放,对修改关闭。我们看看SimpleNet是如何做的:

public class ImageRequest extends Request<Bitmap> {

  public ImageRequest(HttpMethod method, String url,
    RequestListener<Bitmap> listener) {
    super(method, url, listener);
  }

  // 将数据解析为Bitmap
  @Override
  public Bitmap parseResponse(Response response) {
      return BitmapFactory.decodeByteArray(response.rawData,
       0, response.rawData.length);
  }

}


ImageRequest类的parseResponse函数中将Response中的原始数据转换为Bitmap即可。当我们需要添加其他数据格式时,只需要继承自Request类,并且在parseResponse方法中将数据转换为具体的形式即可。这样通过扩展的形式来应对软件的变化或者说用户需求的多样性,既避免了破坏原有系统,又保证了软件系统的可扩展性。依赖于抽象,而不依赖于具体,使得对扩展开放,对修改关闭。开闭原则与依赖倒置原则、里氏替换原则一样,实际上最终都遵循一句话:面向接口编程。

优点


(1)增加稳定性。
(2)可扩展性高。

 

 

5 接口隔离原则

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
可能描述起来不是很好理解,我们还是以示例来加强理解吧。
我们知道,在SimpleNet的网络队列中是会对请求进行排序的。SimpleNet内部使用PriorityBlockingQueue来维护网络请求队列,PriorityBlockingQueue需要调用Request类的compareTo函数来进行排序。试想一下,PriorityBlockingQueue其实只需要调用Request类的排序方法就可以了,其他的接口它根本不需要,即PriorityBlockingQueue只需要compareTo这个接口,而这个compareTo方法就是我们上述所说的最小接口。当然,compareTo这个方法并不是SimpleNet本身定义的接口方法,而是Java中的Comparable接口,但我们这里只是为了学习,至于哪里定义的无关紧要:

public abstract class Request<T> implements Comparable<Request<T>> {

  /**
   * 排序方法,PriorityBlockingQueue只需要调用元素的compareTo即可进行排序
   */
  @Override
  public intcompareTo(Request<T> another) {
    Priority myPriority = this.getPriority();
    Priority anotherPriority = another.getPriority();
    // 如果优先级相等,那么按照添加到队列的序列号顺序来执行
    return myPriority.equals(anotherPriority) ?this.getSerialNumber()
        - another.getSerialNumber()
        : myPriority.ordinal() - anotherPriority.ordinal();
} 
// 代码省略
}

 

PriorityBlockingQueue类相关代码 :
 

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
  implements BlockingQueue<E>, java.io.Serializable {

  // 代码省略

  // 添加元素时进行排序
  public boolean offer(E e) {
    if (e == null)
      throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    int n, cap;
    Object[] array;
    while ((n = size) >= (cap = (array = queue).length))
        tryGrow(array, cap);
        try {
           Comparator<? super E>cmp = comparator;
          // 没有设置Comparator,则使用元素本身的compareTo方法进行排序
          if (cmp == null)
             siftUpComparable(n, e, array);
          else
             siftUpUsingComparator(n, e, array, cmp);
          size = n + 1;
          notEmpty.signal();
    } finally {
      lock.unlock();
    }
    return true;
  }

  private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
    int parent = (k - 1) >>> 1;
      Object e = array[parent];
      // 调用元素的compareTo方法进行排序
      if (key.compareTo((T) e) >= 0)
        break;
      array[k] = e;
      k = parent;
    }
    array[k] = key;
  }
 }


从PriorityBlockingQueue的代码可知,在元素排序时,PriorityBlockingQueue只需要知道元素是个Comparable对象即可,不需要知道这个对象是不是Request类以及这个类的其他接口。它只需要排序,因此,只要知道它是实现了Comparable接口的对象即可,Comparable就是它的最小接口,也是通过Comparable隔离了PriorityBlockingQueue类对Request类的其他方法的可见性。

优点


(1)降低耦合性。
(2)提升代码的可读性。
(3)隐藏实现细节。

 

 

6 迪米特原则

迪米特法则也称为最少知识原则(Least Knowledge Principle),虽然名字不同,但描述的是同一个原则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,这有点类似接口隔离原则中的最小接口的概念。类的内部如何实现、如何复杂都与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它需要的方法即可,其他的一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
迪米特法则还有一个英文解释是:Only talk to your immedate friends(只与直接的朋友通信)。什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。
例如,SimpleNet中的Response缓存接口的设计。

/**
 * 请求缓存接口
* @param<K> key的类型
 * @param<V> value类型
 */
public interface Cache<K, V> {
  public V get(K key);
  public void put(K key, V value);
  public void remove(K key);
}


Cache接口定义了缓存类需要实现的最小接口,依赖缓存类的对象只需要知道这些接口即可。例如,需要将Http Response缓存到内存中,并且按照LRU的规则进行存储。我们需要LruCache类实现这个功能,代码如下: 

// 将请求结果缓存到内存中
public class LruMemCache implements Cache<String, Response> {

  /**
   * Reponse LRU缓存
   */
  private LruCache<String, Response>mResponseCache;

  public LruMemCache() {
    // 计算可使用的最大内存
    final intmaxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // 取八分之一的可用内存作为缓存
    final intcacheSize = maxMemory / 8;
    mResponseCache = new LruCache<String, Response>(cacheSize) {
      @Override
      protected intsizeOf(String key, Response response) {
        return response.rawData.length / 1024;
      }
    };

  }

  @Override
  public Response get(String key) {
    return mResponseCache.get(key);
  }

  @Override
  public void put(String key, Response response) {
    mResponseCache.put(key, response);
  }

  @Override
  public void remove(String key) {
    mResponseCache.remove(key);
  }
}


在这里,SimpleNet的直接朋友就是Cache或者LruMemCahce,间接朋友就是LruCache类。SimpleNet只需要直接和Cache类交互即可,并不需要知道LruCache的对象的存在,即真正实现缓存功能的对象是LruCache。这就是迪米特原则,尽量少地知道对象的信息,只与直接的朋友交互。

优点


(1)降低复杂度。
(2)降低耦合度。
(3)增加稳定性。
面向对象六大原则在开发过程中极为重要,它们给灵活、可扩展的软件系统提供了更细粒度的指导原则。如果能够很好地将这些原则运用到项目中,再在一些合适的场景运用一些经过验证过的设计模式,那么开发出来的软件在一定程度上能够得到质量保证。其实这六大原则最终可以简化为几个关键词:抽象、单一职责、最小化。那么在实际开发过程中如何权衡、实践这些原则,也是需要大家在工作中不断地思考、摸索。

3 依赖倒置原则

依赖倒置原则这个名字看着有点不好理解,“依赖”还要“倒置”,这到底是什么意思?依赖倒置原则的几个关键点如下:
(1)高层模块不应该依赖低层模块,两者都应该依赖其抽象。
(2)抽象不应该依赖细节。
(3)细节应该依赖抽象。
在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的。细节就是实现类、实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字 new 产生一个对象。依赖倒置原则在 Java 语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。软件先驱们总是喜欢将一些理论定义得很抽象,弄得不是那么容易理解,其实就是一句话:面向接口编程,或者说是面向抽象编程,这里的抽象指的是接口或者抽象类。面向接口编程是面向对象精髓之一。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

优点


(1)可扩展性好。
(2)耦合度低。

 

 

4 开闭原则

开闭原则是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。开闭原则的定义是:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,
可能会给旧代码引入错误。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
在软件开发过程中,永远不变的就是变化。开闭原则是使我们的软件系统拥抱变化的核心原则之一。对扩展开放,对修改关闭这样的高层次的概括,即在需要对软件进行升级、变化时应该通过扩展的形式来实现,而非修改原有代码。当然这只是一种比较理想的状态,是通过扩展还是通过修改旧代码需要根据代码自身来定。
在SimpleNet中,开闭原则体现得比较好的是Request类族的设计。我们知道,在开发C/S应用时,服务器返回的数据格式多种多样,有字符串类型、xml、Json等。而解析服务器返回的Response的原始数据类型则是通过Request类来实现的,这样就使得Request类对于服务器返回的数据格式有良好的扩展性,即Request的可变性太大。
例如,返回的数据格式是Json,那么,使用JsonRequest请求来获取数据,它会将结果转成JsonObject对象,我们看看JsonRequest的核心实现:

// 返回的数据类型为Json的请求, Json对应的对象类型为JSONObject
public class JsonRequest extends Request<JSONObject> {

  public JsonRequest(HttpMethod method, String url,
     RequestListener<JSONObject> listener) {
     super(method, url, listener);
}

// 将Response的结果转换为JSONObject
@Override
public JSONObjectparseResponse(Response response) {
    String jsonString = new String(response.getRawData());
    try {
      return new JSONObject(jsonString);
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return null;
  }
}


JsonRequest通过实现Request抽象类的parseResponse解析服务器返回的结果,这里将结果转换为JSONObject,并且封装到Response类中。
例如,SimpleNet添加对图片请求的支持,即要实现类似ImageLoader的功能。这个时候我的请求返回的数据是Bitmap图片。因此,我需要在该类型的Request中得到的结果是Request,但支持一种新的数据格式不能通过修改源码的形式,这样可能会为旧代码引入错误,但是,你又必须实现功能扩展。这就是开闭原则的定义:对扩展开放,对修改关闭。我们看看SimpleNet是如何做的:

public class ImageRequest extends Request<Bitmap> {

  public ImageRequest(HttpMethod method, String url,
    RequestListener<Bitmap> listener) {
    super(method, url, listener);
  }

  // 将数据解析为Bitmap
  @Override
  public Bitmap parseResponse(Response response) {
      return BitmapFactory.decodeByteArray(response.rawData,
       0, response.rawData.length);
  }

}


ImageRequest类的parseResponse函数中将Response中的原始数据转换为Bitmap即可。当我们需要添加其他数据格式时,只需要继承自Request类,并且在parseResponse方法中将数据转换为具体的形式即可。这样通过扩展的形式来应对软件的变化或者说用户需求的多样性,既避免了破坏原有系统,又保证了软件系统的可扩展性。依赖于抽象,而不依赖于具体,使得对扩展开放,对修改关闭。开闭原则与依赖倒置原则、里氏替换原则一样,实际上最终都遵循一句话:面向接口编程。

优点


(1)增加稳定性。
(2)可扩展性高。

 

 

5 接口隔离原则

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
可能描述起来不是很好理解,我们还是以示例来加强理解吧。
我们知道,在SimpleNet的网络队列中是会对请求进行排序的。SimpleNet内部使用PriorityBlockingQueue来维护网络请求队列,PriorityBlockingQueue需要调用Request类的compareTo函数来进行排序。试想一下,PriorityBlockingQueue其实只需要调用Request类的排序方法就可以了,其他的接口它根本不需要,即PriorityBlockingQueue只需要compareTo这个接口,而这个compareTo方法就是我们上述所说的最小接口。当然,compareTo这个方法并不是SimpleNet本身定义的接口方法,而是Java中的Comparable接口,但我们这里只是为了学习,至于哪里定义的无关紧要:

public abstract class Request<T> implements Comparable<Request<T>> {

  /**
   * 排序方法,PriorityBlockingQueue只需要调用元素的compareTo即可进行排序
   */
  @Override
  public intcompareTo(Request<T> another) {
    Priority myPriority = this.getPriority();
    Priority anotherPriority = another.getPriority();
    // 如果优先级相等,那么按照添加到队列的序列号顺序来执行
    return myPriority.equals(anotherPriority) ?this.getSerialNumber()
        - another.getSerialNumber()
        : myPriority.ordinal() - anotherPriority.ordinal();
} 
// 代码省略
}

 

PriorityBlockingQueue类相关代码 :
 

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
  implements BlockingQueue<E>, java.io.Serializable {

  // 代码省略

  // 添加元素时进行排序
  public boolean offer(E e) {
    if (e == null)
      throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    int n, cap;
    Object[] array;
    while ((n = size) >= (cap = (array = queue).length))
        tryGrow(array, cap);
        try {
           Comparator<? super E>cmp = comparator;
          // 没有设置Comparator,则使用元素本身的compareTo方法进行排序
          if (cmp == null)
             siftUpComparable(n, e, array);
          else
             siftUpUsingComparator(n, e, array, cmp);
          size = n + 1;
          notEmpty.signal();
    } finally {
      lock.unlock();
    }
    return true;
  }

  private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
    int parent = (k - 1) >>> 1;
      Object e = array[parent];
      // 调用元素的compareTo方法进行排序
      if (key.compareTo((T) e) >= 0)
        break;
      array[k] = e;
      k = parent;
    }
    array[k] = key;
  }
 }


从PriorityBlockingQueue的代码可知,在元素排序时,PriorityBlockingQueue只需要知道元素是个Comparable对象即可,不需要知道这个对象是不是Request类以及这个类的其他接口。它只需要排序,因此,只要知道它是实现了Comparable接口的对象即可,Comparable就是它的最小接口,也是通过Comparable隔离了PriorityBlockingQueue类对Request类的其他方法的可见性。

优点


(1)降低耦合性。
(2)提升代码的可读性。
(3)隐藏实现细节。

 

 

6 迪米特原则

迪米特法则也称为最少知识原则(Least Knowledge Principle),虽然名字不同,但描述的是同一个原则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,这有点类似接口隔离原则中的最小接口的概念。类的内部如何实现、如何复杂都与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它需要的方法即可,其他的一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
迪米特法则还有一个英文解释是:Only talk to your immedate friends(只与直接的朋友通信)。什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。
例如,SimpleNet中的Response缓存接口的设计。

/**
 * 请求缓存接口
* @param<K> key的类型
 * @param<V> value类型
 */
public interface Cache<K, V> {
  public V get(K key);
  public void put(K key, V value);
  public void remove(K key);
}


Cache接口定义了缓存类需要实现的最小接口,依赖缓存类的对象只需要知道这些接口即可。例如,需要将Http Response缓存到内存中,并且按照LRU的规则进行存储。我们需要LruCache类实现这个功能,代码如下: 

// 将请求结果缓存到内存中
public class LruMemCache implements Cache<String, Response> {

  /**
   * Reponse LRU缓存
   */
  private LruCache<String, Response>mResponseCache;

  public LruMemCache() {
    // 计算可使用的最大内存
    final intmaxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // 取八分之一的可用内存作为缓存
    final intcacheSize = maxMemory / 8;
    mResponseCache = new LruCache<String, Response>(cacheSize) {
      @Override
      protected intsizeOf(String key, Response response) {
        return response.rawData.length / 1024;
      }
    };

  }

  @Override
  public Response get(String key) {
    return mResponseCache.get(key);
  }

  @Override
  public void put(String key, Response response) {
    mResponseCache.put(key, response);
  }

  @Override
  public void remove(String key) {
    mResponseCache.remove(key);
  }
}


在这里,SimpleNet的直接朋友就是Cache或者LruMemCahce,间接朋友就是LruCache类。SimpleNet只需要直接和Cache类交互即可,并不需要知道LruCache的对象的存在,即真正实现缓存功能的对象是LruCache。这就是迪米特原则,尽量少地知道对象的信息,只与直接的朋友交互。

优点


(1)降低复杂度。
(2)降低耦合度。
(3)增加稳定性。
面向对象六大原则在开发过程中极为重要,它们给灵活、可扩展的软件系统提供了更细粒度的指导原则。如果能够很好地将这些原则运用到项目中,再在一些合适的场景运用一些经过验证过的设计模式,那么开发出来的软件在一定程度上能够得到质量保证。其实这六大原则最终可以简化为几个关键词:抽象、单一职责、最小化。那么在实际开发过程中如何权衡、实践这些原则,也是需要大家在工作中不断地思考、摸索。

设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。这个术语是由Erich Gamma等人在1990年从建筑设计领域引入到软件工程领域,从此设计模式在面向对象设计领域逐渐被重视起来。

设计模式并不直接用来完成代码的编写,而是描述在各种情况下要如何解决软件设计问题。面向对象设计模式通常以类或对象来描述其中的关系和相互作用,它们的相互作用能够使软件系统具有高内聚、低耦合的特性,并且使软件能够应对变化。