【設計模式】設計模式的七大原則-1

在我們的稱後續的編寫過程中,我們會面臨着來自耦合。內聚性以及可維護性,可擴展性,重用性,靈活性等多方面的挑戰,設計模式爲了讓程序具有更好的:

  • 代碼重用性(即相同功能的代碼,不用多次編寫)
  • 可讀性(即:編程規範,便於其他人閱讀理解)
  • 可靠性(即:當我們增加新功能是,非常方便,對原來的功能沒有影響)
  • 使程序實現高內聚,低耦合的特性

設計模式的七大原則

不要問爲什麼設計模式要這麼去設計,這個只是設計模式的一個開發規範,你不遵守也沒關係,但是我們應該去遵守這個規範,方便你我他。

單一職責原則

基本介紹

簡單的理解就是:一個類只負責一項職責,就像筆者一樣,一生只夠愛一人,雖然目前還是單身。

注意點

  • 降低類的複雜性,一個類只負責一項職責
  • 提高類的可讀性,可維護性
  • 降低變更引起的風險
  • 只有邏輯足夠簡單,纔可以在代碼級別違反單一職責原則;只有類中方法只夠少,可以再方法級別保持單一原則

接口隔離原則

基本介紹

簡單的理解就是:一個類對另一個類的依賴應該建立在最小的接口上。比如說,我是安徽的,安徽是中國的一個省,是依賴於中國的,我是依賴於安徽的,但是這個時候,雖然可以說,我是依賴於中國的,但是,我們不能這麼說,因爲安徽是我們的依賴關係中最小的那個依賴接口,所以說,我們依賴於安徽(大致是這個意思),看個圖:

1573090935925

A會通過接口依賴類B,C會通過接口依賴D,如果接口對於A,C來說不是最小接口的,那麼B和D就要去實現他們不需要的方法;

按照隔離原則,A,C分別於他們需要的接口建立依賴關係,也就是採用依賴隔離。

存在的問題以及改進思路

  1. 類A通過接口依賴於類B,類C通過接口依賴於D,如果接口對於AC不是最小的接口,那麼BD就必須要去實現他們不需要的方法;

  2. 將接口拆分爲獨立的幾個接口,AC分別於他們需要的接口建立依賴關係,也就是採用依賴隔離。

  3. 效果圖如下

1573091399104

依賴倒轉(倒置)原則

基本介紹

依賴倒轉原則是指:

  • 高層模式不應該依賴底層模式,二者都應該依賴其抽象。
  • 抽象不應該依賴細節,細節應該依賴抽象
  • 依賴倒轉的中心思想是面向接口編程
  • 依賴倒轉原則是基於這樣的設計理念:相比於細節的多變性,抽象的東西要穩定的多。以抽象爲基礎搭建的架構比以細節爲基礎的架構要穩定的多。在Java中,抽象指的是接口或抽象類,細節就是具體的實現類。
  • 使用接口或抽象類的目的是指定好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。

看個實例:

package com.ci123.dependence;

/**
 * Copyright (c) 2018-2028 Corp-ci All Rights Reserved
 * <p> 依賴倒轉(倒置)原則
 * Project: design-pattern
 * Package: com.ci123.dependence
 * Version: 1.0
 * <p>
 * Created by SunYang on 2019/11/7 10:14
 */
public class DependenceInversionPrinciple {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new EmailIR());
        person.receive(new WeiXinIR());
    }
}

class Email {
    public String getInfo() {
        return "電子郵件信息:Hello,World";
    }
}

/**
 * 完成Person接收消息的功能
 * 方式1 分析:
 * 1. 簡單,比較容易想到的
 * 2. 如果我們獲取的對象是 微信,短信等,則新增類,同時Person也要增加相應的接收方法
 * 3. 解決思路: 引入一個抽象的接口 IReceiver ,表示接收者 , 這樣Person類與接口IReceiver發生依賴
 * 因爲Email,WeiXin等屬於接收的範圍,他們各自實現IReceiver接口就OK,這樣我們就符合依賴倒轉原則
 */
class Person {
    public void receive(IRceiver iRceiver) {
        System.out.println(iRceiver.getInfo());
    }
}

interface IRceiver {
    String getInfo();
}

class EmailIR implements IRceiver {

    @Override
    public String getInfo() {
        return "電子郵件信息(IReceiver):Hello,World";
    }
}

class WeiXinIR implements IRceiver {

    @Override
    public String getInfo() {
        return "微信信息(IReceiver):Hello,World";
    }
}

依賴關係的三種傳遞

  • 接口傳遞
  • 構造方法傳遞
  • setter方式傳遞

注意點

  • 低層模塊儘量都要有抽象類或接口,或者兩者都有,程序穩定性更好
  • 變量的聲明類型儘量是抽象類或接口,這樣我們的變量引用和實際對象間,就存在一個緩衝層,利於程序擴展和優化
  • 集成式遵循里氏替換原則

里氏替換原則

OO中的繼承性的思考和說明

  • 繼承包含這樣一層含義:父類中凡是已經實現好的方法,實際上是設定規範和契約,雖然他不強制要求所有的子類都必須遵守這些契約,但是如果子類對這樣已經實現的方法任意修改,就會對整個集成體系造成破壞
  • 繼承再給程序設計帶來便利的同時,也帶來了弊端,比如使用繼承會給程序帶來侵入性,程序的可移植性降低,增加對象間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改的時候,必須考慮到所有的子類,並且父類修改後,所有涉及到子類的功能都有可能產生故障。
  • 思考:在編程中,如何正確的使用繼承?=> 里氏替換原則

基本介紹

  1. 如果對每個類型爲T1的對象 o1 ,都有類型爲 T2 的對象 o2 ,使得以T1定義的所有程序P在所有的對象 o1 都替換成 o2時,程序P的行爲沒有發生變化,那麼類型T2 是類型T1的子類型,換句話說,所有引用基類的地方必須要透明地使用其子類的對象。
  2. 在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類的方法
  3. 里氏替換原則告訴我們,繼承實際上讓兩個子類耦合性增強了,在適當的情況下,可以通過聚合,組合,依賴,來解決問題。

這裏我們看個例子:

package com.ci123.base.liskov;
/**
 * Copyright (c) 2018-2028 Corp-ci All Rights Reserved
 * <p>
 * Project: design-pattern
 * Package: com.ci123.base.liskov
 * Version: 1.0
 * <p> 這裏可以看到,我們的B重寫了A的func1(int num1,int num2)方法,無意中將減法改成了加法,但是我們的整個繼承體系
 * 是已經被破壞了的,會導致繼承體系的複用性會比較差,特別是運行多態比較繁瑣的時候
 * <p>
 * 改進:
 * 通用的做法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴,聚合,組合等關係替代。
 * 即:
 * base <- A
 * base <- B
 * B <<- A
 * <p>
 * Created by SunYang on 2019/11/7 10:55
 */
public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("5-3="   a.func1(5, 3));
        B b = new B();
        System.out.println("5-3="   b.func1(5, 3)); // 這裏原本是要輸出 5-3 的
        System.out.println("/************************************************/");
        AA aa = new AA();
        System.out.println("5-3="   aa.func1(5, 3));
        BB bb = new BB();
        System.out.println("5-3="   bb.func(5, 3)); // 這裏原本是要輸出 5-3 的
    }
}
class Base {
    // 這裏放很基礎的方法和成員
}
/********************************************************************************************/
class A {
    // 返回兩個數的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
// B 繼承A
// 增加一個新的功能,完成兩個數相加
class B extends A {
    // 這裏重寫 A類方法
    @Override
    public int func1(int num1, int num2) {
        // 這裏是無意識的重寫,是我們不需要的重寫,我們本意是要求 減法的
        return num1   num2;
    }

    public int func2(int num1, int num2) {
        return func1(num1, num2) * 8;
    }
}
/********************************************************************************************/
class AA {

    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
class BB extends Base {
    // 這裏重寫 A類方法
    public int func1(int num1, int num2) {
        // 這裏是無意識的重寫,是我們不需要的重寫,我們本意是要求 減法的
        return num1   num2;
    }

    public int func2(int num1, int num2) {
        return func1(num1, num2) * 8;
    }

    private AA aa = new AA();
    // 這裏想要用AA方法
    public int func(int num1 , int num2){
        return this.aa.func1(num1 , num2) ;
    }
}

開閉原則

基本介紹

  • 是編程中最基礎,最重要的設計原則
  • 一個軟件實體如類,模塊核函數應該對擴展開放(對提供方),對修改關閉(對使用方)。用抽象構建框架,用實現擴展細節
  • 當軟件需要變化時,儘量通過擴展軟件實體的行爲來實現變化,而不是通過修改已有的代碼來實現變化
  • 編程中遵循其他原則,以及使用設計模式的目的就是遵循開閉原則
package com.ci123.base.ocp;

/**
 * Copyright (c) 2018-2028 Corp-ci All Rights Reserved
 * <p>
 * Project: design-pattern
 * Package: com.ci123.base.ocp
 * Version: 1.0
 * <p> 1. 優點比較好理解,簡單易操作
 * 2. 缺點是違反了設計模式的OCP原則,即對擴展開放(提供方),對修改關閉(使用方)。即當我們給類增加新功能的時候,儘量不要修改代碼,或者儘量少修改代碼
 * 3. 如果我們要增加一個新的圖形種類,我們要做的修改還是挺多的
 * Created by SunYang on 2019/11/7 11:20
 */
public class OCPDemo {
    public static void main(String[] args) {
        // 使用看看存在的問題
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
    }
}
// 使用方,繪圖
class GraphicEditor{
    public void drawShape(Shape shape){
        switch (shape.m_type){
            case 1:
                drawRectangle(shape);
                break;
            case 2:
                drawCircle(shape);
                break;
            case 3:
                drawTriangle(shape);
                break;
            default:
                break;
        }
    }

    private void drawRectangle(Shape shape){
        System.out.println("矩形");
    }
    private void drawCircle(Shape shape){
        System.out.println("圓");
    }
    private void drawTriangle(Shape shape){
        System.out.println("三角形");
    }
}
// 基類
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 ;
    }
}

在看看修改後的:

public class OCPDemo {
    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 Other());
    }
}

// 使用方,繪圖
class GraphicEditor {
    public void drawShape(Shape shape) {

        shape.draw();
    }

}

// 基類
abstract class Shape {
    int m_type;

    abstract void draw();
}

class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }

    @Override
    void draw() {
        System.out.println("矩形");
    }
}

class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }

    @Override
    void draw() {
        System.out.println("圓");
    }
}

// 新增三角形
class Triangle extends Shape {
    Triangle() {
        super.m_type = 3;
    }

    @Override
    void draw() {
        System.out.println("三角形");
    }
}

class Other extends Shape {
    Other() {
        super.m_type = 4;
    }

    @Override
    void draw() {
        System.out.println("其他的");
    }
}

迪米特法則

基本介紹

  • 一個對象應該對其他對象保持最少的瞭解
  • 類與類關係密切,耦合度大
  • 迪米特法則又被稱爲最好知道原則,即一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類不管對麼複雜,都儘量將邏輯封裝在類的內部。對外除了提供的public方法,不對外泄露任何信息;
  • 迪米特法則還有個更簡單的定義:只與直接朋友通信
  • 直接朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這倆搞個對象之間是朋友關係,耦合方式很多,而出現在局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量形出現在類的內部。
package com.ci123.base.dp;

import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.xpath.internal.SourceTree;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;

/**
 * Copyright (c) 2018-2028 Corp-ci All Rights Reserved
 * <p> 迪米特法則
 * Project: design-pattern
 * Package: com.ci123.base.dp
 * Version: 1.0
 * <p>
 * Created by SunYang on 2019/11/7 11:50
 */
// 有一個學校,下屬有各個學院和總部,現要求打印出學校總部員工ID和學院員工ID
public class DemeterDemo {
    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 String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
// 管理學院員工的管理類
class CollegeManager{
    // 返回學院的所有員工
    public List<CollegeEmployee> geAllEmployee(){
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i  ) {
            CollegeEmployee employee = new CollegeEmployee();
            employee.setId("學院員工ID="   i);
            list.add(employee) ;
        }
        return list ;
    }
}
// 分析SchoolManager類的直接朋友有哪些,Employee,CollegeManager
// CollegeEmployee 不是直接朋友關係,而是一個陌生類,這樣違背了 迪米特法則
class SchoolManager{
    // 返回學校的總員工
    public List<Employee> getAllEmployees(){
        List<Employee> list = new ArrayList<>();

        for (int i = 0; i < 20; i  ) {
            Employee employee = new Employee();
            employee.setId("學校ID="   i);
            list.add(employee) ;
        }
        return list ;
    }
    // 該方法完成輸出學校總部和學院員工信息 ID
    void printAllEmployee(CollegeManager manager){
        // 分析問題
        // 1. 這裏的 CollegeEmployee 不是SchoolManager 的直接朋友
        // 2. CollegeEmployee 是以局部變量方式出現在SchoolManager
        // 3. 違反了 迪米特 法則

        // 獲取到學院員工
        List<CollegeEmployee> list = manager.geAllEmployee();
        System.out.println("========== 學院員工 ============");
        for (CollegeEmployee employee : list) {
            System.out.println(employee.getId());
        }

        // 獲取到學校員工
        List<Employee> allEmployee = this.getAllEmployees();
        System.out.println("========== 學校員工 ============");
        for (Employee employee : allEmployee) {
            System.out.println(employee.getId());
        }
    }
}

改進如下:

// 管理學院員工的管理類
class CollegeManager{
    // 返回學院的所有員工
    public List<CollegeEmployee> geAllEmployee(){
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i  ) {
            CollegeEmployee employee = new CollegeEmployee();
            employee.setId("學院員工ID="   i);
            list.add(employee) ;
        }
        return list ;
    }
    public void printEmployee(){
        // 獲取到學院員工
        List<CollegeEmployee> list = this.geAllEmployee();
        System.out.println("========== 學院員工 ============");
        for (CollegeEmployee employee : list) {
            System.out.println(employee.getId());
        }
    }
}

class SchoolManager{
    // 返回學校的總員工
    public List<Employee> getAllEmployees(){
        List<Employee> list = new ArrayList<>();

        for (int i = 0; i < 20; i  ) {
            Employee employee = new Employee();
            employee.setId("學校ID="   i);
            list.add(employee) ;
        }
        return list ;
    }
    // 該方法完成輸出學校總部和學院員工信息 ID
    void printAllEmployee(CollegeManager manager){
        // 封裝到 CollegeManager 裏面
        manager.printEmployee();

        // 獲取到學校員工
        List<Employee> allEmployee = this.getAllEmployees();
        System.out.println("========== 學校員工 ============");
        for (Employee employee : allEmployee) {
            System.out.println(employee.getId());
        }
    }
}

注意點

  • 迪米特法則的核心是降低類之間的耦合
  • 但是注意,由於每個類減少不必要的依賴,因此迪米特法則只是要求降低類間(對象間)耦合關係,並不是要求完全沒有依賴關係

合成複用原則

基本介紹

一句話:儘量使用合成 / 聚合的方式 , 而不是使用繼承

1573106532862

設計原則核心思想

  • 找出應用中可能需要變化之處,把他們獨立出來,不要和那些不需要變化的代碼混合在一起
  • 針對接口編程,而不是針對實現編程
  • 爲了交互對象之間的松藕合設計而努力
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章