<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Facebook 對(duì) Golang 依賴注入的實(shí)現(xiàn)

          共 5918字,需瀏覽 12分鐘

           ·

          2020-08-18 00:16

          依賴注入是一個(gè)經(jīng)典的設(shè)計(jì)模式,在解決復(fù)雜的對(duì)象依賴關(guān)系方面是一個(gè)非常行之有效的手段。

          對(duì)于有反射功能的語言來說,實(shí)現(xiàn)依賴注入都比較方便一些。在 Golang 中有幾個(gè)比較知名的依賴注入開源庫,例如 google/wire、uber-go/dig 以及 facebookgo/inject 等。

          本文將基于 facebookgo/inject 介紹依賴注入, 接下來將會(huì)著重討論以下幾點(diǎn)內(nèi)容:

          1. 依賴注入的背景以及解決的問題
          2. facebookgo/inject 的使用方法
          3. facebookgo/inject 的缺陷

          依賴注入的背景

          對(duì)于稍微復(fù)雜些的項(xiàng)目,我們往往就會(huì)遇到對(duì)象之間復(fù)雜的依賴關(guān)系。手動(dòng)管理和初始化這些管理關(guān)系將會(huì)極其繁瑣,依賴注入可以幫我們自動(dòng)實(shí)現(xiàn)依賴的管理和對(duì)象屬性的賦值,將我們從這些繁瑣的依賴管理中解放出來。

          以一個(gè)常見的 HTTP 服務(wù)為例,我們?cè)陂_發(fā)后臺(tái)時(shí)往往會(huì)把代碼分為 Controller、Service 等層次。如下:

          type?UserController?struct?{
          ?UserService?*UserService
          ?Conf????????*Conf
          }

          type?PostController?struct?{
          ?UserService?*UserService
          ?PostService?*PostService
          ?Conf????????*Conf
          }

          type?UserService?struct?{
          ?Db???*DB
          ?Conf?*Conf
          }

          type?PostService?struct?{
          ?Db?*DB
          }

          type?Server?struct?{
          ????UserApi?*UserController
          ????PostApi?*PostController
          }

          上述的代碼例子中,有兩個(gè) Controller:UserController 和 PostController,分別用來接收用戶和文章的相關(guān)請(qǐng)求邏輯。除此之外還會(huì)有 Service 相關(guān)類、Conf 配置文件、DB 連接等。

          這些對(duì)象之間存在比較復(fù)雜的依賴關(guān)系,這就給項(xiàng)目的初始化帶來了一些困擾。對(duì)于以上代碼,對(duì)應(yīng)初始化邏輯大概就會(huì)是這樣:

          func?main()?{
          ?conf?:=?loadConf()
          ?db?:=?connectDB()

          ?userService?:=?&UserService{
          ??Db:???db,
          ??Conf:?conf,
          ?}

          ?postService?:=?&PostService{
          ??Db:?db,
          ?}

          ?userHandler?:=?&UserController{
          ??UserService:?userService,
          ??Conf:????????conf,
          ?}

          ?postHandler?:=?&PostController{
          ??UserService:?userService,
          ??PostService:?postService,
          ??Conf:????????conf,
          ?}

          ?server?:=?&Server{
          ??UserApi:?userHandler,
          ??PostApi:?postHandler,
          ?}

          ?server.Run()
          }

          我們會(huì)有一大段的邏輯都是用來做對(duì)象初始化,而當(dāng)接口越來越多的時(shí)候,整個(gè)初始化過程就會(huì)異常的冗長(zhǎng)和復(fù)雜。

          針對(duì)以上問題,依賴注入可以完美的解決。

          facebookgo/inject 的使用

          接下來,我們?cè)囍褂?facebookgo/inject 的方式,對(duì)這段代碼進(jìn)行依賴注入的改造。如下:

          type?UserController?struct?{
          ?UserService?*UserService?`inject:""`
          ?Conf????????*Conf????????`inject:""`
          }

          type?PostController?struct?{
          ?UserService?*UserService?`inject:""`
          ?PostService?*PostService?`inject:""`
          ?Conf????????*Conf????????`inject:""`
          }

          type?UserService?struct?{
          ?Db???*DB???`inject:""`
          ?Conf?*Conf?`inject:""`
          }

          type?PostService?struct?{
          ?Db?*DB?`inject:""`
          }

          type?Server?struct?{
          ?UserApi?*UserController?`inject:""`
          ?PostApi?*PostController?`inject:""`
          }

          func?main()?{
          ?conf?:=?loadConf()?//?*Conf
          ?db?:=?connectDB()?//?*DB

          ?server?:=?Server{}

          ?graph?:=?inject.Graph{}

          ?if?err?:=?graph.Provide(
          ??&inject.Object{
          ???Value:?&server,
          ??},
          ??&inject.Object{
          ???Value:?conf,
          ??},
          ??&inject.Object{
          ???Value:?db,
          ??},
          ?);?err?!=?nil?{
          ??panic(err)
          ?}

          ?if?err?:=?graph.Populate();?err?!=?nil?{
          ??panic(err)
          ?}

          ?server.Run()
          }
          1. 首先每一個(gè)需要注入的字段都需要打上 inject:"" 這樣的 tag。所謂依賴注入,這里的依賴指的就是對(duì)象中包含的字段,而注入則是指有其它程序會(huì)幫你對(duì)這些字段進(jìn)行賦值。

          2. 其次,我們使用 inject.Graph{} 創(chuàng)建一個(gè) graph 對(duì)象。這個(gè) graph 對(duì)象將負(fù)責(zé)管理和注入所有的對(duì)象。至于為什么叫 Graph,其實(shí)這個(gè)名詞起的非常形象,因?yàn)楦鱾€(gè)對(duì)象之間的依賴關(guān)系,也確實(shí)像是一張圖一樣。

          3. 接下來,我們使用 graph.Provide() 將需要注入的對(duì)象提供給 graph

          graph.Provide(
          ?&inject.Object{
          ??Value:?&server,
          ?},
          ?&inject.Object{
          ??Value:?&conf,
          ?},
          ?&inject.Object{
          ??Value:?&db,
          ?},
          );
          1. 最后調(diào)用 Populate 函數(shù),開始進(jìn)行注入。

          從代碼中可以看到,我們一共就向 Graph 中 Provide 了三個(gè)對(duì)象。我們提供了 server 對(duì)象,是因?yàn)樗且粋€(gè)頂層對(duì)象。提供了 confdb對(duì)象,是因?yàn)樗械膶?duì)象都依賴于它們,可以說它們是基礎(chǔ)對(duì)象了。

          但是其他的對(duì)象呢?例如 UserApiUserService 呢?我們并沒有向 graph 調(diào)用 Provide 過。那么它們是怎么完成賦值和注入的呢?

          其實(shí)從下面這張對(duì)象依賴圖能夠很簡(jiǎn)單的看清楚。

          對(duì)象依賴圖

          從這個(gè)依賴圖中可以看出,confdb 對(duì)象是屬于根節(jié)點(diǎn),所有的對(duì)象都依賴和包含著它們。而 server 屬于葉子節(jié)點(diǎn),不會(huì)有其他對(duì)象依賴它了。

          我們需要提供給 Graph 的就是根節(jié)點(diǎn)和葉子節(jié)點(diǎn),對(duì)于中間節(jié)點(diǎn)來說,Graph 會(huì)通過 inject:"" 標(biāo)簽,自動(dòng)將其 Provide 到 Graph 中,并進(jìn)行注入。

          對(duì)以上例子,我們深入剖析下 Graph 內(nèi)部進(jìn)行 Populate 時(shí)都發(fā)生了哪些動(dòng)作:

          1. Graph 首先解析 server 對(duì)象,發(fā)現(xiàn)其有兩個(gè)標(biāo)記為 inject 的字段:UserApiPostApi。其類型 UserControllerPostController, Graph 中從未出現(xiàn)過這兩個(gè)類型。因此,Graph 會(huì)自動(dòng)對(duì)該字段調(diào)用 Provide,提供給 Graph。
          2. 解析 UserApi 時(shí),發(fā)現(xiàn)其依然有也有兩個(gè)標(biāo)記為 inject 的字段:UserServiceConf。對(duì)于 UserService 這種 Graph 中未登記過的類型,會(huì)自動(dòng) Provide。而對(duì) Conf, Graph 中之前已經(jīng)注冊(cè)過了,因此直接將注冊(cè)的對(duì)象賦值給該字段即可。
          3. 接下來就是繼續(xù)逐步解析,直至沒有tag為 inject 的字段。

          以上就是整個(gè)依賴注入的流程了。

          這里需要注意的是,在我們上面的示例中,以這種方式注入,其中所有的對(duì)象都相當(dāng)于單例對(duì)象。即一個(gè)類型,只會(huì)在 Graph 中存在一個(gè)實(shí)例對(duì)象。比如 UserControllerPosterController 中的 UserService 實(shí)際上是同一個(gè)對(duì)象。

          我們的 main 函數(shù)使用 inject 進(jìn)行改造后,將會(huì)變得非常簡(jiǎn)潔。而且即使隨著業(yè)務(wù)越來越復(fù)雜,Handler 和 Service 越來越多,這個(gè) main 函數(shù)中的注入邏輯也不會(huì)任何改變,除非有新的根節(jié)點(diǎn)對(duì)象出現(xiàn)。

          當(dāng)然,對(duì)于 Graph 來說,也不是只能 Provide 根節(jié)點(diǎn)和葉子節(jié)點(diǎn),我們也可以自行 Provide 一個(gè) UserService 的實(shí)例進(jìn)去,對(duì)于 Graph 的運(yùn)作是沒有任何影響的。只不過只 Provide 根節(jié)點(diǎn)和葉子節(jié)點(diǎn),代碼會(huì)更簡(jiǎn)潔一些。

          inject 的高級(jí)用法

          我們?cè)诼暶?tag 時(shí),除了聲明為 inject:"" 這種默認(rèn)用法外,還可以有其他三種高級(jí)的用法:

          1. inject:"private"。私有注入。
          2. inject:"inline"。內(nèi)聯(lián)注入。
          3. inject:"object_name"。命名注入,這里的 object_name 可以取成任意的名字。

          private (私有注入)

          我們上文講過,默認(rèn)情況下,所有的對(duì)象都是單例對(duì)象。一個(gè)類型只會(huì)有一個(gè)實(shí)例對(duì)象存在。但也可以不使用單例對(duì)象,private 就是提供了這種可能。

          例如:

          type?UserController?struct?{
          ?UserService?*UserService?`inject:"private"`
          ?Conf????????*Conf????????`inject:""`
          }

          我們將 UserController 中的 UserService 屬性聲明為 private 注入。這樣的話,graph 遇到 private 標(biāo)簽時(shí),會(huì)自動(dòng)的 new 一個(gè)全新的 UserService 對(duì)象,將其賦值給該字段。

          這樣 Graph 中就同時(shí)存在了兩個(gè) UserService 的實(shí)例,一個(gè)是 UserService 的全局實(shí)例,給默認(rèn)的 inject:"" 使用。一個(gè)是專門給 UserController 實(shí)例中的 UserService 使用。

          但在實(shí)際開發(fā)中,這種 private 的場(chǎng)景似乎也比較少,大部分情況下,默認(rèn)的單例對(duì)象就足夠了。

          inline (內(nèi)聯(lián)注入)

          默認(rèn)情況下,需要注入的屬性必須得是 *Struct。但是也是可以聲明為普通對(duì)象的。例如:

          type?UserController?struct?{
          ?UserService?UserService?`inject:"inline"`
          ?Conf????????*Conf???????`inject:""`
          }

          注意,這里的 UserService 的類型,并非是 *UserService 指針類型了,而是普通的 struct 類型。struct 類型在 Go 里面都是值語義,這里當(dāng)然也就不存在單例的問題了。

          命名注入

          如果我們需要對(duì)某些字段注入專有的對(duì)象實(shí)例,那么我們可能會(huì)用到命名注入。使用方法就是在 inject 的 tag 里寫上專有的名字。如下:

          type?UserController?struct?{
          ?UserService?UserService?`inject:"named_service"`
          ?Conf????????*Conf???????`inject:""`
          }

          當(dāng)然,這個(gè)命名肯定不能命名為 privateinline,這兩個(gè)屬于保留詞。

          同時(shí),我們一定要把這個(gè)命名實(shí)例 Provide 到 graph 里面,這樣 graph 才能把兩個(gè)對(duì)象聯(lián)系起來。

          graph.Provide(
          ?&inject.Object{
          ??Value:?&namedService,
          ??Name:?"named_service",
          ?},
          );

          注入 map

          我們除了可以注入對(duì)象外,還可以注入 map。如下:

          type?UserController?struct?{
          ?UserService?UserService???????`inject:"inline"`
          ?Conf????????*Conf?????????????`inject:""`
          ?UserMap?????map[string]string?`inject:"private"`
          }

          需要注意的是,map 的注入 tag 一定要是 inject:"private"

          facebookgo/inject 的缺陷

          facebookgo/inject 固然很好用,只要聲明 inject:"" 的 tag,提供幾個(gè)對(duì)象,就可以完全自動(dòng)的注入所有依賴關(guān)系。

          但是由于Golang本身的語言設(shè)計(jì), facebookgo/inject 也會(huì)有一些缺陷和短板:

          1. 所有需要注入的字段都需要是 public 的。 這也是 Golang 的限制,不能對(duì)私有屬性進(jìn)行賦值。所以只能對(duì)public的字段進(jìn)行注入。但這樣就會(huì)把代碼稍顯的不那么優(yōu)雅,畢竟很多變量我們其實(shí)并不想 public。

          2. 只能進(jìn)行屬性賦值,不能執(zhí)行初始化函數(shù)。 facebookgo/inject只會(huì)幫你注入好對(duì)象,把各個(gè)屬性賦值好。但很多時(shí)候,我們往往需要在對(duì)象賦值完成后,再進(jìn)行其他一些動(dòng)作。但對(duì)于這個(gè)需求場(chǎng)景,facebookgo/inject并不能很好的支持。

          這兩個(gè)問題的原因總結(jié)歸納為:Golang沒有構(gòu)造函數(shù)......





          推薦閱讀



          學(xué)習(xí)交流 Go 語言,掃碼回復(fù)「進(jìn)群」即可


          站長(zhǎng) polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)


          Go語言中文網(wǎng)

          每天為你

          分享 Go 知識(shí)

          Go愛好者值得關(guān)注



          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码精品一区二区三区同学聚会 | 亚洲欧美高清在线 | 大色综合色综合网站 | 国产AV电影院 | 超碰0374在线观看 |