如何理解設計思想與代碼質量優化

本文將通過六大原則、設計模式、數據結構、算法來闡述設計思想與代碼質量優化

一、六大原則

1、單一職責原則
不要存在多於一個導致類變更的原因,也就是說每個類應該實現單一的職責,如若不然,就應該把類拆分。

2、里氏替換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

歷史替換原則中,子類對父類的方法儘量不要重寫和重載。因爲父類代表了定義好的結構,通過這個規範的接口與外界交互,子類不應該隨便破壞它。

3、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:面向接口編程,依賴於抽象而不依賴於具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。

4、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:每個接口中不存在子類用不到卻必須實現的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。

5、迪米特法則(最少知道原則)(Demeter Principle)
就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。

最少知道原則的另一個表達方式是:只與直接的朋友通信。類之間只要有耦合關係,就叫朋友關係。耦合分爲依賴、關聯、聚合、組合等。我們稱出現爲成員變量、方法參數、方法返回值中的類爲直接朋友。局部變量、臨時變量則不是直接的朋友。我們要求陌生的類不要作爲局部變量出現在類中。

6、合成複用原則(Composite Reuse Principle)
原則是儘量首先使用合成/聚合的方式,而不是使用繼承。

二、設計模式

總體來說設計模式分爲三大類:

創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

其實還有兩類:併發型模式和線程池模式。用一個圖片來整體描述一下:
設計模式直接的關係.png

三、數據結構

<meta charset="utf-8">

1.數組
①概念
存儲多個相同類型的數據的集合。

②特點
a)數組中的數據元素可以是基本數據類型,也可以是引用數據類型;

b)數組具有下標,下標從0開始計數,用於快速獲取數組中的數據,比如a[0],表示數組中的第一個數據;

c)數組在創建的時候,需要在內存中申請一段固定長度的內存,如果申請的長度超過內存剩餘的長度,則容易產生碎片,導致存儲失敗;

d)數組便於查找和修改數據,不便於增刪數據;

e)數組分爲數值數組,字符數組,指針數組,結構數組等;

③圖解
數組.png

2.棧
①概念
一種只能在表頭進行數據插入和刪除操作的線性表,又名堆棧。

②特點
a)按照先進後出的原則存儲數據;

b)棧分爲順序棧和鏈式棧;

③圖解
棧.png
3.隊列
①概念
一種特殊的線性表,只能在隊頭進行刪除數據操作,在隊尾進行增加數據操作。

②特點
a)遵循先進先出的原則存儲數據;

b)隊列分爲順序隊列和循環隊列;

③圖解
隊列.png
4.鏈表
①概念
一種非連續,非順序的存儲方式,通過指針將數據進行連接的方式實現。

②特點
a)在創建的時候,不需要指定長度,可以動態調整長度,不易產生碎片;

b)鏈表的每個元素分爲數據和指針,指針指向下一個數據的地址,從而形成串聯;

c)便於數據增刪,不便於數據查詢;

d)鏈表分爲單向鏈表,雙向鏈表,循環列表;

③圖解
鏈表.png
5.樹
①概念
由一個根節點和若干個子樹構成的集合。

②特點
a)有且僅有一個根節點;

b)子樹之間不可以有交集;

c)樹分爲無序樹,有序樹,二叉樹等;

d)樹的深度指的是樹的有多少層;

e)一個節點的度指的是該節點下有多少個子節點;

f)二叉樹指的是每個結點的度≤2的樹。

g)樹的遍歷方式分爲三種,分別是前序遍歷(根左右),中序遍歷(左根右),後序遍歷(左右根);

③圖解
樹.png
6.圖
①概念
由頂點的有窮非空集合和頂點之間邊的集合組成。

②特點
a)圖分爲有向圖和無向圖,區別在於邊是否有方向;

b)圖主要涉及到的內容是最短路徑;

③圖解
圖.png
7.堆
①概念
用於動態分配和釋放程序所使用的對象。

②特點
a)堆分爲最小堆和最大堆,區別在於所有父節點是否大於等於其子節點,是則是最大堆,否則反之;

b)堆是一顆完全二叉樹;

③圖解

堆.png
8.散列表
①概念
根據key-value來進行數據獲取的存儲數據方式。

②特點
a)又名哈希表;

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

c)key以數組的方式存儲在棧內存中,value以鏈表的方式存儲在堆空間中;

d)不同的key通過哈希函數可能得到相同的結果,這時候就發生了哈希碰撞;

③圖解
散列表.png

四、算法

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 二分查找

二分查找又稱折半查找,它是一種效率較高的查找方法。

【二分查找要求】: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;

好了文章就分享到這裏了,喜歡的朋友別忘了關注+點贊喔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章