zoukankan      html  css  js  c++  java
  • 面向对象六大原则

    在此之前,有一点需要大家知道,熟悉这些原则并不是说你写出的程序就一定灵活、清晰,只是为你的优秀代码之路铺上了一层栅栏,在这些原则的指导下你才能避免陷入一些常见的代码泥沼,从而让你专心写出优秀的东西。
    下面我们就以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年从建筑设计领域引入到软件工程领域,从此设计模式在面向对象设计领域逐渐被重视起来。

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

  • 相关阅读:
    数据结构第九篇——栈与递归
    c++重载(以运算符重载为主)
    (五)分数阶微分方程的解法及其适定性问题介绍
    (四)分数阶微积分
    (三)分数阶微积分
    (二)分数阶微积分
    小学教育试讲
    高中教育试讲
    【级数】 求和
    题东湖风光村
  • 原文地址:https://www.cnblogs.com/zhangboyu/p/7452583.html
Copyright © 2011-2022 走看看