golang简单技巧

我会整理一些我在使用golang时发现的一些小技巧。

工厂转换

在Golang中,函数是一等公民,因此可以根据情况替换某个接口的工厂方法。

// ユーザー情報を表す構造体
type User struct {
    Name string
    Age  int
}

// Userに関係するインターフェイス
type UserRepo interface {
    FindByName(string) (*User, error)
}
// メモリ上にユーザー情報を持っておく
type userRepoMem struct {
    mem []*User
}
func (r userRepoMem) FindByName(name string) (*User, error) {
    for _, u := range r.mem {
        if u.Name == name {
            return u, nil
        }
    }
    return nil, errors.New("not found")
}

func newUserRepoByMem() Repo {
    return userRepoMem{}
}
// SQLite上にユーザー情報を持っておく
type userRepoSQLite struct {
    db *sql.DB
}
func (r userRepoSQLite) FindByName(name string) (*User, error){
    rows := r.db.QueryRow("select * from user where name = ?", name)

    user := new(User)
    err  := rows.Scan(&user.Name, &user.Age)

    return user, err
}

func newUserRepoBySQLite() Repo {
    db, err := sql.Open("sqlite3", ":memory:")
    panic(err)

    return userRepoSQLite{ db }
}
var (
    // デフォルトではメモリ実装を使う
    NewUserRepo func() UserRepo = newUserRepoByMem
)

// 外部からどちらを使うか決められる
func UseMem() {
    NewUserRepo = newUserRepoByMem
}
func UseSQLite() {
    NewUserRepo = newUserRepoBySQLite
}

在这个例子中,虽然我们从外部进行配置,但根据使用情况,我认为可能会有一种在内部进行切换并确认使用哪个设置的方法。

请参考以下链接:
https://github.com/GoogleCloudPlatform/go-endpoints

结构体{}

没有任何字段的结构体的大小为0,因此可以用作”不需要具体值的虚拟变量”。
例如,在go语言中没有set,所以可以用map代替set,这时可以使用这种结构体。

// 記事を表す構造体
type Post struct {
    OwnerID int
    Title   string
    Body    string
}

// 記事群から筆者IDを重複なしで抜き出す
posts  := GetAllPost()
owners := func() []int {
    set := make(map[int]struct{})
    for _, post := range posts {
        set[post.OwnerID] = struct{}{}
    }

    res := make([]int, 0, len(set))
    for id := range set {
        res = append(res, id)
    }

    return res
}()

请参考以下链接:
http://qiita.com/najeira/items/47539ab346fa0c00dc62#tips-%E7%A9%BAstruct%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

x/net/context = x/net/上下文

谷歌发布了一些非标准的软件包,其中包括x/net/context。
这个软件包似乎是为了并发处理而设计的,但也似乎被用作一般的上下文。
它定义了NewContext / FromContext 函数,用于与现有上下文的关联/ 解除关联。

package database

import (
    "database/sql"
    "golang.org/x/net/context"
)

const (
    reqKey = "database"
)

func NewContext(ctx context.Context, db *sql.DB) context.Context {
    return context.WithValue(ctx, reqKey, db)
}
func FromContext(ctx context.Context) *sql.DB {
    if db, ok := ctx.Value(reqKey).(*sql.DB); ok {
        return db
    }
    // いちおうnilを返しているけど型アサーションの確認はしないで返している例もありました
    return nil
}

// ctxにはdb以外にもいろいろと情報が巻き込まれている
func GetUserByName(ctx context.Context, name string) (*User, error) {
    // 確実にctxにdbが巻き込まれていることを想定する
    db := FromContext(ctx)

    // ... 以下dbを使って処理する
}

参考资料:
https://github.com/drone/drone/tree/master/server
https://github.com/guregu/kami
http://qiita.com/nyarla/items/c89ee1ae8703cc459aec

独特的内部键

x/net/context公开了一个名为context.WithValue的函数,用于将它与现有上下文关联起来。
调用此函数需要提供”现有上下文”、”要关联的值”和”在提取值时使用的键(类型为interface{})”。
为了确保键的唯一性,可以通过创建一个私有类型的私有变量来轻松生成唯一键。

在之前的例子中,

// struct{}のままだとどこでも使えるので、別名をつけてプライベートな型にする
type private struct{}

var (
    reqKey private
)

func NewContext(ctx context.Context, db *sql.DB) context.Context {
    return context.WithValue(ctx, reqKey, db)
}
func FromContext(ctx context.Context) *sql.DB {
    if db, ok := ctx.Value(reqKey).(*sql.DB); ok {
        return db
    }
    return nil
}

大概的情况是这样的。在golang中无法进行指针操作,而且以小写字母开头的变量对外部是不可见的,所以按照正常的方法来做的话,不应该会有重复的情况发生。
(使用reflect或者unsafe等方式可能可以创建与reqKey相同的变量,但不确定是否可行)

顺便提一下,如果将reqKey定义为一个struct{},将会普通地产生冲突。它被定义为私有类型是非常重要的。

如果您想在同一封装中创建多个NewContext/FromContext对象,可能可以考虑获取一个私有int变量的指针之类的方法。(获取struct{}的指针会导致冲突)

参考文献:
https://github.com/goji/context/blob/master/bridge.go
http://ikawaha.hateblo.jp/entry/2015/03/13/013521
http://qiita.com/nyarla/items/202d1ded0f576f1bb36b

请参考以下链接:
https://github.com/goji/context/blob/master/bridge.go
http://ikawaha.hateblo.jp/entry/2015/03/13/013521
http://qiita.com/nyarla/items/202d1ded0f576f1bb36b

全局变量

func echo(msg string) int {
    fmt.Println(msg)
    return 0
}

var n = echo("global var")

func init(){
    _ = echo("init")
}

func main(){
    _ = echo("main")
}

当我们运行这样的golang文件时,我们会发现在全局变量初始化时,函数调用比init函数和main函数早。
有一个利用这种特性的包叫做flag。

package server

import (
    "flag"
    "net/http"
    "fmt"
)

var port = flag.Int("port", 8080, "listen post")

func Serve(){
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
        fmt.Fprint(w, "Hello World")
    })
    http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}
package main

import (
    "flag"

    // go buildする場合は下のimportパスを適宜書き換えてください
    "./server"
)

func main(){
    flag.Parse()

    server.Serve()
}

使用 “go build” 命令编译此代码,并调用”./server -port=9000″,在 http://localhost:9000 上将显示 “Hello World”。这种跨多个包分发配置的方法非常方便。

参考:
https://github.com/drone/config (这是 flag.FlagSet 的包装库。看起来很容易使用,但许可证不太清楚)

不会被推荐的技术。

这可能不是一个被推荐的技巧。请把它当作一个搞笑话题。

_代码.go

在 Golang 中,以 _ 开头的文件不会被编译。使用这种方法可以根据源代码实现进行切换。

var mem []*User

func GetUserByName(name string) (*User, error) {
    for _, u := range mem {
        if u.Name == name {
            return u, nil
        }
    }
    return nil, errors.New("not found")
}
var db *sql.DB

func GetUserByName(name string) (*User, error){
    rows := db.QueryRow("select * from user where name = ?", name)

    user := new(User)
    err  := rows.Scan(&user.Name, &user.Age)

    return user, err
}

可以这样,按照源代码单元准备实现,未使用的部分可改为像_~~~.go那样进行切换。
最好还是正常地创建接口……

广告
将在 10 秒后关闭
bannerAds