設計模式7大原則

来源:https://www.cnblogs.com/1314xf/archive/2018/12/15/10122504.html
-Advertisement-
Play Games

面向對象設計原則 概述 對於面向對象軟體系統的設計而言,在支持可維護性的同時,提高系統的可復用性是一個至關重要的問題,如何同時提高一個軟體系統的可維護性和可復用性是面向對象設計需要解決的核心問題之一。在面向對象設計中,可維護性的復用是以設計原則為基礎的。每一個原則都蘊含一些面向對象設計的思想,可以從 ...


面向對象設計原則

概述

對於面向對象軟體系統的設計而言,在支持可維護性的同時,提高系統的可復用性是一個至關重要的問題,如何同時提高一個軟體系統的可維護性和可復用性是面向對象設計需要解決的核心問題之一。在面向對象設計中,可維護性的復用是以設計原則為基礎的。每一個原則都蘊含一些面向對象設計的思想,可以從不同的角度提升一個軟體結構的設計水平。 面向對象設計原則為支持可維護性復用而誕生,這些原則蘊含在很多設計模式中,它們是從許多設計方案中總結出的指導性原則。面向對象設計原則也是我們用於評價一個設計模式的使用效果的重要指標之一,在設計模式的學習中,大家經常會看到諸如“XXX模式符合XXX原則”、“XXX模式違反了XXX原則”這樣的語句。
最常見的7種面向對象設計原則如下表所示:

1.單一職責原則

單一職責定義

一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因

從定義中不難思考,一個類的所做的事情越多,也就越難以復用,因為一旦做的事情多了,職責的耦合度就變高了所以我們根據這個原則應該將不同職責封裝在不同類中,不同的變化封裝在不同類中。從我們平常的開發中不難發現,如果一個類或者方法介面等等只做一件事,那麼可讀性很高,並且復用性也很高,並且一旦需求變化,也容易維護,假如你一個類糅雜多個職責,那麼很難維護。

單一職責舉例分析

從實際業務來剝離一個例子:現在有這麼一種情況,某租車平臺個人模塊類涉及多個方法,有如下登錄、註冊、支付寶押金支付、微信押金支付、支付寶套餐支付、微信套餐支付、整個結構如下:

     /**
     * 個人模塊
     */
    @Controller
    public class userController{
        /**
         * 登錄
         */
        public void login(){
        }

        /**
         * 註冊
         */
        public void register(){
        }
        /**
         * 押金支付(阿裡)
         */
        public void payAliDeposit(){
        }

        /**
         * 押金支付(微信)
         */
        public void payWXDeposit(){
        }

        /**
         * 套餐支付(阿裡)
         */
        public void payAliPackage(){
        }

        /**
         * 套餐支付(微信)
         */
        public void payWXPackage(){
        }
    }

我們可以看到很多功能都糅雜在一起,一個類做了那麼多事情,很臃腫,別提維護,就連找代碼都很困難,所以我們可以對這個UserController進行拆解,與此同時我們應該分包,比如這個應該在xxx.xxx.userMoudule下麵,可能支付相關的有公共的方法,登錄抑或也有公共的方法,那邊抽成公共服務去調用。

public class LoginController(){}
public class registerController(){}
public class depositPayController(){
    // 支付寶支付
    // 微信支付
}
public class packagePayController(){
    // 支付寶支付
    // 微信支付
}

整個方案實現的目的就是為瞭解決高耦合,代碼復用率低下的問題。單一職責理解起來不難,但是實際操作需要根據具體業務的糅雜度來切割,實際上很難運用。

2.開閉原則

開閉原則簡介

開閉原則是面向對象的可復用設計的第一塊基石,它是最重要的面向對象設計原則,定義如下:

一個軟體實體應當對擴展開放,對修改關閉。即軟體實體應儘量在不修改原有代碼的情況下進行擴展。

軟體實體包括以下幾個部分:

  • 項目或軟體產品中按照一定的邏輯規則劃分的模塊
  • 抽象和類
  • 方法

註意:開閉原則是指對擴展開放,對修改關閉,並不是說不做任何的修改

開閉原則的優勢

  • 可以使原來的測試代碼依舊可以運行,只需要對擴展的代碼進行測試即可
  • 可以提高代碼的復用性
  • 可以提高系統的維護性

如何使用開閉原則

  • 抽象約束
    • 通過介面或者抽象類約束擴展,對擴展進行邊界限定,不允許出現在介面或抽象類中不存在的public方法;
    • 參數類型、引用對象儘量使用介面或者抽象類,而不是實現類;(針對抽象編程)
    • 抽象層儘量保持穩定,一旦確定即不允許修改。
  • 元數據控制模塊行為
    通俗來說就是通過配置文件來操作數據,spring的控制反轉就是一個很典型的例子。
  • 約定優於配置
  • 封裝變化
  • 將相同的變化封裝到一個介面或者類中
  • 將不同的變化封裝到不同的類或者介面中(單一職責的體現)

案例

某公司開發的租車系統有一個押金支付功能,支付方式有支付寶、阿裡支付,後期可能還有銀聯支付、易支付等等,原始的設計方案如下:

1544321940668

// 客戶端調用-押金支付選擇支付手段
public class DepositPay {

    void pay(String type){
        if(type.equals("ali")){
            AliPay aliPay = new AliPay();
            aliPay.pay();
        }else if(type.equals("wx")){
            WXPay wxPay = new WXPay();
            wxPay.pay();
        }
    }
}
// 支付寶支付
public class AliPay {
    public void pay() {
        System.out.println("正在使用支付寶支付");
    }
}
// 微信支付
public class WXPay{
    public void pay() {
        System.out.println("正在使用微信支付");
    }
}

在以上代碼中,如果需要增加銀聯支付,如YLPay,那麼就必須要修改DepositPay中的pay方法的源代碼,增加新的判斷邏輯,違反了開閉原則(對修改關閉,對擴展開放,註意這邊的銀聯支付相當於擴展,所以它沒有違反規則),所以現在必須重構此代碼,讓其遵循開閉原則,做法如下:

  1. 增加一個介面,使得各種具體支付實現其介面
  2. DepositPay類針對介面編程,由客戶端來決定具體使用哪種支付方式

重構後的圖如下所示:

1544324553495

在上圖中我們引入了介面Pay,定義了pay方法,並且DepositPay是針對介面編程,通過setPayMode()由客戶端來實例化具體的支付方式,在DepositPay的pay()方法中調用payMode對象來支付。如果需要增加新的支付方式,比如銀聯支付,只需要讓它也實現Pay介面,在配置文件中配置銀聯支付即可,依賴註入是實現此開閉原則的一種手段,在這裡不贅述,源碼如下:

public interface Pay {
    // 支付
    void pay();
}
public class AliPay implements Pay {
    @Override
    public void pay() {
        System.out.println("正在使用支付寶支付");
    }
}
public class WXPay implements Pay{
    @Override
    public void pay() {
        System.out.println("正在使用微信支付");
    }
}
// 客戶端調用-押金支付選擇支付手段
public class DepositPay {
    // 支付方式 (這邊可以通過依賴註入的方式來註入)
    // 支付方式可以寫在配置文件中
    // 現在不管你選用何種方式,我都不需要更改
    @Autowired
    Pay payMode;
    void pay(Pay payMode){
        payMode.pay();
    }
}

因為配置文件可以直接編輯,且不需要編譯,所以一般不認為更改配置文件是更改源碼。如果一個系統能做到只需要修改配置文件,無需修改源碼,那麼複合開閉原則。

3.里氏代換原則

里氏替換原則簡介

Barbara Liskov提出:

標准定義:如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程式P在所有的對象o1代換o2時,程式P的行為沒有變化,那麼類型S是類型T的子類型。

上面的定義可能比較難以理解,簡單理解就是所有引用基類(父類的)地方都可以用子類來替換,且程式不會有任何的異常。但是反過來就不行,所有使用子類的地方則不一定能用基類來替代,很簡單的例子狗是動物,不能說動物是狗,因為可能還有貓。。。。

里氏替換原則是實現開閉原則的重要方式之一,由於使用基類的所有地方都可以用子類來替換,因此在程式中儘量使用基類來定義對象,在運行時確定其子類類型

里氏替換原則約束

  • 子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。
  • 子類中可以添加特有方法(父類中不存在),此時則無法在以父類定義的對象中使用該方法,除非在使用的時候強轉基類成子類進行調用。
  • 當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。
  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

所以我們在運用里氏替換原則的時候,儘量把父類設計為抽象類或者介面,讓子類繼承父類或者實現介面並實現在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現。里氏代換原則是開閉原則的具體實現手段之一。

里氏替換原則實戰

某租車系統客戶分為普通用戶(customer)和VIP客戶(VIPCustomer),系統需要提供一個根據郵箱重置密碼的功能。原始設計圖:

1544358831335

在編寫重置密碼的時候發現,業務邏輯是一樣的,存在著大量的重覆代碼,而且還可能增加新的用戶類型,為了減少代碼重覆性,使用里氏替換原則進行重構:

1544358831335

圖上重置密碼交由ResetPassword類去處理,只需要傳入Customer類即可,不管任何類型的Customer類,只要繼承自Customer,都可以使用里氏替換原則進行替換,假如有新的類型,我們只需要在配置文件中註入新的類型即可。代碼如下(簡單意會一下):

// 抽象基類
public abstract class Customer {
}
public class CommonCustomer  extends Customer{
}
public class VIPCustomer  extends Customer{
}
// 重置密碼邏輯在這裡實現,只需要傳入對應的類型即可
public class ResetPassword {
    void resetPassword(Customer customer){
    }
}

里氏替換原則是實現開閉原則不可或缺的手段之一,在本例中,通過傳遞參數使用基類對象,針對抽象編程,從而滿足開閉原則。

4.依賴倒轉原則

依賴倒轉原則簡介

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對介面編程,而不是針對實現編程。

可以通俗的定義為兩種:

  1. 高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象。
  2. 抽象不應該依賴於具體實現,具體實現應該依賴於抽象。

要求我們在設計程式的時候儘量使用層次高的抽象層類,即使用介面和抽象類進行變數的聲明、參數類型聲明、方法返回類型聲明以及數據類型轉換等等,同時要註意一個具體類應該只實現抽象類或者介面中存在的方法,不要給出多餘的方法,這樣抽象類將無法調用子類增加的方法.我們可以通過配置文件來寫入具體類,這樣一旦程式行為改變,可直接改變配置文件,而不需要更改程式,重新編譯,通過依賴倒轉原則來滿足開閉原則。

在實現依賴倒轉原則時,我們需要針對抽象層編程,而將具體類的對象通過依賴註入(DependencyInjection, DI)的方式註入到其他對象中,依賴註入是指當一個對象要與其他對象發生依賴關係時,通過抽象來註入所依賴的對象。常用的註入方式有三種,分別是:構造註入,設值註入(Setter註入)和介面註入

依賴倒轉原則實例

這部分可以參照上面開閉原則案例,可以從那例子中看出,開閉原則,依賴倒轉原則,里氏替換原則同時出現了,可以說`開閉原則是我們要實現的目標,而里氏替換原則是實現手段之一,而同時里氏替換原則又是依賴倒轉原則實現的基礎,因為加入沒有這個理論,依賴倒轉原則是不成立的,無法針對抽象編程,要註意這3個原則基本都是同時出現的。

5.介面隔離原則

介面隔離原則簡介

介面隔離原則的兩個定義:

1:使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些它不需要的介面

2:類間的依賴關係應該建立在最小的介面上

介面的含義:

  1. 一個介面代表一個角色,不應該將不同的角色都交給一個介面,因為這樣可能會形成一個臃腫的大介面;
  2. 特定語言的介面,表示介面僅僅是提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應當為客戶端提供儘可能小的單獨的介面,而不要提供大的總介面。

根據介面隔離原則,我們可明白,每個介面都應只承擔一種相對獨立的角色,不幹不該乾的事情.

實例演示

場景:模擬動物平時的動作,當然也包括人,最初的設計就是一個總介面IAnimal,裡面定義動物會有的一些動作。

1544426255953

代碼如下:

 public interface IAnimal{
        /**
         * 吃飯
         */
        void eat();

        /**
         * 工作
         */
        void work();

        /**
         * 飛行
         */
        void  fly();
    }
 public class Tony implements IAnimal{

        @Override
        public void eat() {
            System.out.println("tony吃");
        }

        @Override
        public void work() {
            System.out.println("tony工作");
        }

        @Override
        public void fly() {
            System.out.println("tony不會飛");
        }
    }
public class Bird implements IAnimal{

        @Override
        public void eat() {
            System.out.println("鳥吃");
        }

        @Override
        public void work() {
            System.out.println("鳥工作");
        }

        @Override
        public void fly() {
            System.out.println("鳥飛");
        }
    }
    

根據上面的寫法發現Tony需要實現飛的介面,這很明顯不僅僅是多餘,而且不合理,因此需要通過介面隔離原則進行重構:

1544428677838

/**
 * 抽象動物的行為
 */
public interface IAnimal {
    /**
     * 吃飯
     */
    void eat();

    /**
     * 睡覺
     */
    void sleep();
}
/**
 * 高級動物人 的行為
 */
public interface IAdvancedAnimalBehavior {
    /**
     * 打牌
     */
    void playCard();

    /**
     * 騎車
     */
    void byBike();
}
/**
 * 低級動物的行為
 */
public interface IJuniorAnimalBehavior {
    /**
     * fly
     */
    void fly();
}
/**
 * 實現高級動物人的共通方法
 */
public class AbstractAdvancedAnimal implements IAnimal {
    @Override
    public void eat() {
        System.out.println("人吃");
    }

    @Override
    public void sleep() {
        System.out.println("人睡");
    }
}
/**
 * 實現低級動物人的共通方法
 */
public class AbstractJuniorAnimal implements IAnimal {
    @Override
    public void eat() {
        System.out.println("動物吃");
    }

    @Override
    public void sleep() {
        System.out.println("動物睡");
    }
}
// tony
public class Tony extends AbstractAdvancedAnimal implements IAdvancedAnimalBehavior {
    @Override
    public void playCard() {
        System.out.println("tony打牌");
    }

    @Override
    public void byBike() {
        System.out.println("tony騎車");
    }
}
// 鳥
public class Bird extends AbstractJuniorAnimal implements IJuniorAnimalBehavior{
    @Override
    public void fly() {
        System.out.println("鳥飛");
    }
}

重構之後,首先定義了一個總的動物介面的大類,然後分別使用了兩個抽象類(一個是高級動物,一個是低級動物)分別去實現這些公共的方法,實現中可以拋出異常,表明繼承此抽象類的類可以選擇性的重寫,可不重寫。之後再定義了兩個行為介面表明高級動物和低級動物所特有的,這樣使得介面之間完全隔離,動物介面不再糅雜各種各樣的角色,當然介面的大小尺度還是要靠經驗來調整,不能太小,會造成介面泛濫,也不能太大,會背離介面隔離原則。

6.合成復用原則

合成復用原則簡介

合成復用原則(Composite Reuse Principle, CRP):儘量使用對象組合,而不是繼承來達到復用的目的。

通過合成復用原則來使一些已有的對象使之成為對象的一部分,一般通過組合/聚合關係來實現,而儘量不要使用繼承。因為組合和聚合可以降低類之間的耦合度,而繼承會讓系統更加複雜,最重要的一點會破壞系統的封裝性,因為繼承會把基類的實現細節暴露給子類,同時如果基類變化,子類也必須跟著改變,而且耦合度會很高。

7.迪米特法則

參考:https://www.cnblogs.com/muzongyan/archive/2010/08/05/1793454.html

參考:https://blog.csdn.net/lovelion/article/details/7537584

參考:https://blog.csdn.net/qq_34966814/article/details/79475977



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 傳統的佈局解決方案 盒狀模型 diplay + position + float 缺陷:對於一些特殊佈局(垂直居中)和網格式佈局(幾行幾列)不易實現 09年W3C頒佈的 flex佈局 使用簡單、API完整豐富、響應式動態地實現各種頁面佈局(一些常見的佈局思想) 當然針對Gekco30+以下,最好的布 ...
  • 先說一下網路的層級:由下往上分為 物理層、數據鏈路層、網路層、傳輸層、會話層、表示層和應用層 1、TCP和UDP TCP:是面向連接的一種傳輸控制協議。屬於傳輸層協議。TCP連接之後,客戶端和伺服器可以互相發送和接收消息,在客戶端或者伺服器沒有主動斷開之前,連接一直存在屬於長連接。 優點:安全、傳輸 ...
  • 國內高層建築不斷興建,它的特點是高度高、層數多、體量大。面積可達幾萬平方米到幾十萬平方米。這些建築都是一個個龐然大物,高高的聳立在地面上,這是它的外觀,而隨之帶來的內部的建築設備也是大量的。為了提高設備利用率,合理地使用能源,加強對建築設備狀態的監視等,自然地就提出了樓宇自動化控制系統。下麵我們將用 ...
  • Bootstrap -- 插件: 模態框、滾動監聽、標簽頁 1. 模態框(Modal): 覆蓋在父窗體上的子窗體。 使用模態框: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; cha ...
  • 作者按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用 和`python`兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :) 原文地址是: "《每天一個設計模式之組合模式》" 歡迎關註個人技術博客: "godbmw.com" 。每周 1 篇原創技術分 ...
  • 使用橋接模式可以將類型的抽象和具體實現進行分離,兩者通過橋接模式進行關聯,從而達到解耦 介紹 橋接模式屬於結構型模式。在現實世界中,我們裝修房子時,佈線的工人和安裝空調的工人之間可以同時工作,不用互相依賴。而對於屋主人來講也不用關係他們具體時怎麼工作的,只需要等他們完成即可。在軟體開發中,當我們面對 ...
  • 建造者模式 建造者模式適用場景: 建造一個複雜的對象適用,將構建對象的過程分開,每個類單獨構造對象的一部分,最後組裝起來,返回我們需要的對象。 下麵的例子主要講解構造一個飛船 Demo: //要獲得的對象,但是各個組件要拆分開,讓對應的類去實現 class AirShip { private Orb ...
  • 見名知其意,適配器可用於對多個不相容介面提供適配橋梁 介紹 適配器模式屬於結構型模式。在現實世界中,這個模式適用的較為廣泛,比如 DIY 一些電子產品,主要元器件提供的是標準介面,那麼無論我們購買什麼品牌的元器件,最終都能組裝起來正常運行。 類圖描述 由上圖可知,我們通過定義 IAdvancedMe ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...