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

守給你的承諾、 提交于 2019-12-02 22:42:53

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

一、六大原则

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;

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

 

 
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!