在go語言中,控制反轉(IoC)是物件導向程式設計中的一種設計原則,可以用來減少電腦程式碼之間的耦合度,就是程式碼控制權從商業程式碼「反轉”到框架代碼。常見的控制反轉方式叫做依賴注入,還有一種方式叫做「依賴查找」;透過控制反轉,物件在被創建的時候,由一個調控系統內所有物件的外界實體將其所依賴的物件的引用傳遞給它。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計原則,可以用來減低電腦程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫做「依賴查找」(Dependency Lookup)。透過控制反轉,物件在被創建的時候,由一個調控系統內所有物件的外界實體將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。
講得通俗一點,假如我有一個控制器,UserController,它可以Code,Read,Eat ,當然它還有隱式的__construct建構函數,__destruct析構函數,我們知道這些預設函數在特定的情景會自己觸發,例如初始化的時候,生命週期結束釋放資源的時候,但是我們如果假如這些函數本身都不會自己觸發,那麼我們作為作者怎麼去讓他執行。其實我的控制器還有ArticleController ,YouBadBadController,我要怎麼去處理。
各幹各的User你幹活之前先去構建一下自己,Article你幹活之前也去構建一下自己這個情況短板就很明顯了,後面介紹,每個控制器都要去各幹各的,其實都是Controller ,在處理公共行為的時候,其實我們可以藉組外部實現和管理。我們不用預設的魔法函數了,介紹一個具體場景,假如我現在需要每個控制器都要實作並呼叫一個handle函數。我們怎麼合理去完成,假如現在還要執行一個run 方法,每個控制器添加完run函數之後,我們是不是還要寫他們的調度;
控制反轉統一管理這個操作是不是可以讓一個公共的ControllerService幫忙handle就行了,我們現在不考慮繼承。
class ControllerService{ public functiondo(){ ->handle(); } //去吧比卡丘; } }
等等,小智不投精靈球怎麼去吧,小智呢?我們需要把控制方帶過來
class ControllerService{ public $handler; public function __construct($handler){ $this->handler=$handler ; } //通过构造函数带入; } // public function setHandler($handler){ $this->handler->handle(); } //通过setter带入; } public function do(){ $this->handler->handle(); } //去吧比卡丘; } } new ControllerService()->setHandler(new UserController())->do();
這樣控制權已經反轉給ControllerService了;
Go語言中的interface 反射機制也是Ioc的體現
設計
所採用的第三方函式庫:https: //github.com/berkaroad/ioc
使用起來還是比較簡單的,無非就是RegisterTo, Invoke,但是任何的函式庫都需要結合框架起來才有意義。
一提到松耦合,在GO中很容易就想到介面(interface),所以我們用介面實現的各個層之間的鬆散耦合。
依照傳統的MVC框架,一般服務端會有幾種分層,Controler層、Service層、Module層 從上到下,如何將Ioc結合在框架中才是值得探討的事情。
呼叫結構:由於沒有服務,main函數充當的是Controler、Service是服務層、Module是數據層、Resource是儲存層、app是各種介面的定義
main-->Service-->Module-->Resource
為了示範服務之間的調用,我們定義了service1和service2兩種服務
實作
package app type Service1 interface { AddData(string) DelData(string) } type Service2 interface { AddData(string) DelData(string) } type Module interface { DataToSave(string) DataToRemove(string) } type Resource interface { Save(string) Remove(string) }
package app import ( "github.com/berkaroad/ioc" "github.com/spf13/viper" ) func GetOrCreateRootContainer() ioc.Container { v := viper.Get("runtime.container") if v == nil { v = ioc.NewContainer() viper.Set("runtime.container", v) } return v.(ioc.Container) }
這裡其實怎麼實作都行,只是一個單例NewContainer就可以
package resource import ( "fmt" "github.com/berkaroad/ioc" "github.com/zhaoshoucheng/hodgepodge/IoC/app" ) type ResourceObj struct { name string } func (r *ResourceObj) Save(str string) { fmt.Println(r.name, " Save ", str) } func (r *ResourceObj) Remove(str string) { fmt.Println(r.name, " Remove ", str) } func init() { mo := &ResourceObj{name: "mongo"} // static assert 静态断言类型检测 func(t app.Resource) {}(mo) app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Resource)(nil), ioc.Singleton) //rd := &ResourceObj{name: "redis"} 实现是用的map,所以mong会被覆盖 //app.GetOrCreateRootContainer().RegisterTo(rd, (*app.Resource)(nil), ioc.Singleton) }
RegisterTo是註冊過程,在mo物件後續會當作app.Resource介面的實作來使用,其底層實作是一個map
package module import ( "fmt" "github.com/berkaroad/ioc" "github.com/zhaoshoucheng/hodgepodge/IoC/app" ) var ( rs app.Resource ) type ModuleObj struct { } func (mo *ModuleObj) DataToSave(str string) { fmt.Println("ModuleObj DataToSave ", str) rs.Save(str) } func (mo *ModuleObj) DataToRemove(str string) { fmt.Println("ModuleObj DataToRemove ", str) rs.Remove(str) } func init() { mo := &ModuleObj{} // static assert 静态断言类型检测 func(t app.Module) {}(mo) app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Module)(nil), ioc.Singleton) app.GetOrCreateRootContainer().Invoke(func(r app.Resource) { rs = r }) }
因為我們之前app.Resource已經註冊過,所以這裡Invoke的時候就可以取得到實作該介面的物件
package service import ( "fmt" "github.com/berkaroad/ioc" "github.com/zhaoshoucheng/hodgepodge/IoC/app" ) var ( module app.Module service2 app.Service2 ) type Service1 struct { } func (s1 *Service1) AddData(str string) { service2.AddData(str) fmt.Println("Service1 AddData ", str) module.DataToSave(str) } func (s1 *Service1) DelData(str string) { service2.DelData(str) fmt.Println("Service1 DelData ", str) module.DataToRemove(str) } func init() { s1 := &Service1{} s2 := &Service2{} service2 = s2 //static assert 静态断言做类型检查 func(t app.Service1) {}(s1) func(t app.Service2) {}(s2) app.GetOrCreateRootContainer().RegisterTo(s1, (*app.Service1)(nil), ioc.Singleton) app.GetOrCreateRootContainer().RegisterTo(s2, (*app.Service2)(nil), ioc.Singleton) app.GetOrCreateRootContainer().Invoke(func(mod app.Module) { module = mod }) }
package main import ( "github.com/zhaoshoucheng/hodgepodge/IoC/app" _ "github.com/zhaoshoucheng/hodgepodge/IoC/resource" _ "github.com/zhaoshoucheng/hodgepodge/IoC/module" _ "github.com/zhaoshoucheng/hodgepodge/IoC/service" ) func main() { var s1 app.Service1 app.GetOrCreateRootContainer().Invoke(func(service app.Service1) { s1 = service }) s1.AddData("IOC Test") }
思考
#######我们为什么要用到Ioc呢?个人感觉有几点好处
1.解决各种依赖问题,写GO可能都遇到过循环引用问题,越是复杂的系统就越有可能出现这种混乱的调用现象。
2.实现了很好的扩展性,如果存储层想从redis切换到mongo,定义一个相同的对象,替换注册对象就可以轻松实现。
3.易使用,随时随地可以通过Invoke获取相应的接口对象。
问题
难道就没有问题吗?
当然有,就是引用顺序的问题,也就是先register 还是先invoke 这个在例子中感觉很简单,但是在工程中很容易出错
_ "github.com/zhaoshoucheng/hodgepodge/IoC/module" _ "github.com/zhaoshoucheng/hodgepodge/IoC/resource" _ "github.com/zhaoshoucheng/hodgepodge/IoC/service"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/resource" _ "github.com/zhaoshoucheng/hodgepodge/IoC/module" _ "github.com/zhaoshoucheng/hodgepodge/IoC/service"
第一种写法就会崩溃,第二种正确
原因第一种module 的init 先执行,app.Resource的对象还没有注册。所以init的先后顺序很重要
但这个是凭借字节码进行的排序,有时IDE还不让我们改,所以需要一些控制器去处理这种情况。
以上是go語言中控制反轉是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!