• 如何理解设计思想与代码质量优化


    本文将通过六大原则、设计模式、数据结构、算法来阐述设计思想与代码质量优化的结合

    一、六大原则

    1、单一职责原则

    不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

    2、里氏替换原则(Liskov Substitution Principle)

    里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

    历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

    3、依赖倒转原则(Dependence Inversion Principle)

    这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

    4、接口隔离原则(Interface Segregation Principle)

    这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

    5、迪米特法则(最少知道原则)(Demeter Principle)

    就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

    最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

    6、合成复用原则(Composite Reuse Principle)

    原则是尽量首先使用合成/聚合的方式,而不是使用继承。

    二、设计模式

    总体来说设计模式分为三大类:

    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:


    三、数据结构

    <meta charset="utf-8">

    1.数组

    ①概念

    存储多个相同类型的数据的集合。

    ②特点

    a)数组中的数据元素可以是基本数据类型,也可以是引用数据类型;

    b)数组具有下标,下标从0开始计数,用于快速获取数组中的数据,比如a[0],表示数组中的第一个数据;

    c)数组在创建的时候,需要在内存中申请一段固定长度的内存,如果申请的长度超过内存剩余的长度,则容易产生碎片,导致存储失败;

    d)数组便于查找和修改数据,不便于增删数据;

    e)数组分为数值数组,字符数组,指针数组,结构数组等;

    ③图解

    2.栈

    ①概念

    一种只能在表头进行数据插入和删除操作的线性表,又名堆栈。

    ②特点

    a)按照先进后出的原则存储数据;

    b)栈分为顺序栈和链式栈;

    ③图解

    3.队列

    ①概念

    一种特殊的线性表,只能在队头进行删除数据操作,在队尾进行增加数据操作。

    ②特点

    a)遵循先进先出的原则存储数据;

    b)队列分为顺序队列和循环队列;

    ③图解

    4.链表

    ①概念

    一种非连续,非顺序的存储方式,通过指针将数据进行连接的方式实现。

    ②特点

    a)在创建的时候,不需要指定长度,可以动态调整长度,不易产生碎片;

    b)链表的每个元素分为数据和指针,指针指向下一个数据的地址,从而形成串联;

    c)便于数据增删,不便于数据查询;

    d)链表分为单向链表,双向链表,循环列表;

    ③图解

     

    5.树

    ①概念

    由一个根节点和若干个子树构成的集合。

    ②特点

    a)有且仅有一个根节点;

    b)子树之间不可以有交集;

    c)树分为无序树,有序树,二叉树等;

    d)树的深度指的是树的有多少层;

    e)一个节点的度指的是该节点下有多少个子节点;

    f)二叉树指的是每个结点的度≤2的树。

    g)树的遍历方式分为三种,分别是前序遍历(根左右),中序遍历(左根右),后序遍历(左右根);

    ③图解

    6.图

    ①概念

    由顶点的有穷非空集合和顶点之间边的集合组成。

    ②特点

    a)图分为有向图和无向图,区别在于边是否有方向;

    b)图主要涉及到的内容是最短路径;

    ③图解

    7.堆

    ①概念

    用于动态分配和释放程序所使用的对象。

    ②特点

    a)堆分为最小堆和最大堆,区别在于所有父节点是否大于等于其子节点,是则是最大堆,否则反之;

    b)堆是一颗完全二叉树;

    ③图解

    8.散列表

    ①概念

    根据key-value来进行数据获取的存储数据方式。

    ②特点

    a)又名哈希表;

    b)便于插入,查找等操作;

    c)key以数组的方式存储在栈内存中,value以链表的方式存储在堆空间中;

    d)不同的key通过哈希函数可能得到相同的结果,这时候就发生了哈希碰撞;

    ③图解

    四、算法

    4.1排序算法

    4.1.1 简单排序算法

    冒泡排序
    两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止
    直接插入排序
    通过 n-i 次关键字间的比较,从 n-i+1 个记录中选出关键字最小的记录,并和第 i 个记录交换
    简单选择排序

    改进算法

    快速排序(冒泡排序的改进)
    先随机选择一个记录,比它大的放在右边,比它小的放在左边,采用递归的方式进行排序
    java 代码

    /**
     * 快排,先找一个记录,把大于他的放在右边,小的放在左边,然后采用递归的方式进行排序
     */
    public class QuickSort2 {
    
        public void quickSort(int[] array) {
            if (array.length > 0) {
                doQuickSort(array, 0, array.length - 1);
            }
        }
    
        private void doQuickSort(int[] array, int left, int right) {
            if (left >= right) {
                return;
            }
            int low = left;
            int high = right;
            int temp = array[low];
            while (low != high) {
                while (low < high && array[high] > temp) {
                    high--;
                }
                array[low] = array[high];
                while (low < high && array[low] < temp) {
                    low++;
                }
                array[high] = array[low];
            }
            array[high] = temp;
            Utils.printArray(array);
            doQuickSort(array, left, low - 1);
            doQuickSort(array, high + 1, right);
        }
    
        public static void main(String[] args) {
            QuickSort2 qs = new QuickSort2();
            int[] a = {9, 1, 5, 8, 3, 7, 4, 6, 2};
            qs.quickSort(a);
        }
    }
    

    测试结果

    2 1 5 8 3 7 4 6 9 -
    1 2 5 8 3 7 4 6 9 -
    1 2 4 3 5 7 8 6 9 -
    1 2 3 4 5 7 8 6 9 -
    1 2 3 4 5 6 7 8 9 -
    

    希尔排序
    堆排序
    并排序

    4.2查找算法

    4.2.1顺序查找

    顺序查找过程:从表中的最后一个记录开始,逐个进行记录的关键字与给定值进行比较,若某个记录的关键字与给定值相等,则查找成功,找到所查的记录;反之,若直到第一个记录,其关键字和给定值比较都不相等,则表明表中没有所查的记录,查找失败。
      算法描述为

    int Search(int d,int a[],int n)
      {
    

    /在数组a[]中查找等于D元素,若找到,则函数返回d在数组中的位置,否则为0。其中n为数组长度/

      int i ;
      /*从后往前查找*/
      for(i=n-1;a*!=d;--i)
      return i ;
      /*如果找不到,则i为0*/
      }*
    

    4.2.2 二分查找

    [编辑](javascript:;)

    二分查找又称折半查找,它是一种效率较高的查找方法。

    【二分查找要求】:1.必须采用顺序存储结构2.必须按关键字大小有序排列。

    【优缺点】折半查找法的优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
      【算法思想】首先,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
      重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
      【算法复杂度】假设其数组长度为n,其算法复杂度为o(log(n))

    下面提供一段二分查找实现的伪代码:

    BinarySearch(max,min,des)
    
    mid-des then
      max=mid-1
      else
      min=mid+1
      return max
    

    折半查找法也称为二分查找法,它充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。它的基本思想是,将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止。如 果xa[n/2],则我们只要在数组a的右 半部继续搜索x。

    4.2.3 分块查找

    分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
      方法描述:将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……。
      操作步骤:
      step1 先选取各块中的最大关键字构成一个索引表;
      step2 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。

    4.2.4 哈希表查找

    1 基本原理

    我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素"分类",然后将这个元素存储在相应"类"所对应的地方。

    但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了"冲突",换句话说,就是把不同的元素分在了相同的"类"之中。后面我们将看到一种解决"冲突"的简便做法。

    总的来说,"直接定址"与"解决冲突"是哈希表的两大特点。

    2 函数构造

    构造函数的常用方法(下面为了叙述简洁,设 h(k) 表示关键字为 k 的元素所对应的函数值):

    a) 除余法:

    选择一个适当的正整数 p ,令 h(k ) = k mod p
      这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。

    b) 数字选择法:

    如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。

    3冲突处理

    线性重新散列技术易于实现且可以较好的达到目的。令数组元素个数为 S ,则当 h(k) 已经存储了元素的时候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的)。

    4 支持运算

    哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。
      设插入的元素的关键字为 x ,A 为存储的数组。
      初始化比较容易,例如

      const empty=maxlongint; // 用非常大的整数代表这个位置没有存储元素
      p=9997; // 表的大小
      procedure makenull;
      var i:integer;
      begin
      for i:=0 to p-1 do
      A*:=empty;
      End;*
    

    哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:

      function h(x:longint):Integer;
      begin
      h:= x mod p;
      end;
    

    我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素若存在,它应该存储在什么位置,因此加入一个定位的函数 locate

      function locate(x:longint):integer;
      var orig,i:integer;
      begin
      orig:=h(x);
      i:=0;
      while (ix)and(A[(orig+i)mod S]empty) do
      inc(i);
      //当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元
      //素存储的单元,要么表已经满了
      locate:=(orig+i) mod S;
      end;
      插入元素
      procedure insert(x:longint);
      var posi:integer;
      begin
      posi:=locate(x); //定位函数的返回值
      if A[posi]=empty then A[posi]:=x
      else error; //error 即为发生了错误,当然这是可以避免的
      end;
    

    查找元素是否已经在表中

      procedure member(x:longint):boolean;
      var posi:integer;
      begin
      posi:=locate(x);
      if A[posi]=x then member:=true
      else member:=false;
      end;
    

    好了文章就分享到这里了,喜欢的朋友别忘了关注+点赞喔

     

     
     
  • 相关阅读:
    Ext2.0布局类初探
    从DHTML、HTC、XHTML到AJAX
    我常用的一些ASP自定义函数
    Javascript的调试利器:Firebug使用详解
    ODBC的多线程应用
    召唤有丰富IOCP实践经验的同行
    欢迎
    关于.NET安装时的dotNETFXRedist_x86.msm
    所谓设计模式
    VC编程经验汇总(一)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11765460.html
Copyright © 2020-2023  润新知