• [技术干货] go语言常量
    Go 语言常量常量是一个简单值的标识符,在程序运行时,不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。常量的定义格式:const identifier [type] = value你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。显式类型定义: const b string = "abc"隐式类型定义: const b = "abc"多个相同类型的声明可以简写为:const c_name1, c_name2 = value1, value2以下实例演示了常量的应用:实例package mainimport "fmt"func main() {   const LENGTH int = 10   const WIDTH int = 5      var area int   const a, b, c = 1, false, "str" //多重赋值   area = LENGTH * WIDTH   fmt.Printf("面积为 : %d", area)   println()   println(a, b, c)   }以上实例运行结果为:面积为 : 501 false str常量还可以用作枚举:const (    Unknown = 0    Female = 1    Male = 2)数字 0、1 和 2 分别代表未知性别、女性和男性。常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:实例package mainimport "unsafe"const (    a = "abc"    b = len(a)    c = unsafe.Sizeof(a))func main(){    println(a, b, c)}以上实例运行结果为:abc 3 16iotaiota,特殊常量,可以认为是一个可以被编译器修改的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。iota 可以被用作枚举值:const (    a = iota    b = iota    c = iota)第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:const (    a = iota    b    c)iota 用法实例package mainimport "fmt"func main() {    const (            a = iota   //0            b          //1            c          //2            d = "ha"   //独立值,iota += 1            e          //"ha"   iota += 1            f = 100    //iota +=1            g          //100  iota +=1            h = iota   //7,恢复计数            i          //8    )    fmt.Println(a,b,c,d,e,f,g,h,i)}以上实例运行结果为:0 1 2 ha ha 100 100 7 8再看个有趣的的 iota 实例:实例package mainimport "fmt"const (    i=1<<iota    j=3<<iota    k    l)func main() {    fmt.Println("i=",i)    fmt.Println("j=",j)    fmt.Println("k=",k)    fmt.Println("l=",l)}以上实例运行结果为:i= 1j= 6k= 12l= 24iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。
  • [技术干货] go语言变量
    Go 语言变量变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。声明变量的一般形式是使用 var 关键字:var identifier type可以一次声明多个变量:var identifier1, identifier2 type实例package mainimport "fmt"func main() {    var a string = "Runoob"    fmt.Println(a)    var b, c int = 1, 2    fmt.Println(b, c)}以上实例输出结果为:Runoob1 2变量声明第一种,指定变量类型,如果没有初始化,则变量默认为零值。var v_name v_typev_name = value零值就是变量没有做初始化时系统默认设置的值。实例package mainimport "fmt"func main() {    // 声明一个变量并初始化    var a = "RUNOOB"    fmt.Println(a)    // 没有初始化就为零值    var b int    fmt.Println(b)    // bool 零值为 false    var c bool    fmt.Println(c)}以上实例执行结果为:RUNOOB0false数值类型(包括complex64/128)为 0布尔类型为 false字符串为 ""(空字符串)以下几种类型为 nil:var a *intvar a []intvar a map[string] intvar a chan intvar a func(string) intvar a error // error 是接口实例package mainimport "fmt"func main() {    var i int    var f float64    var b bool    var s string    fmt.Printf("%v %v %v %q\n", i, f, b, s)}输出结果是:0 0 false ""第二种,根据值自行判定变量类型。var v_name = value实例package mainimport "fmt"func main() {    var d = true    fmt.Println(d)}输出结果是:true
  • [技术干货] go语言的基础语法
    Go 标记Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:fmt.Println("Hello, World!")6 个标记是(每行一个):1. fmt2. .3. Println4. (5. "Hello, World!"6. )行分隔符在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。以下为两个语句:fmt.Println("Hello, World!")fmt.Println(":runoob.com")注释注释不会被编译,每一个包应该有相关注释。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:// 单行注释/* Author by 菜鸟教程 我是多行注释 */标识符标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。以下是有效的标识符:mahesh   kumar   abc   move_name   a_123myname50   _temp   j   a23b9   retVal以下是无效的标识符:1ab(以数字开头)case(Go 语言的关键字)a+b(运算符是不允许的)字符串连接Go 语言的字符串可以通过 + 实现:实例package mainimport "fmt"func main() {    fmt.Println("Google" + "Runoob")}
  • [技术干货] go语言的结构
    Go Hello World 实例Go 语言的基础组成有以下几个部分:包声明引入包函数变量语句 & 表达式注释接下来让我们来看下简单的代码,该代码输出了"Hello World!":实例package mainimport "fmt"func main() {   /* 这是我的第一个简单的程序 */   fmt.Println("Hello, World!")}让我们来看下以上程序的各个部分:第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。执行 Go 程序让我们来看下如何编写 Go 代码并执行它。步骤如下:打开编辑器如Sublime2,将以上代码添加到编辑器中。将以上代码保存为 hello.go打开命令行,并进入程序文件保存的目录中。输入命令 go run hello.go 并按回车执行代码。如果操作正确你将在屏幕上看到 "Hello World!" 字样的输出。$ go run hello.goHello, World!我们还可以使用 go build 命令来生成二进制文件:$ go build hello.go $ lshello    hello.go$ ./hello Hello, World!注意需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:实例package mainimport "fmt"func main()  {  // 错误,{ 不能在单独的行上    fmt.Println("Hello, World!")}
  • [技术干货] 华为大咖分享:Gopher China2020 华为云的go语言云原生实战经验(后附PPT下载)
    回复本贴查看下载完整版PPT点击 →《华为云DevCloud大咖分享汇总(附PPT下载)》[hide]https://devcloud.cn-north-4.huaweicloud.com/codehub/project/8e8846873de740e396de90d8182b63fd/codehub/1284981/home/commit/4c77cfc5d0ea1b56a91e5df12605bb5f508a503d?ref=master&filePath=Gopher%20China2020%20%E5%8D%8E%E4%B8%BA%E4%BA%91%E7%9A%84go%E8%AF%AD%E8%A8%80%E4%BA%91%E5%8E%9F%E7%94%9F%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C.pdf&isFile=true[/hide]
  • [问题求助] 运行startGoClient.sh 提示: ./go-client: No such file or directory
    运行根据自己的项目修改的startGoClient.sh文件,总是报错:这是项目结构config内部的结构
  • [问题求助] 输入./startGoClient.sh,提示:./go-client: No such file or directory
    参考go-demo-sdk编写了对智能合约进行增删查改的命令,建立了应用项目,按照华为官网的提示配置文件和运行相关命令,可是在输入之后提示:./startGoClient.sh: line 28: ./go-client: No such file or directory./startGoClient.sh: line 31: ./go-client: No such file or directory尝试以各种方式重新构建项目,还是不行,请问大家都是怎么建立包含命令的项目的呀?有遇到类似情况的伙伴可以分享一下解决办法吗?
  • [技术干货] 快来,这里有23种设计模式的Go语言实现(一)
    【摘要】 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。前言从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题。在当下,如果你不会一点设计模式,都不好意思说自己是一个合格的程序员。设计模式通常被定义为:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。从定义上看,设计模式其实是一种经验的总结,是针对特定问题的简洁而优雅的解决方案。既然是经验总结,那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。然而,学习设计模式的最高境界是习得其中解决问题所用到的思想,当你把它们的本质思想吃透了,也就能做到即使已经忘掉某个设计模式的名称和结构,也能在解决特定问题时信手拈来。好的东西有人吹捧,当然也会招黑。设计模式被抨击主要因为以下两点:1、设计模式会增加代码量,把程序逻辑变得复杂。这一点是不可避免的,但是我们并不能仅仅只考虑开发阶段的成本。最简单的程序当然是一个函数从头写到尾,但是这样后期的维护成本会变得非常大;而设计模式虽然增加了一点开发成本,但是能让人们写出可复用、可维护性高的程序。引用《软件设计的哲学》里的概念,前者就是战术编程,后者就是战略编程,我们应该对战术编程Say No!(请移步《专家教你系列<三>:写出的代码复杂度太高?看下专家怎么说》)2、滥用设计模式。这是初学者最容易犯的错误,当学到一个模式时,恨不得在所有的代码都用上,从而在不该使用模式的地方刻意地使用了模式,导致了程序变得异常复杂。其实每个设计模式都有几个关键要素:适用场景、解决方法、优缺点。模式并不是万能药,它只有在特定的问题上才能显现出效果。所以,在使用一个模式前,先问问自己,当前的这个场景适用这个模式吗?《设计模式》一书的副标题是“可复用面向对象软件的基础”,但并不意味着只有面向对象语言才能使用设计模式。模式只是一种解决特定问题的思想,跟语言无关。就像Go语言一样,它并非是像C++和Java一样的面向对象语言,但是设计模式同样适用。本系列文章将使用Go语言来实现GoF提出的23种设计模式,按照创建型模式(Creational Pattern)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)三种类别进行组织,文本主要介绍其中的创建型模式。单例模式(Singleton Pattern)简述单例模式算是23中设计模式里最简单的一个了,它主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。在程序设计中,有一些对象通常我们只需要一个共享的实例,比如线程池、全局缓存、对象池等,这种场景下就适合使用单例模式。但是,并非所有全局唯一的场景都适合使用单例模式。比如,考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。如何判断一个对象是否应该被建模成单例?通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,这个对象是一个中心点吗?Go实现在对某个对象实现单例模式时,有两个点必须要注意:(1)限制调用者直接实例化该对象;(2)为该对象的单例提供一个全局唯一的访问方法。对于C++/Java而言,只需把类的构造函数设计成私有的,并提供一个static方法去访问该类点唯一实例即可。但对于Go语言来说,即没有构造函数的概念,也没有static方法,所以需要另寻出路。我们可以利用Go语言package的访问规则来实现,将单例结构体设计成首字母小写,就能限定其访问范围只在当前package下,模拟了C++/Java中的私有构造函数;再在当前package下实现一个首字母大写的访问函数,就相当于static方法的作用了。在实际开发中,我们经常会遇到需要频繁创建和销毁的对象。频繁的创建和销毁一则消耗CPU,二则内存的利用率也不高,通常我们都会使用对象池技术来进行优化。考虑我们需要实现一个消息对象池,因为是全局的中心点,管理所有的Message实例,所以将其实现成单例,实现代码如下: package msgpool ... // 消息池 type messagePool struct { pool *sync.Pool } // 消息池单例 var msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } // 访问消息池单例的唯一方法 func Instance() *messagePool { return msgPool } // 往消息池里添加消息 func (m *messagePool) AddMsg(msg *Message) { m.pool.Put(msg) } // 从消息池里获取消息 func (m *messagePool) GetMsg() *Message { return m.pool.Get().(*Message) } ...测试代码如下: package test ... func TestMessagePool(t *testing.T) { msg0 := msgpool.Instance().GetMsg() if msg0.Count != 0 { t.Errorf("expect msg count %d, but actual %d.", 0, msg0.Count) } msg0.Count = 1 msgpool.Instance().AddMsg(msg0) msg1 := msgpool.Instance().GetMsg() if msg1.Count != 1 { t.Errorf("expect msg count %d, but actual %d.", 1, msg1.Count) } } // 运行结果 === RUN TestMessagePool --- PASS: TestMessagePool (0.00s) PASS以上的单例模式就是典型的“饿汉模式”,实例在系统加载的时候就已经完成了初始化。对应地,还有一种“懒汉模式”,只有等到对象被使用的时候,才会去初始化它,从而一定程度上节省了内存。众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验锁来优化。对于“懒汉模式”,Go语言有一个更优雅的实现方式,那就是利用sync.Once,它有一个Do方法,其入参是一个方法,Go语言会保证仅仅只调用一次该方法。 // 单例模式的“懒汉模式”实现 package msgpool ... var once = &sync.Once{} // 消息池单例,在首次调用时初始化 var msgPool *messagePool // 全局唯一获取消息池pool到方法 func Instance() *messagePool { // 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次 once.Do(func() { msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } }) return msgPool } ...建造者模式(Builder Pattern)简述在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。这种情况下,创建这个复杂对象就会变得很繁琐。对于C++/Java而言,最常见的表现就是构造函数有着长长的参数列表: MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)而对于Go语言来说,最常见的表现就是多层的嵌套实例化: obj := &MyObject{ Field1: &Field1 { Param1: &Param1 { Val: 0, }, Param2: &Param2 { Val: 1, }, ... }, Field2: &Field2 { Param3: &Param3 { Val: 2, }, ... }, ... }上述的对象创建方法有两个明显的缺点:(1)对对象使用者不友好,使用者在创建对象时需要知道的细节太多;(2)代码可读性很差。针对这种对象成员较多,创建对象逻辑较为繁琐的场景,就适合使用建造者模式来进行优化。建造者模式的作用有如下几个:1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。3、对多个对象复用同样的对象创建逻辑。其中,第1和第2点比较常用,下面对建造者模式的实现也主要是针对这两点进行示例。Go实现考虑如下的一个Message结构体,其主要有Header和Body组成: package msg ... type Message struct { Header *Header Body *Body } type Header struct { SrcAddr string SrcPort uint64 DestAddr string DestPort uint64 Items map[string]string } type Body struct { Items []string } ...如果按照直接的对象创建方式,创建逻辑应该是这样的: // 多层的嵌套实例化 message := msg.Message{ Header: &msg.Header{ SrcAddr: "192.168.0.1", SrcPort: 1234, DestAddr: "192.168.0.2", DestPort: 8080, Items: make(map[string]string), }, Body: &msg.Body{ Items: make([]string, 0), }, } // 需要知道对象的实现细节 message.Header.Items["contents"] = "application/json" message.Body.Items = append(message.Body.Items, "record1") message.Body.Items = append(message.Body.Items, "record2")虽然Message结构体嵌套的层次不多,但是从其创建的代码来看,确实存在对对象使用者不友好和代码可读性差的缺点。下面我们引入建造者模式对代码进行重构: package msg ... // Message对象的Builder对象 type builder struct { once *sync.Once msg *Message } // 返回Builder对象 func Builder() *builder { return &builder{ once: &sync.Once{}, msg: &Message{Header: &Header{}, Body: &Body{}}, } } // 以下是对Message成员对构建方法 func (b *builder) WithSrcAddr(srcAddr string) *builder { b.msg.Header.SrcAddr = srcAddr return b } func (b *builder) WithSrcPort(srcPort uint64) *builder { b.msg.Header.SrcPort = srcPort return b } func (b *builder) WithDestAddr(destAddr string) *builder { b.msg.Header.DestAddr = destAddr return b } func (b *builder) WithDestPort(destPort uint64) *builder { b.msg.Header.DestPort = destPort return b } func (b *builder) WithHeaderItem(key, value string) *builder { // 保证map只初始化一次 b.once.Do(func() { b.msg.Header.Items = make(map[string]string) }) b.msg.Header.Items[key] = value return b } func (b *builder) WithBodyItem(record string) *builder { b.msg.Body.Items = append(b.msg.Body.Items, record) return b } // 创建Message对象,在最后一步调用 func (b *builder) Build() *Message { return b.msg }测试代码如下: package test ... func TestMessageBuilder(t *testing.T) { // 使用消息建造者进行对象创建 message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() if message.Header.SrcAddr != "192.168.0.1" { t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr) } if message.Body.Items[0] != "record1" { t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0]) } } // 运行结果 === RUN TestMessageBuilder --- PASS: TestMessageBuilder (0.00s) PASS从测试代码可知,使用建造者模式来进行对象创建,使用者不再需要知道对象具体的实现细节,代码可读性也更好。工厂方法模式(Factory Method Pattern)简述工厂方法模式跟上一节讨论的建造者模式类似,都是将对象创建的逻辑封装起来,为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别,建造者模式更常用于需要传递多个参数来进行实例化的场景。使用工厂方法来创建对象主要有两个好处:1、代码可读性更好。相比于使用C++/Java中的构造函数,或者Go中的{}来创建对象,工厂方法因为可以通过函数名来表达代码含义,从而具备更好的可读性。比如,使用工厂方法productA := CreateProductA()创建一个ProductA对象,比直接使用productA := ProductA{}的可读性要好。2、与使用者代码解耦。很多情况下,对象的创建往往是一个容易变化的点,通过工厂方法来封装对象的创建过程,可以在创建逻辑变更时,避免霰弹式修改。工厂方法模式也有两种实现方式:(1)提供一个工厂对象,通过调用工厂对象的工厂方法来创建产品对象;(2)将工厂方法集成到产品对象中(C++/Java中对象的static方法,Go中同一package下的函数)Go实现考虑有一个事件对象Event,分别有两种有效的时间类型Start和End: package event ... type Type uint8 // 事件类型定义 const ( Start Type = iota End ) // 事件抽象接口 type Event interface { EventType() Type Content() string } // 开始事件,实现了Event接口 type StartEvent struct{ content string } ... // 结束事件,实现了Event接口 type EndEvent struct{ content string } ...1、按照第一种实现方式,为Event提供一个工厂对象,具体代码如下: package event ... // 事件工厂对象 type Factory struct{} // 更具事件类型创建具体事件 func (e *Factory) Create(etype Type) Event { switch etype { case Start: return &StartEvent{ content: "this is start event", } case End: return &EndEvent{ content: "this is end event", } default: return nil } }测试代码如下: package test ... func TestEventFactory(t *testing.T) { factory := event.Factory{} e := factory.Create(event.Start) if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = factory.Create(event.End) if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEventFactory --- PASS: TestEventFactory (0.00s) PASS2、按照第二种实现方式,分别给Start和End类型的Event单独提供一个工厂方法,代码如下: package event ... // Start类型Event的工厂方法 func OfStart() Event { return &StartEvent{ content: "this is start event", } } // End类型Event的工厂方法 func OfEnd() Event { return &EndEvent{ content: "this is end event", } }测试代码如下: package event ... func TestEvent(t *testing.T) { e := event.OfStart() if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = event.OfEnd() if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEvent --- PASS: TestEvent (0.00s) PASS抽象工厂模式(Abstract Factory Pattern)简述在工厂方法模式中,我们通过一个工厂对象来创建一个产品族,具体创建哪个产品,则通过swtich-case的方式去判断。这也意味着该产品组上,每新增一类产品对象,都必须修改原来工厂对象的代码;而且随着产品的不断增多,工厂对象的职责也越来越重,违反了单一职责原则。抽象工厂模式通过给工厂类新增一个抽象层解决了该问题,如上图所示,FactoryA和FactoryB都实现·抽象工厂接口,分别用于创建ProductA和ProductB。如果后续新增了ProductC,只需新增一个FactoryC即可,无需修改原有的代码;因为每个工厂只负责创建一个产品,因此也遵循了单一职责原则。Go实现考虑需要如下一个插件架构风格的消息处理系统,pipeline是消息处理的管道,其中包含了input、filter和output三个插件。我们需要实现根据配置来创建pipeline ,加载插件过程的实现非常适合使用工厂模式,其中input、filter和output三类插件的创建使用抽象工厂模式,而pipeline的创建则使用工厂方法模式。各类插件和pipeline的接口定义如下: package plugin ... // 插件抽象接口定义 type Plugin interface {} // 输入插件,用于接收消息 type Input interface { Plugin Receive() string } // 过滤插件,用于处理消息 type Filter interface { Plugin Process(msg string) string } // 输出插件,用于发送消息 type Output interface { Plugin Send(msg string) } package pipeline ... // 消息管道的定义 type Pipeline struct { input plugin.Input filter plugin.Filter output plugin.Output } // 一个消息的处理流程为 input -> filter -> output func (p *Pipeline) Exec() { msg := p.input.Receive() msg = p.filter.Process(msg) p.output.Send(msg) }接着,我们定义input、filter、output三类插件接口的具体实现: package plugin ... // input插件名称与类型的映射关系,主要用于通过反射创建input对象 var inputNames = make(map[string]reflect.Type) // Hello input插件,接收“Hello World”消息 type HelloInput struct {} ​ func (h *HelloInput) Receive() string { return "Hello World" } // 初始化input插件映射关系表 func init() { inputNames["hello"] = reflect.TypeOf(HelloInput{}) } package plugin ... // filter插件名称与类型的映射关系,主要用于通过反射创建filter对象 var filterNames = make(map[string]reflect.Type) // Upper filter插件,将消息全部字母转成大写 type UpperFilter struct {} ​ func (u *UpperFilter) Process(msg string) string { return strings.ToUpper(msg) } // 初始化filter插件映射关系表 func init() { filterNames["upper"] = reflect.TypeOf(UpperFilter{}) } package plugin ... // output插件名称与类型的映射关系,主要用于通过反射创建output对象 var outputNames = make(map[string]reflect.Type) // Console output插件,将消息输出到控制台上 type ConsoleOutput struct {} ​ func (c *ConsoleOutput) Send(msg string) { fmt.Println(msg) } // 初始化output插件映射关系表 func init() { outputNames["console"] = reflect.TypeOf(ConsoleOutput{}) }然后,我们定义插件抽象工厂接口,以及对应插件的工厂实现: package plugin ... // 插件抽象工厂接口 type Factory interface { Create(conf Config) Plugin } // input插件工厂对象,实现Factory接口 type InputFactory struct{} // 读取配置,通过反射机制进行对象实例化 func (i *InputFactory) Create(conf Config) Plugin { t, _ := inputNames[conf.Name] return reflect.New(t).Interface().(Plugin) } // filter和output插件工厂实现类似 type FilterFactory struct{} func (f *FilterFactory) Create(conf Config) Plugin { t, _ := filterNames[conf.Name] return reflect.New(t).Interface().(Plugin) } type OutputFactory struct{} func (o *OutputFactory) Create(conf Config) Plugin { t, _ := outputNames[conf.Name] return reflect.New(t).Interface().(Plugin) }最后定义pipeline的工厂方法,调用plugin.Factory抽象工厂完成pipelien对象的实例化: package pipeline ... // 保存用于创建Plugin的工厂实例,其中map的key为插件类型,value为抽象工厂接口 var pluginFactories = make(map[plugin.Type]plugin.Factory) // 根据plugin.Type返回对应Plugin类型的工厂实例 func factoryOf(t plugin.Type) plugin.Factory { factory, _ := pluginFactories[t] return factory } // pipeline工厂方法,根据配置创建一个Pipeline实例 func Of(conf Config) *Pipeline { p := &Pipeline{} p.input = factoryOf(plugin.InputType).Create(conf.Input).(plugin.Input) p.filter = factoryOf(plugin.FilterType).Create(conf.Filter).(plugin.Filter) p.output = factoryOf(plugin.OutputType).Create(conf.Output).(plugin.Output) return p } // 初始化插件工厂对象 func init() { pluginFactories[plugin.InputType] = &plugin.InputFactory{} pluginFactories[plugin.FilterType] = &plugin.FilterFactory{} pluginFactories[plugin.OutputType] = &plugin.OutputFactory{} }测试代码如下: package test ... func TestPipeline(t *testing.T) { // 其中pipeline.DefaultConfig()的配置内容见【抽象工厂模式示例图】 // 消息处理流程为 HelloInput -> UpperFilter -> ConsoleOutput p := pipeline.Of(pipeline.DefaultConfig()) p.Exec() } // 运行结果 === RUN TestPipeline HELLO WORLD --- PASS: TestPipeline (0.00s) PASS原型模式(Prototype Pattern)简述原型模式主要解决对象复制的问题,它的核心就是clone()方法,返回Prototype对象的复制品。在程序设计过程中,往往会遇到有一些场景需要大量相同的对象,如果不使用原型模式,那么我们可能会这样进行对象的创建:新创建一个相同对象的实例,然后遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这种方法的缺点很明显,那就是使用者必须知道对象的实现细节,导致代码之间的耦合。另外,对象很有可能存在除了对象本身以外不可见的变量,这种情况下该方法就行不通了。对于这种情况,更好的方法就是使用原型模式,将复制逻辑委托给对象本身,这样,上述两个问题也都迎刃而解了。Go实现还是以建造者模式一节中的Message作为例子,现在设计一个Prototype抽象接口: package prototype ... // 原型复制抽象接口 type Prototype interface { clone() Prototype } ​ type Message struct { Header *Header Body *Body } ​ func (m *Message) clone() Prototype { msg := *m return &msg }测试代码如下: package test ... func TestPrototype(t *testing.T) { message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() // 复制一份消息 newMessage := message.Clone().(*msg.Message) if newMessage.Header.SrcAddr != message.Header.SrcAddr { t.Errorf("Clone Message failed.") } if newMessage.Body.Items[0] != message.Body.Items[0] { t.Errorf("Clone Message failed.") } } // 运行结果 === RUN TestPrototype --- PASS: TestPrototype (0.00s) PASS总结本文主要介绍了GoF的23种设计模式中的5种创建型模式,创建型模式的目的都是提供一个简单的接口,让对象的创建过程与使用者解耦。其中,单例模式主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点;建造者模式主要解决需要创建对象时需要传入多个参数,或者对初始化顺序有要求的场景;工厂方法模式通过提供一个工厂对象或者工厂方法,为使用者隐藏了对象创建的细节;抽象工厂模式是对工厂方法模式的优化,通过为工厂对象新增一个抽象层,让工厂对象遵循单一职责原则,也避免了霰弹式修改;原型模式则让对象复制更加简单。下一篇文章,将介绍23种设计模式中的7种结构型模式(Structural Pattern),及其Go语言的实现。
  • [页面编排] GDE2.0用adc cli工具创建完的no code自定义组件运行adc run显示页面不存在,应该怎样配置2.0noCode
    【功能模块】【操作步骤&问题现象】如何使用nocode【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] undefined: fabsdk.WithOrgid如何解决
    搭建的为fabric链,运行GO示例Demo-GO SDK Demo中的Chaincode_Go_Demo时,出现图中错误,想请教一下怎么解决?
  • [问题求助] 求个 go-fastdfs 的arm版本
    go-fastdfs
  • [交流吐槽] Go os.NewFile函数:新建文件
    描述NewFile函数是os包用于新建文件的函数。NewFile并不是真正创建了一个文件,而是新建了文件但并不保存,返回新建后文件的指针。 语法和参数函数签名func NewFile(fd uintptr, name string) *File参数名称    含义fd    文件描述符name    文件名返回值os.NewFile函数返回os.File类型的指针。 使用示例os.NewFile函数返回了fun.go文件的指针。(文件没有被创建)package main import (    "os") func main() {    file := os.NewFile(0, "fun.go")    defer file.Close()     file.Write([]byte("hi"))} 注意事项不能用os.NewFile去创建文件,因为os.NewFile不会真正的将文件保存。package main import (    "fmt"    "os") func main() {    file := os.NewFile(0, "fun.go")    defer file.Close()     _, err := os.Open("fun.go")    fmt.Println(err)    // output: open fun.go: no such file or directory
  • [技术干货] 【我用鲲鹏做开发】部署鲲鹏golang环境
    关联【我用鲲鹏做开发】搭建VScode remote development远程开发环境介绍go语言是目前主流开发语言之一,本篇将继续介绍如何在鲲鹏Ubuntu环境下搭建go语言环境。需求资源华为云鲲鹏ECS一台,操作系统安装Ubuntu 18.04 server 64bit with ARM操作步骤Ubuntu环境下安装go语言可选以下两种方式使用apt安装手动安装1、使用apt安装apt install golang使用apt安装一般版本较旧,比如Ubuntu 18.04 server 64bit with ARM默认安装版本为1.10可以通过手动安装更高版本,安装前使用以下命令卸载旧版本apt remove golang sudo rm -rf /usr/bin/go2、手动安装参考[其他语言] 鲲鹏服务器上部署golang环境 并修正环境变量设置# 到https://studygolang.com/dl下载想要的版本,比如1.16.4 wget https://studygolang.com/dl/golang/go1.16.4.linux-arm64.tar.gz tar -C /usr/local -zxvf go1.11.5.linux-amd64.tar.gz # 解压到/usr/local/go mkdir -p $HOME/go-workspace/{src,bin,pkg} # 创建go的工作空间配置/etc/profileexport GOROOT=/usr/local/go #设置为go安装的路径 export GOPATH=$HOME/go-workspace #默认安装包的路径 export PATH=$PATH:$GOROOT/bin:$GOPATH/bin生效/etc/profilesource /etc/profile验证新建hello.go文件package main import "fmt" func main() { fmt.Printf("hello, world\n") }验证go版本,并运行hello.go文件
  • [技术干货] go语言的四数相加等于指定数算法
    给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。首先将四个数组分割为两两数组,前两个数组值相加,后两个数组相加,入股前两个数组相加和与后两个数组相加和正好为相反数,四个元素之和为0.首先将两数组的元素进行遍历相加,相加之和为map的索引。所指向的元素,就是出现的次数。12345678func foursumcount(A []int, B []int, C []int, D []int) int{ des :=map[int]int{} for _,v:=range A{  for _,w:=range B{   des[v+w]++  } }}再次遍历另两个数组,将两个数组的元素进行相加,取和的相反数,通过使用相反数在map中查找,如果没出现,所指向的数是0,如果出现过这个数的相反数,则所指向的数大于一。123456789func foursumcount(A []int, B []int, C []int, D []int) int{ des :=map[int]int{} ans:=0 for _,v:=range C{  for _,w:=range D{   ans +=des[-v-w]  } }}最后返回总数全部代码123456789101112131415func fourSumCount(A []int, B []int, C []int, D []int) int { des := map[int]int{} ans:=0 for _,v :=range A{//遍历两个数组,将两个数组的和作为一个索引,进行+1操作  for _,w:=range B{    des[v+w]++  } } for _,v :=range C{//遍历另两个数组,如果这两个数组进行相加的和的相反数在map中不为1,则证明出现过  for _,w:=range D{   ans +=des[-v-w]  } } return ans//返回总数}补充:算法题:三个数相加等于某个特定值题目来自于leetcode第十五题给定一个n个整数的数组S,是否存在S中的元素a,b,c,使得a + b + c = 0? 查找数组中所有唯一的三元组,它们的总和为零。注意:解决方案集不能包含重复的三元组。例子给定数组:1S = [-1, 0, 1, 2, -1, -4]解决方案:1[[-1, 0, 1],[-1, -1, 2]]在刚看到这道题目的题目的时候,首先想到的就是暴力解法,将数组排序后直接嵌套三个循环,这样子虽然简单,但是时间复杂度确实n^3,遇到数据量过大的时候消耗太大,提交的时候并没有通过。自己在想了一段时间后想到了一些优化方案,但是本质上都没有将次方缩减,所以仍然需要改进,目标为n^2。首先,目标为n^2的话,就需要将数组扫描两遍,第一层循环没有问题,但要将第二层和第三层循环缩减为扫描一遍,因为是要将两个数相加等于某个值,所以可将有序数组分别从前往后和从后往前扫描,直至碰头,碰头后如果继续循环的话,所得到的结果会重复,所以到碰头后可以跳出循环。这样子只需要扫描数组一遍就可达到两层循环的结果。思路简单是这样,在实现的时候要考虑一些其他的问题,具体实现的代码如下:12345678910111213141516171819202122232425262728293031public class Solution {    public List<List<Integer>> threeSum(int[] nums) {        List<List<Integer>> result = new LinkedList<List<Integer>>();        if(nums.length<3){            return result;        }        Arrays.sort(nums);        int left=0,right=nums.length-1;        for(int mid=0;mid< nums.length-2;mid++){            if(nums[mid]>0) break;            if(mid == 0 || (mid > 0 && nums[mid] != nums[mid-1])){                left=mid+1;                right=nums.length-1;                while(left<right){                    if(nums[left]+nums[mid]+nums[right] ==0){                        result.add(Arrays.asList(nums[mid],nums[left],nums[right]));                        while (left < right && nums[left] == nums[left+1]) left++;                        while (left < right && nums[right] == nums[right-1]) right--;                        left++;                        right--;                    }else if(nums[left]+nums[mid]+nums[right]<0){                        left++;                    }else if(nums[left]+nums[mid]+nums[right]>0){                        right--;                    }                }            }        }        return result;    }}
  • [技术干货] golang如何去除多余空白字符(含制表符)
    看代码吧~123456789//利用正则表达式压缩字符串,去除空格或制表符func compressStr(str string) string {    if str == "" {        return ""    }    //匹配一个或多个空白符的正则表达式    reg := regexp.MustCompile("\\s+")    return reg.ReplaceAllString(str, "")}补充:go语言去除字符串尾部所有空格刷 leetcode 的一个算法题。要求只删除字符串尾部的所有字符串. google 只搜出通过 strings.Trim() 方法删除前后空格或者删除字符串前边和后边的固定子字符串。方法从字符串后端开始计算空格数量,然后用切片切掉:1234567891011func deleteTailBlank(str string) string { spaceNum := 0 for i := len(str)-1; i >= 0; i-- {  // 去除字符串尾部的所有空格  if str[i] == ' ' {   spaceNum++  } else {   break  } } return str[:len(str)-spaceNum]}补充:go:字符串去除空格和换行符 strings.Replace看代码吧~12345678910111213141516package main import ( "fmt" "strings") func main() { str := "这里是 www\n.runoob\n.com" fmt.Println("-------- 原字符串 ----------") fmt.Println(str) // 去除空格 str = strings.Replace(str, " ", "", -1) // 去除换行符 str = strings.Replace(str, "\n", "", -1) fmt.Println("-------- 去除空格与换行后 ----------") fmt.Println(str)}
总条数:166 到第
上滑加载中