• 技术文章 >后端开发 >Golang

    go语言中控制反转是什么

    青灯夜游青灯夜游2023-01-28 11:12:00原创114

    在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的体现

    Golang 控制反转 (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)
    }

    IOC 初始化

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

    Main

    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视频教程编程教学

    以上就是go语言中控制反转是什么的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:go语言 Golang
    上一篇:go语言中const怎么用 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • Go语言break停止语句有什么用• Go语言中注释有什么作用• go语言中return怎么用• go语言中反射三定律是什么• go语言依赖注入是什么• go语言有没有注解• go语言面向什么
    1/1

    PHP中文网