1. 程式人生 > 實用技巧 >設計模式-原則

設計模式-原則

七大設計原則

所有原則都為了降低類之間的耦合。

一、單一職責

  • 降低類複雜度,一個類負責一項職責。
  • 提高類可讀性,可維護性。
  • 降低變更引起的風險。
  • 通常情況下,應遵循。只有邏輯夠簡單,才可在程式碼級別(通過if...else...)違反;只有類方法數量足夠少,才可在方法級別(定義不同方法實現不同操作)保持單一職責

二、介面隔離

客戶端不應依賴不需要的介面,即對一個類的依賴,應該建立在最小介面上。

如:介面Interface有1、2、3等方法,B類實現了Interface介面。A類通過介面依賴B類,但A只需要用到介面中1、2兩個方法。
按介面隔離原則應把介面分離:
1、Interface分成兩個介面:Interface1有方法1、2;Interface2有方法3;
2、B類實現介面Interface1即可;
3、如果後續某個類需要用到Interface中1、2、3方法,再讓B實現Interface2。

三、依賴倒轉

  • 底層模組儘量使用抽象類或介面,或兩者都有,程式穩定性更好
  • 變數宣告型別儘量用抽象類或介面。這樣變數引用和實際物件存在一個緩衝層(即介面),利用程式擴充套件和優化。
  • 繼承時遵循里氏替換原則
//客戶使用端
public class Client {
   public static void main(String[] args){
       OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
       openAndCloseTv.open(new Changhong());
       openAndCloseTv.open(new Kangjia());
   }
}

//開關介面
interface IOpenAndCloseTv{
    public void open(ITV tv);
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
    @Override
    public void open(ITV tv) {
        tv.play();
    }
}

//tv型別介面
interface ITV{
    public void play();
}
//定義不同tv型別
class Changhong implements ITV{
    @Override
    public void play() {
        System.out.println("長虹電視開啟。。。");
    }
}
class Kangjia implements ITV{
    @Override
    public void play() {
        System.out.println("康佳電視打開了。。。");
    }
}

結果:
長虹電視開啟。。。
康佳電視打開了。。。

依賴關係傳遞三種方式:

  • 介面傳遞
//開關介面
interface IOpenAndCloseTv{
    public void open(ITV tv);
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
    @Override
    public void open(ITV tv) {
        tv.play();
    }
}
---------------------使用
public static void main(String[] args){
    OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
    openAndCloseTv.open(new Changhong());
    openAndCloseTv.open(new Kangjia());
}
  • 構造方法傳遞
//開關介面
interface IOpenAndCloseTv{
    public void open();
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
    private ITV tv; // 成員變數
    public OpenAndCloseTv(ITV tv){
        this.tv = tv;
    }
    @Override
    public void open() {
        tv.play();
    }
}
---------------------使用
public static void main(String[] args){
    OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(new Changhong());
    openAndCloseTv.open();
    OpenAndCloseTv openAndCloseTv2 = new OpenAndCloseTv(new Kangjia());
    openAndCloseTv2.open();
}
  • setter方法傳遞
//開關介面
interface IOpenAndCloseTv{
    public void open();
    public void setTv(ITV tv)
}
//實現
class OpenAndCloseTv implements IOpenAndCloseTv{
    private ITV tv; // 成員變數
    
    @Override    
    public void setTv(ITV tv){
        this.tv = tv;
    }
    @Override
    public void open() {
        tv.play();
    }
}
---------------------使用
public static void main(String[] args){
    OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
    openAndCloseTv.setTv(new Changhong();
    openAndCloseTv.open();
}

四、里氏替換

一個類被其他類所繼承,該類修改時,必須考慮所有子類。父類修改後,所有子類有可能產生故障。
如何正確使用繼承?--》里氏替換原則

  • 使用基類的地方必須能透明使用子類物件。
  • 使用繼承時,在子類中儘量不要重寫父類方法。
  • 繼承實際讓兩個類耦合性增強,在適當情況下可通過聚合,組合,依賴解決問題,不要通過繼承。

如某類子類需要改寫父類方法,應把子類提升和父類一樣的檔次,把共同方法抽取,讓子類和父類繼承同一個更基層的基類。

若要改變父,就不要做父子關係了,做兄弟關係吧。

public class Liskov {
    public static void main(String[] args){

        A a = new A();
        System.out.println("11-3="+a.func1(11,3));
        System.out.println("1-8="+a.func1(1,8));

        System.out.println("----------------------------");
        B b = new B();
        System.out.println("11-3="+b.func1(11,3)); // 這裡其實已經覆寫父類方法了,所以-的話是錯誤的。
        System.out.println("1-8="+b.func1(1,8));
        System.out.println("11+3+9="+b.func2(11,3));
    }
}

class A {
    public int func1(int num1,int num2){
        return num1 - num2;
    }
}
class B extends A{
    // 重新了A 類方法,可能是無意識
    @Override
    public int func1(int a,int b){
        return a + b;
    }
    public int func2(int a,int b){
        return func1(a,b)+9;
    }
}

通過里氏替換原則:

public class Liskov2 {
    public static void main(String[] args){

        A a = new A();
        System.out.println("11-3="+ a.func1(11,3));
        System.out.println("1-8="+ a.func1(1,8));

        System.out.println("----------------------------");
        B b = new B();
        System.out.println("11+3="+ b.func1(11,3)); // 這裡其實已經覆寫父類方法了,所以-的話是錯誤的。
        System.out.println("1+8="+ b.func1(1,8));
        System.out.println("11+3+9="+ b.func2(11,3));

        System.out.println("11-3="+ b.func3(11,3)); //實際呼叫的是A類的方法。
    }
}
class base{
    //共有的更基礎的方法、成員變數定義到該類
    //如:
    public int funcMult(int num1,int num2){
        return num1*num2;
    }
}
class A extends base{
    public int func1(int num1,int num2){
        return num1 - num2;
    }
}
class B extends base{
    private A  aobj = new A(); //通過組合方式解決繼承問題-step1

    public int func1(int a,int b){
        return a + b;
    }
    public int func2(int a,int b){
        return func1(a,b)+9;
    }
    public int func3(int a,int b){
        return aobj.func1(a,b); //通過組合方式解決繼承問題-step2
    }
}

----------結果
11-3=8
1-8=-7
----------------------------
11+3=14
1+8=9
11+3+9=23
11-3=8

五、開閉原則

  • 最基礎、最重要的設計原則
  • 一個實體如類、模組、函式應【擴充套件開發(對提供方),修改關閉(對使用方)】。抽象構建框架,實現擴充套件細節。
  • 軟體需變化,儘量通過擴充套件程式碼而不是修改已有程式碼。
  • 程式設計中遵循其他原則,及設計模式目的就是遵循開閉原則(核心)。

不遵循OCP原則:

public class Ocp {
    public static void main(String[] args){
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
    }
}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
    //接收Shape物件,呼叫draw方法
    public void drawShape(Shape s) {
        if (s.m_type == 1){
            drawRectangle();
        }else if (s.m_type == 2){
            drawCircle();
        }else if (s.m_type == 3){ //新增圖形,使用方需要進行修改
            drawTriangle();
        }
    }
    public void drawRectangle(){ System.out.println(" 繪製矩形 "); }
    public void drawCircle() { System.out.println(" 繪製圓形 "); }
    //新增圖形,使用方需要進行修改
    public void drawTriangle() { System.out.println(" 繪製三角形 "); }
}

//定義各種圖形
//Shape類,基類,
abstract class Shape {
    int m_type;
}
//矩形
class Rectangle extends Shape {
    Rectangle() { super.m_type = 1; }
}
//圓形
class Circle extends Shape {
    Circle() { super.m_type = 2; }
}
//新增三角形
class Triangle extends Shape {
    Triangle() { super.m_type = 3; }
}

----------結果
 繪製矩形 
 繪製圓形 
 繪製三角形 

遵循OCP原則:

public class Ocp {
    public static void main(String[] args) {
        //使用看看存在的問題
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
        graphicEditor.drawShape(new OtherGraphic());
    }
}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
    //接收Shape物件,呼叫draw方法
    public void drawShape(Shape s) { s.draw(); //只需要新增圖形就可以,不需要修改程式碼。 }
}

//Shape類,遵守ocp原則通過建立一個基類,
abstract class Shape {
    public abstract void draw();//抽象方法
}

//繪製不同影象時直接擴充套件,而不是修改。
//矩形
class Rectangle extends Shape {
    @Override
    public void draw() {System.out.println(" 繪製矩形 "); }
}
//圓形
class Circle extends Shape {
    @Override
    public void draw() { System.out.println(" 繪製圓形 "); }
}
//新增畫三角形
class Triangle extends Shape {
    @Override
    public void draw() { System.out.println(" 繪製三角形 "); }
}
//新增一個圖形
class OtherGraphic extends Shape {
    @Override
    public void draw() { System.out.println(" 繪製其它圖形 "); }
}

----------結果
 繪製矩形 
 繪製圓形 
 繪製三角形 
 繪製其它圖形 

六、迪米特法則

核心是為了降低類之間的耦合。

  • 一個物件對其他物件保持最少了解。
  • 也叫【最少知道原則】,即一個被依賴的類不管多複雜,儘量將邏輯封裝類內部,對外只提供public方法,不對外洩露任何資訊。
  • 更簡單定義:只與【直接朋友(兩物件耦合即朋友,出現在成員變數,方法引數,方法返回值中的類為直接的朋友,出現在區域性變數中的類不是直接的朋友,稱陌生類)】通訊。
  • 耦合方式:依賴、關聯、組合、聚合等。
  • 陌生類最好不要以區域性變量出現在類內部。

非迪米特法則:

//客戶端
public class Demeter1 {
    public static void main(String[] args) {
        //建立了一個 SchoolManager 物件
        SchoolManager schoolManager = new SchoolManager();
        //輸出學院的員工id 和  學校總部的員工資訊
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

//學校總部員工類
class Employee {
    private String id;
    public void setId(String id) { this.id = id; }
    public String getId() { return id; }
}
//學院員工類
class CollegeEmployee {
    private String id;
    public void setId(String id) { this.id = id; }
    public String getId() { return id; }
}

//學院員工管理類
class CollegeManager {
    //返回學院的所有員工
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
        for (int i = 0; i < 10; i++) { //這裡我們增加了10個員工到 list
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("學院員工id= " + i);
            list.add(emp);
        }
        return list;
    }
}
//學校管理類
//分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一個陌生類,這樣違背了 迪米特法則 
class SchoolManager {
    //返回學校總部的員工
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();
        for (int i = 0; i < 5; i++) { //這裡我們增加了5個員工到 list
            Employee emp = new Employee();
            emp.setId("學校總部員工id= " + i);
            list.add(emp);
        }
        return list;
    }

    //該方法完成輸出學校總部和學院員工資訊(id)
    void printAllEmployee(CollegeManager sub) {
        //分析問題
        //1. 這裡的 CollegeEmployee 不是  SchoolManager的直接朋友
        //2. CollegeEmployee 是以區域性變數方式出現在 SchoolManager
        //3. 違反了 迪米特法則 

        //獲取學院員工
        List<CollegeEmployee> list1 = sub.getAllEmployee();
        System.out.println("------------學院員工------------");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
        //獲取學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("------------學校總部員工------------");
        for (Employee e : list2) {
            System.out.println(e.getId());
        }
    }
}
-----------結果
------------學院員工------------
學院員工id= 0
學院員工id= 1
學院員工id= 2
學院員工id= 3
學院員工id= 4
學院員工id= 5
學院員工id= 6
學院員工id= 7
學院員工id= 8
學院員工id= 9
------------學校總部員工------------
學校總部員工id= 0
學校總部員工id= 1
學校總部員工id= 2
學校總部員工id= 3
學校總部員工id= 4

遵循迪米特法則:

//客戶端
public class Demeter1 {
    public static void main(String[] args) {
        System.out.println("~~~使用迪米特法則的改進~~~");
        //建立了一個 SchoolManager 物件
        SchoolManager schoolManager = new SchoolManager();
        //輸出學院的員工id 和  學校總部的員工資訊
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

//學校總部員工類
class Employee {
    private String id;
    public void setId(String id) { this.id = id; }
    public String getId() { return id; }
}
//學院員工類
class CollegeEmployee {
    private String id;
    public void setId(String id) { this.id = id; }
    public String getId() { return id; }
}

//學院員工管理類
class CollegeManager {
    //返回學院的所有員工
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
        for (int i = 0; i < 10; i++) { //這裡我們增加了10個員工到 list
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("學院員工id= " + i);
            list.add(emp);
        }
        return list;
    }
    //輸出學院員工的資訊
    public void printEmployee() {
        //獲取到學院員工
        List<CollegeEmployee> list1 = getAllEmployee();
        System.out.println("------------學院員工------------");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
    }
}
//學校管理類
//分析 SchoolManager 類的直接朋友類有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一個陌生類,這樣違背了 迪米特法則
class SchoolManager {
    //返回學校總部的員工
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();

        for (int i = 0; i < 5; i++) { //這裡我們增加了5個員工到 list
            Employee emp = new Employee();
            emp.setId("學校總部員工id= " + i);
            list.add(emp);
        }
        return list;
    }

    //該方法完成輸出學校總部和學院員工資訊(id)
    void printAllEmployee(CollegeManager sub) {
        //分析問題
        //1. 將輸出學院的員工方法,封裝到CollegeManager
        sub.printEmployee();

        //獲取到學校總部員工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("------------學校總部員工------------");
        for (Employee e : list2) {
            System.out.println(e.getId());
        }
    }
}
----------結果
~~~使用迪米特法則的改進~~~
------------學院員工------------
學院員工id= 0
學院員工id= 1
學院員工id= 2
學院員工id= 3
學院員工id= 4
學院員工id= 5
學院員工id= 6
學院員工id= 7
學院員工id= 8
學院員工id= 9
------------學校總部員工------------
學校總部員工id= 0
學校總部員工id= 1
學校總部員工id= 2
學校總部員工id= 3
學校總部員工id= 4

七、合成複用原則

  • 儘量使用合成/聚合的方式,不是使用繼承。

依賴:通過方法引數依賴進來。
聚合:新增類屬性,通過方法設定進來。
組合:構建屬性直接new一個出來。

設計原則核心思想

  • 找出可能需變化之處獨立出來,不要和不需變化的程式碼混寫。
  • 針對介面程式設計,不是具體實現類程式設計
  • 為互動物件之間低耦合設計努力