成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

依賴注入與控制反轉:優化Go語言REST API客戶端

開發 前端
在這篇文章中,我展示了如何以及為什么在Go中使用DI和IoC。正確使用DI/IoC可以導致更易于測試和維護的代碼,特別是在代碼庫不斷增長時。雖然代碼示例是用Go編寫的,但這里描述的原則同樣適用于其他編程語言。

在這篇文章中,我將探討依賴注入(DI)和控制反轉(IoC)是什么,以及它們的重要性。作為示例,我將使用Monibot的REST API客戶端。讓我們開始吧:

一個簡單的客戶端實現

我們從一個簡單的客戶端實現開始,允許調用者訪問Monibot的REST API,具體來說,是為了發送指標值。客戶端的實現可能如下所示:

package monibot

type Client struct {
}

func NewClient() *Client {
    return &Client{}
}

func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    http.Post("https://monibot.io/api/metric", []byte(body))
}

這里有一個客戶端,提供了PostMetricValue方法,該方法用于將指標值上傳到Monibot。我們的庫的用戶可能像這樣使用它:

import "monibot"

func main() {
    // 初始化API客戶端
    client := monibot.NewClient()
    // 發送指標值
    client.PostMetricValue(42)
}

依賴注入

現在假設我們想對客戶端進行單元測試。當所有HTTP發送代碼都是硬編碼的時候,我們如何測試客戶端呢?對于每次測試運行,我們都需要一個“真實”的HTTP服務器來回答我們發送給它的所有請求。不可取!我們可以做得更好:讓我們將HTTP處理作為“依賴”;讓我們發明一個 Transport 接口:

package monibot

// Transport傳輸請求。
type Transport interface {
    Post(url string, body []byte)
}

讓我們再發明一個具體的使用HTTP作為通信協議的Transport:

package monibot

// HTTPTransport是一個使用HTTP協議傳輸請求的Transport。
type HTTPTransport struct {
}

func (t HTTPTransport) Post(url string, data []byte) {
    http.Post(url, data)
}

然后讓我們重寫客戶端,使其“依賴”于一個Transport 接口:

package monibot

type Client struct {
    transport Transport
}

func NewClient(transport Transport) *Client {
    return &Client{transport}
}

func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    c.transport.Post("https://monibot.io/api/metric", []byte(body))
}

現在,客戶端將請求轉發到它的Transport依賴。當創建客戶端時,transport(客戶端的依賴項)被“注入”到客戶端中。調用者可以這樣初始化一個客戶端:

import "monibot"

func main() {
    // 初始化API客戶端
    var transport monibot.HTTPTransport
    client := monibot.NewClient(transport)
    // 發送指標值
    client.PostMetricValue(42)
}

單元測試

現在我們可以編寫一個使用“偽造”Transport的單元測試:

// TestPostMetricValue確保客戶端向REST API發送正確的POST請求。
func TestPostMetricValue(t *testing.T) {
    transport := &fakeTransport{}
    client := NewClient(transport)
    client.PostMetricValue(42)
    if len(transport.calls) != 1 {
        t.Fatal("期望1次傳輸調用,但是是%d次", len(transport.calls))
    }
    if transport.calls[0] != "POST https://monibot.io/api/metric, body=\\"value=42\\"" {
        t.Fatal("錯誤的傳輸調用 %q", transport.calls[0])
    }
}

// 偽造的Transport是單元測試中使用的Transport。
type fakeTransport struct {
    calls []string
}

func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}

添加更多的Transport函數

現在假設我們庫的其他部分,也使用了Transport功能,需要比POST更多的HTTP方法。對于它們,我們必須擴展我們的Transport接口:

package monibot

// Transport傳輸請求。
type Transport interface {
    Get(url string) []byte     // 添加,因為health-monitor需要
    Post(url string, body []byte)
    Delete(url string)         // 添加,因為resource-monitor需要
}

現在我們有一個問題。編譯器抱怨我們的fakeTransport不再滿足Transport接口。所以讓我們通過添加缺失的函數來解決它:

// 偽造的Transport是單元測試中使用的Transport。
type fakeTransport struct {
    calls []string
}

func (f *fakeTransport) Get(url string) []byte {
    panic("不使用")
}

func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}

func (f *fakeTransport) Delete(url string) {
    panic("不使用")
}

我們做了什么?由于在單元測試中我們不需要新的Get()和Delete()函數,如果它們被調用,我們就拋出異常。這里有一個問題:每次在Transport中添加新函數時,我們都會破壞現有的fakeTransport實現。對于大型代碼庫來說,這將導致維護噩夢。我們能做得更好嗎?

控制反轉

問題在于我們的客戶端(和相應的單元測試)依賴于一個它們不能控制的類型。在這種情況下,它是Transport接口。為了解決這個問題,讓我們通過引入一個未導出的接口,該接口僅聲明了我們的客戶端所需的內容,來反轉控制:

package monibot

// clientTransport傳輸Client的請求。
type clientTransport interface {
    Post(url string, body []byte)
}

type Client struct {
    transport clientTransport
}

func NewClient(transport clientTransport) *Client {
    return &Client{transport}
}

func (c *Client) PostMetricValue(value int) {
    body := fmt.Sprintf("value=%d", value)
    c.transport.Post("https://monibot.io/api/metric", []byte(body))
}

現在讓我們將我們的單元測試更改為使用假的clientTransport:

// TestPostMetricValue確保客戶端向REST API發送正確的POST請求。
func TestPostMetricValue(t *testing.T) {
    transport := &fakeTransport{}
    client := NewClient(transport)
    client.PostMetricValue(42)
    if len(f.calls) != 1 {
        t.Fatal("期望1次傳輸調用,但是是%d次", len(f.calls))
    }
    if f.calls[0] != "POST https://monibot.io/api/metric, body=\\"value=42\\"" {
        t.Fatal("錯誤的傳輸調用 %q", f.calls[0])
    }
}

// 偽造的Transport是在單元測試中使用的clientTransport。
type fakeTransport struct {
    calls []string
}

func (f *fakeTransport) Post(url string, body []byte) {
    f.calls = append(f.calls, fmt.Sprintf("POST %v, body=%q", url, string(body)))
}

由于Go的隱式接口實現(如果愿意,可以稱之為'鴨子類型'),我們庫的用戶什么也不需要改變:

import "monibot"

func main() {
    // 初始化API客戶端
    var transport monibot.HTTPTransport
    client := monibot.NewClient(transport)
    // 發送指標值
    client.PostMetricValue(42)
}

重新審視Transport

如果我們使IoC成為規范(正如我們應該做的那樣),就不再需要導出Transport接口了。為什么呢?因為如果消費者需要一個接口,讓他們在自己的作用域中定義它,就像我們對'clientTransport'做的那樣。

不要導出接口。導出具體實現。如果消費者需要接口,讓他們在自己的作用域中定義。

總結

在這篇文章中,我展示了如何以及為什么在Go中使用DI和IoC。正確使用DI/IoC可以導致更易于測試和維護的代碼,特別是在代碼庫不斷增長時。雖然代碼示例是用Go編寫的,但這里描述的原則同樣適用于其他編程語言。

責任編輯:武曉燕 來源: 愛發白日夢的后端
相關推薦

2022-04-30 08:50:11

控制反轉Spring依賴注入

2019-09-18 18:12:57

前端javascriptvue.js

2014-01-07 14:39:26

Android開發RxJavaREST

2009-06-12 19:18:08

REST客戶端框架JavaScript

2024-04-01 00:02:56

Go語言代碼

2024-05-27 00:13:27

Go語言框架

2024-07-30 08:12:04

Java消息go

2010-05-31 10:11:32

瘦客戶端

2020-07-14 14:59:00

控制反轉依賴注入容器

2020-11-16 08:05:26

API調用VS Code

2018-12-27 13:11:04

愛奇藝APP優化

2024-04-18 08:39:57

依賴注入控制反轉WPF

2022-09-30 15:31:21

Golang開發工具

2012-12-07 10:15:53

IBMdW

2021-10-18 05:00:38

語言GoRequestHTTP

2021-05-07 15:28:03

Kafka客戶端Sarama

2010-08-31 16:29:40

DHCP客戶端

2010-12-17 10:16:33

OpenVAS

2011-08-17 10:10:59

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧洲一区在线观看 | 91国自产 | 成人一区二 | 亚洲图片一区二区三区 | 在线三级网址 | 日本二区| 伊人看片 | 99成人免费视频 | 欧美一级二级三级 | 欧美13videosex性极品 | 一区二区三区小视频 | 一a一片一级一片啪啪 | 超碰免费在线观看 | 国产第一区二区 | 国产综合精品 | 久国久产久精永久网页 | 成人黄色在线 | 日韩av在线一区二区三区 | 99久久亚洲| 天天躁日日躁狠狠的躁天龙影院 | 中文字幕在线播放第一页 | 中文字幕在线播放第一页 | 黄色免费三级 | 国产精品久久福利 | 亚洲一区二区三区四区视频 | 天堂一区二区三区 | 精品国产乱码久久久久久88av | 亚洲国产精品视频 | 日韩在线视频一区 | 久久久久久网 | 欧美日韩视频在线第一区 | 欧美精品v国产精品v日韩精品 | 亚洲日本乱码在线观看 | 欧美黄视频 | 日韩精品免费一区二区在线观看 | 在线观看亚洲欧美 | 天天躁日日躁xxxxaaaa | 综合五月婷 | 一区网站 | 国产精品中文字幕在线 | 91免费在线视频 |