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

依賴(lài)注入與控制反轉(zhuǎn):優(yōu)化Go語(yǔ)言REST API客戶(hù)端

開(kāi)發(fā) 前端
在這篇文章中,我展示了如何以及為什么在Go中使用DI和IoC。正確使用DI/IoC可以導(dǎo)致更易于測(cè)試和維護(hù)的代碼,特別是在代碼庫(kù)不斷增長(zhǎng)時(shí)。雖然代碼示例是用Go編寫(xiě)的,但這里描述的原則同樣適用于其他編程語(yǔ)言。

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

一個(gè)簡(jiǎn)單的客戶(hù)端實(shí)現(xiàn)

我們從一個(gè)簡(jiǎn)單的客戶(hù)端實(shí)現(xiàn)開(kāi)始,允許調(diào)用者訪問(wèn)Monibot的REST API,具體來(lái)說(shuō),是為了發(fā)送指標(biāo)值。客戶(hù)端的實(shí)現(xiàn)可能如下所示:

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))
}

這里有一個(gè)客戶(hù)端,提供了PostMetricValue方法,該方法用于將指標(biāo)值上傳到Monibot。我們的庫(kù)的用戶(hù)可能像這樣使用它:

import "monibot"

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

依賴(lài)注入

現(xiàn)在假設(shè)我們想對(duì)客戶(hù)端進(jìn)行單元測(cè)試。當(dāng)所有HTTP發(fā)送代碼都是硬編碼的時(shí)候,我們?nèi)绾螠y(cè)試客戶(hù)端呢?對(duì)于每次測(cè)試運(yùn)行,我們都需要一個(gè)“真實(shí)”的HTTP服務(wù)器來(lái)回答我們發(fā)送給它的所有請(qǐng)求。不可取!我們可以做得更好:讓我們將HTTP處理作為“依賴(lài)”;讓我們發(fā)明一個(gè) Transport 接口:

package monibot

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

讓我們?cè)侔l(fā)明一個(gè)具體的使用HTTP作為通信協(xié)議的Transport:

package monibot

// HTTPTransport是一個(gè)使用HTTP協(xié)議傳輸請(qǐng)求的Transport。
type HTTPTransport struct {
}

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

然后讓我們重寫(xiě)客戶(hù)端,使其“依賴(lài)”于一個(gè)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))
}

現(xiàn)在,客戶(hù)端將請(qǐng)求轉(zhuǎn)發(fā)到它的Transport依賴(lài)。當(dāng)創(chuàng)建客戶(hù)端時(shí),transport(客戶(hù)端的依賴(lài)項(xiàng))被“注入”到客戶(hù)端中。調(diào)用者可以這樣初始化一個(gè)客戶(hù)端:

import "monibot"

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

單元測(cè)試

現(xiàn)在我們可以編寫(xiě)一個(gè)使用“偽造”Transport的單元測(cè)試:

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

// 偽造的Transport是單元測(cè)試中使用的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函數(shù)

現(xiàn)在假設(shè)我們庫(kù)的其他部分,也使用了Transport功能,需要比POST更多的HTTP方法。對(duì)于它們,我們必須擴(kuò)展我們的Transport接口:

package monibot

// Transport傳輸請(qǐng)求。
type Transport interface {
    Get(url string) []byte     // 添加,因?yàn)閔ealth-monitor需要
    Post(url string, body []byte)
    Delete(url string)         // 添加,因?yàn)閞esource-monitor需要
}

現(xiàn)在我們有一個(gè)問(wèn)題。編譯器抱怨我們的fakeTransport不再滿足Transport接口。所以讓我們通過(guò)添加缺失的函數(shù)來(lái)解決它:

// 偽造的Transport是單元測(cè)試中使用的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("不使用")
}

我們做了什么?由于在單元測(cè)試中我們不需要新的Get()和Delete()函數(shù),如果它們被調(diào)用,我們就拋出異常。這里有一個(gè)問(wèn)題:每次在Transport中添加新函數(shù)時(shí),我們都會(huì)破壞現(xiàn)有的fakeTransport實(shí)現(xiàn)。對(duì)于大型代碼庫(kù)來(lái)說(shuō),這將導(dǎo)致維護(hù)噩夢(mèng)。我們能做得更好嗎?

控制反轉(zhuǎn)

問(wèn)題在于我們的客戶(hù)端(和相應(yīng)的單元測(cè)試)依賴(lài)于一個(gè)它們不能控制的類(lèi)型。在這種情況下,它是Transport接口。為了解決這個(gè)問(wèn)題,讓我們通過(guò)引入一個(gè)未導(dǎo)出的接口,該接口僅聲明了我們的客戶(hù)端所需的內(nèi)容,來(lái)反轉(zhuǎn)控制:

package monibot

// clientTransport傳輸Client的請(qǐng)求。
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))
}

現(xiàn)在讓我們將我們的單元測(cè)試更改為使用假的clientTransport:

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

// 偽造的Transport是在單元測(cè)試中使用的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的隱式接口實(shí)現(xiàn)(如果愿意,可以稱(chēng)之為'鴨子類(lèi)型'),我們庫(kù)的用戶(hù)什么也不需要改變:

import "monibot"

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

重新審視Transport

如果我們使IoC成為規(guī)范(正如我們應(yīng)該做的那樣),就不再需要導(dǎo)出Transport接口了。為什么呢?因?yàn)槿绻M(fèi)者需要一個(gè)接口,讓他們?cè)谧约旱淖饔糜蛑卸x它,就像我們對(duì)'clientTransport'做的那樣。

不要導(dǎo)出接口。導(dǎo)出具體實(shí)現(xiàn)。如果消費(fèi)者需要接口,讓他們?cè)谧约旱淖饔糜蛑卸x。

總結(jié)

在這篇文章中,我展示了如何以及為什么在Go中使用DI和IoC。正確使用DI/IoC可以導(dǎo)致更易于測(cè)試和維護(hù)的代碼,特別是在代碼庫(kù)不斷增長(zhǎng)時(shí)。雖然代碼示例是用Go編寫(xiě)的,但這里描述的原則同樣適用于其他編程語(yǔ)言。

責(zé)任編輯:武曉燕 來(lái)源: 愛(ài)發(fā)白日夢(mèng)的后端
相關(guān)推薦

2019-09-18 18:12:57

前端javascriptvue.js

2022-04-30 08:50:11

控制反轉(zhuǎn)Spring依賴(lài)注入

2014-01-07 14:39:26

Android開(kāi)發(fā)RxJavaREST

2009-06-12 19:18:08

REST客戶(hù)端框架JavaScript

2024-04-01 00:02:56

Go語(yǔ)言代碼

2024-05-27 00:13:27

Go語(yǔ)言框架

2020-07-14 14:59:00

控制反轉(zhuǎn)依賴(lài)注入容器

2020-11-16 08:05:26

API調(diào)用VS Code

2024-07-30 08:12:04

Java消息go

2010-05-31 10:11:32

瘦客戶(hù)端

2018-12-27 13:11:04

愛(ài)奇藝APP優(yōu)化

2024-04-18 08:39:57

依賴(lài)注入控制反轉(zhuǎn)WPF

2022-09-30 15:31:21

Golang開(kāi)發(fā)工具

2010-12-17 10:16:33

OpenVAS

2012-12-07 10:15:53

IBMdW

2021-09-22 15:46:29

虛擬桌面瘦客戶(hù)端胖客戶(hù)端

2010-08-31 16:29:40

DHCP客戶(hù)端

2021-10-18 05:00:38

語(yǔ)言GoRequestHTTP

2021-05-07 15:28:03

Kafka客戶(hù)端Sarama

2016-10-09 08:35:09

Linux桌面REST
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品精品| 天堂中文在线播放 | 色婷婷亚洲一区二区三区 | 欧美日韩精品一区二区三区视频 | 亚洲精品亚洲人成人网 | 亚洲区一区二 | 亚洲成人免费视频在线 | 久久久精品一区二区三区四季av | av免费观看在线 | 美国一级黄色片 | 亚洲欧美成人影院 | 国产欧美精品一区二区三区 | 欧美福利三区 | 亚洲成a | 一道本视频| 亚洲精品18 | 欧美11一13sex性hd | 黄色一级大片在线免费看产 | 国产成人免费在线观看 | 黄a在线观看 | 天堂影院av | 亚洲精品乱码久久久久久蜜桃 | 精品视频久久久久久 | 国产一区二区在线播放 | 中文字幕一区二区三区精彩视频 | 亚洲精久| 最新中文字幕在线 | 99精品视频在线观看免费播放 | 亚洲精品免费视频 | 亚洲国产精品一区二区三区 | 91极品尤物在线播放国产 | 久久综合亚洲 | 久久er精品 | 国产亚洲精品91 | 日韩欧美视频 | 日本aa毛片a级毛片免费观看 | 精品久久久一区 | 免费观看色 | 国产精品jizz在线观看老狼 | 天天拍夜夜爽 | 成人一区二区视频 |