面向介面編程原理與實踐

来源:https://www.cnblogs.com/appsucc/archive/2020/06/02/13034479.html
-Advertisement-
Play Games

面向介面編程能非常有效地提高代碼質量,可以將介面和實現相分離,封裝不穩定的實現,暴露穩定的介面。上游系統面向介面而非實現編程,不依賴不穩定的實現細節,這樣當實現發生變化的時候,上游系統的代碼基本上不需要做改動,以此來降低耦合性,提高擴展性。 ...


面向介面編程原理

“基於介面而非實現編程”這條原則的英文描述是:“Program to an interface, not an implementation”。我們理解這條原則的時候,千萬不要一開始就與具體的編程語言掛鉤,局限在編程語言的“介面”語法中(比如 Java 中的 interface 介面語法)。這條原則最早出現於 1994 年 GoF 的《設計模式》這本書,它先於很多編程語言而誕生(比如 Java 語言),是一條比較抽象、泛化的設計思想。

這條原則能非常有效地提高代碼質量,之所以這麼說,那是因為,應用這條原則,可以::

  • 將介面和實現相分離
  • 封裝不穩定的實現
  • 暴露穩定的介面

上游系統面向介面而非實現編程,不依賴不穩定的實現細節,這樣當實現發生變化的時候,上游系統的代碼基本上不需要做改動,以此來降低耦合性,提高擴展性

實際上,“基於介面而非實現編程”這條原則的另一個表述方式,是“基於抽象而非實現編程”。後者的表述方式其實更能體現這條原則的設計初衷。在軟體開發中,最大的挑戰之一就是需求的不斷變化,這也是考驗代碼設計好壞的一個標準。越抽象、越頂層、越脫離具體某一實現的設計,越能提高代碼的靈活性,越能應對未來的需求變化。好的代碼設計,不僅能應對當下的需求,而且在將來需求發生變化的時候,仍然能夠在不破壞原有代碼設計的情況下靈活應對。而抽象就是提高代碼擴展性、靈活性、可維護性最有效的手段之一

面向介面編程實踐

假設我們的系統中有很多涉及圖片處理和存儲的業務邏輯。圖片經過處理之後被上傳到阿裡雲上。為了代碼復用,我們封裝了圖片存儲相關的代碼邏輯,提供了一個統一的 AliyunImageStore 類,供整個系統來使用。具體的代碼實現如下所示:

public class AliyunImageStore {
  //...省略屬性、構造函數等...
  
  public void createBucketIfNotExisting(String bucketName) {
    // ...創建bucket代碼邏輯...
    // ...失敗會拋出異常..
  }
  
  public String generateAccessToken() {
    // ...根據accesskey/secrectkey等生成access token
  }
  
  public String uploadToAliyun(Image image, String bucketName, String accessToken) {
    //...上傳圖片到阿裡雲...
    //...返回圖片存儲在阿裡雲上的地址(url)...
  }
  
  public Image downloadFromAliyun(String url, String accessToken) {
    //...從阿裡雲下載圖片...
  }
}

// AliyunImageStore類的使用舉例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //...省略其他無關代碼...
  
  public void process() {
    Image image = ...; //處理圖片,並封裝為Image對象
    AliyunImageStore imageStore = new AliyunImageStore(/*省略參數*/);
    imageStore.createBucketIfNotExisting(BUCKET_NAME);
    String accessToken = imageStore.generateAccessToken();
    imagestore.uploadToAliyun(image, BUCKET_NAME, accessToken);
  }
  
}

整個上傳流程包含三個步驟:創建 bucket(你可以簡單理解為存儲目錄)、生成 access token 訪問憑證、攜帶 access token 上傳圖片到指定的 bucket 中。代碼實現非常簡單,類中的幾個方法定義得都很乾凈,用起來也很清晰,乍看起來沒有太大問題,完全能滿足我們將圖片存儲在阿裡雲的業務需求。

不過,軟體開發中唯一不變的就是變化。過了一段時間後,我們自建了私有雲,不再將圖片存儲到阿裡雲了,而是將圖片存儲到自建私有雲上。為了滿足這樣一個需求的變化,我們該如何修改代碼呢?

我們需要重新設計實現一個存儲圖片到私有雲的 PrivateImageStore 類,並用它替換掉項目中所有的 AliyunImageStore 類對象。這樣的修改聽起來並不複雜,只是簡單替換而已,對整個代碼的改動並不大。不過,我們經常說,“細節是魔鬼”。這句話在軟體開發中特別適用。實際上,剛剛的設計實現方式,就隱藏了很多容易出問題的“魔鬼細節”,我們一塊來看看都有哪些。

新的 PrivateImageStore 類需要設計實現哪些方法,才能在儘量最小化代碼修改的情況下,替換掉 AliyunImageStore 類呢?這就要求我們必須將 AliyunImageStore 類中所定義的所有 public 方法,在 PrivateImageStore 類中都逐一定義並重新實現一遍。而這樣做就會存在一些問題,我總結了下麵兩點。

首先,AliyunImageStore 類中有些函數命名暴露了實現細節,比如,uploadToAliyun() 和 downloadFromAliyun()。如果開發這個功能的同事沒有介面意識、抽象思維,那這種暴露實現細節的命名方式就不足為奇了,畢竟最初我們只考慮將圖片存儲在阿裡雲上。而我們把這種包含“aliyun”字眼的方法,照抄到 PrivateImageStore 類中,顯然是不合適的。如果我們在新類中重新命名 uploadToAliyun()、downloadFromAliyun() 這些方法,那就意味著,我們要修改項目中所有使用到這兩個方法的代碼,代碼修改量可能就會很大。

其次,將圖片存儲到阿裡雲的流程,跟存儲到私有雲的流程,可能並不是完全一致的。比如,阿裡雲的圖片上傳和下載的過程中,需要生產 access token,而私有雲不需要 access token。一方面,AliyunImageStore 中定義的 generateAccessToken() 方法不能照抄到 PrivateImageStore 中;另一方面,我們在使用 AliyunImageStore 上傳、下載圖片的時候,代碼中用到了 generateAccessToken() 方法,如果要改為私有雲的上傳下載流程,這些代碼都需要做調整。

那這兩個問題該如何解決呢?解決這個問題的根本方法就是,在編寫代碼的時候,要遵從“基於介面而非實現編程”的原則,具體來講,我們需要做到下麵這 3 點。

  1. 函數的命名不能暴露任何實現細節。比如,前面提到的 uploadToAliyun() 就不符合要求,應該改為去掉 aliyun 這樣的字眼,改為更加抽象的命名方式,比如:upload()。
  2. 封裝具體的實現細節。比如,跟阿裡雲相關的特殊上傳(或下載)流程不應該暴露給調用者。我們對上傳(或下載)流程進行封裝,對外提供一個包裹所有上傳(或下載)細節的方法,給調用者使用。
  3. 為實現類定義抽象的介面。具體的實現類都依賴統一的介面定義,遵從一致的上傳功能協議。使用者依賴介面,而不是具體的實現類來編程。

我們按照這個思路,把代碼重構一下。重構後的代碼如下所示:

public interface ImageStore {
  String upload(Image image, String bucketName);
  Image download(String url);
}

public class AliyunImageStore implements ImageStore {
  //...省略屬性、構造函數等...

  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    String accessToken = generateAccessToken();
    //...上傳圖片到阿裡雲...
    //...返回圖片在阿裡雲上的地址(url)...
  }

  public Image download(String url) {
    String accessToken = generateAccessToken();
    //...從阿裡雲下載圖片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ...創建bucket...
    // ...失敗會拋出異常..
  }

  private String generateAccessToken() {
    // ...根據accesskey/secrectkey等生成access token
  }
}

// 上傳下載流程改變:私有雲不需要支持access token
public class PrivateImageStore implements ImageStore  {
  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    //...上傳圖片到私有雲...
    //...返回圖片的url...
  }

  public Image download(String url) {
    //...從私有雲下載圖片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ...創建bucket...
    // ...失敗會拋出異常..
  }
}

// ImageStore的使用舉例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //...省略其他無關代碼...
  
  public void process() {
    Image image = ...;//處理圖片,並封裝為Image對象
    ImageStore imageStore = new PrivateImageStore(...);
    imagestore.upload(image, BUCKET_NAME);
  }
}

除此之外,很多人在定義介面的時候,希望通過實現類來反推介面的定義。先把實現類寫好,然後看實現類中有哪些方法,照抄到介面定義中。如果按照這種思考方式,就有可能導致介面定義不夠抽象,依賴具體的實現。這樣的介面設計就沒有意義了。不過,如果你覺得這種思考方式更加順暢,那也沒問題,只是將實現類的方法搬移到介面定義中的時候,要有選擇性的搬移,不要將跟具體實現相關的方法搬移到介面中,比如 AliyunImageStore 中的 generateAccessToken() 方法。

總結一下,我們在做軟體開發的時候,一定要有抽象意識、封裝意識、介面意識。在定義介面的時候,不要暴露任何實現細節。介面的定義只表明做什麼,而不是怎麼做。而且,在設計介面的時候,我們要多思考一下,這樣的介面設計是否足夠通用,是否能夠做到在替換具體的介面實現的時候,不需要任何介面定義的改動。

面向介面編程總結

  1. “基於介面而非實現編程”,這條原則的另一個表述方式,是“基於抽象而非實現編程”。後者的表述方式其實更能體現這條原則的設計初衷。我們在做軟體開發的時候,一定要有抽象意識、封裝意識、介面意識。越抽象、越頂層、越脫離具體某一實現的設計,越能提高代碼的靈活性、擴展性、可維護性。

  2. 我們在定義介面的時候,一方面,命名要足夠通用,不能包含跟具體實現相關的字眼;另一方面,與特定實現有關的方法不要定義在介面中。

  3. “基於介面而非實現編程”這條原則,不僅僅可以指導非常細節的編程開發,還能指導更加上層的架構設計、系統設計等。比如,服務端與客戶端之間的“介面”設計、類庫的“介面”設計。


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

-Advertisement-
Play Games
更多相關文章
  • 1. <form>標簽 表單容器,指定method屬性和action屬性是個良好的習慣。 <form methor="POST" action="http://www.juetan.cn/user/1"> //其他屬性: name:表單名稱,便於在Javascript中引用該表單。 target:表 ...
  • 在我們開發BS頁面的時候,往往需要瞭解常規界面組件的使用,小到最普通的單文本輸入框、多文本框、下拉列表,以及按鈕、圖片展示、彈出對話框、表單處理等等,本篇隨筆基於普通表格業務的展示錄入的場景介紹這些常規Element組件的使用,使得我們對如何利用Element組件有一個大概的認識。 ...
  • Checkbox a-checkbox 沒有value屬性,綁定用checked Collapse 使用摺疊面板的時候,antd 的層級關係是 .ant-collapse >.ant-collapse-item > .ant-collapse-content > .ant-collapse-cont ...
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document< ...
  • 內置對象:對象是由屬性和方法組成的,使用點語法訪問 一,array數組 1. 特點: 數組用於存儲若幹數據,自動為每位數據分配下標,從0開始 數組中的元素不限數據類型,長度可以動態調整 動態操作數組元素 :根據元素下標讀取或修改數組元素,arr[index] 2. 屬性和方法: 屬性 : lengt ...
  • 深克隆和淺克隆 淺克隆 arr.slice(0) arr.concat() let obj2 = {... obj} 深克隆 function deepClone(obj){ //判斷參數是不是一個對象 let objClone = new obj.constructor(); if(obj && ...
  • 前端的前景怎麼樣,以下是我的分享: 隨著互聯網的高速發展,不知不覺中我們的生活已被互聯網從四面八方給包圍,不管是網上點餐,網上購物、網上購票,還是網上學習,這都表明瞭現在就是互聯網的天下。因此,也就越來越多的選擇互聯網行業,選擇學習前端開發,但是大家也有擔心的問題,最近幾年web前端發展趨勢良好,但 ...
  • HTTP 請求中,空格應該被編碼為什麼?今天我們走進 RFC 文檔和 W3C 文檔,瞭解一下這個「史詩級」大坑。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...