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那样进行切换。
最好还是正常地创建接口……