在Go语言中,不允许解决导入循环问题。

huangapple go评论74阅读模式
英文:

Overcoming import cycle not allowed in Go

问题

我理解了问题,根据这里的答案,但是,我确实需要帮助或更详细的代码解释来解决这个问题。

我的情况是这样的:我过去将模型和控制器分开,在我的模型包中有一个名为datastore.go的文件,其中包含所有模型函数的接口:

package models

type DSDatabase interface {
    CreateUser(ctx context.Context, username string, password []byte) (*datastore.Key, error)
    // 更多模型函数
}

type datastoreDB struct {
    client *datastore.Client
}

var (
    DB DSDatabase
    _  DSDatabase = &datastoreDB{}
)

func init() {
    // 初始化数据存储
}

这一切都很好,因为模型函数也位于模型包中,所以我的控制器包中的函数可以自由调用models.DB.CreateUser(ctx, "username", []byte("password"))

现在,我决定将上述所有代码移动到一个datastore包中,而CreateUser的模型位于一个user包中。换句话说,package user现在包含了控制器和模型函数,其中控制器相关的函数依赖于datastore包,而DSDatabase接口依赖于user模型函数。

我真的很希望能够帮助解决导入循环的问题,同时保持DSDatastore接口与其他包(如homeuser)分离。


如果上述内容不够清晰,上述代码已更改为:

package datastore

import (
    "github.com/username/projectname/user"
)

type DSDatabase interface {
    user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}

...

而在我的user包中,我在一个与控制器相关的文件中有以下代码:

package user

import (
    "github.com/username/projectname/datastore"
)

func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
    // 获取表单数据等
    datastore.DB.CreateUser(ctx, "username", []byte("password"))
}

在另一个与模型相关的文件中,我有以下代码:

package user

import (
    "github.com/username/projectname/datastore"
)

func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
    key := datastore.NameKey("User", username, nil)
    var user User
    err := db.client.Get(ctx, key, &user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

这当然会导致导入循环,我很遗憾无法解决它...

英文:

I understand the problem, as per the answer here, however, I could really use help or a more detailed code explanation of how it's overcome.

My situation is this: I used to have models and controllers separated, and in my models package I had a datastore.go file containing an interface of all the model functions:

package models

type DSDatabase interface {
    CreateUser(ctx context.Context, username string, password []byte) (*datastore.Key, error)
    // More model functions
}

type datastoreDB struct {
	client *datastore.Client
}

var (
	DB DSDatabase
	_  DSDatabase = &datastoreDB{}
)

func init() {
    // init datastore
}

This was all fine because the model functions were also located within the models package, so my functions in the controller package could freely call models.DB.CreateUser(ctx, "username", []byte("password")).

Now, I have decided to move all the above code to a datastore package, whereas the model for CreateUser is located in a user package. In other words, package user now contains both controller and model functions, for which the controller related functions rely on datastore package, while the DSDatabase interface rely on the user model functions.

I would really appreciate help figuring out how to overcome the import cycle while keeping the DSDatastore interface separate from all the other packages such as home and user.


in case the above is not clear enough, the above code has changed to:

package datastore

import (
    "github.com/username/projectname/user"
)

type DSDatabase interface {
    user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}

...

while in my user package I have this in a controller-related file:

package user

import (
    "github.com/username/projectname/datastore"
)

func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
    // get formdata and such
    datastore.DB.CreateUser(ctx, "username", []byte("password"))
}

and in another model-related file I have:

package user

import (
    "github.com/username/projectname/datastore"
)

func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
	key := datastore.NameKey("User", username, nil)
	var user User
	err := db.client.Get(ctx, key, &user)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

Which of course results in an import cycle, that I sadly can't figure out how to overcome..

答案1

得分: 4

首先,你不能在包A中定义一个在包B中声明的类型上的方法。

所以这个代码...

package user

import (
    "github.com/username/projectname/datastore"
)

func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
    key := datastore.NameKey("User", username, nil)
    var user User
    err := db.client.Get(ctx, key, &user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

...甚至不能编译通过。

这段代码...

package datastore

import (
    "github.com/username/projectname/user"
)

type DSDatabase interface {
    user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}

...也是无效的Go代码。


至于你的问题... 你可以在user包中定义Datastore接口,并将实现放在另一个包中,这样可以很好地适应当你需要一个接口的不同实现时。如果你这样做,user包就不需要再知道datastore包的存在了,但是datastore包仍然需要知道user包,这是可以接受的。

一个例子:

package user

import (
    "context"
)

type DSDatabase interface {
    CreateUser(ctx context.Context, username string, password []byte) (*User, error)
    // ...
}

// 这个变量可以由实现接口的包设置,
// 或者由导入user包和定义接口实现的包设置。
var DB DSDatabase

type User struct {
    // ...
}

func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
    // 获取表单数据等等
    DB.CreateUser(ctx, "username", []byte("password"))
}

实现接口的包:

package datastore

import (
    "context"
    "github.com/username/projectname/user"
)

// DB 实现了 user.DSDatabase 接口。
type DB struct { /* ... */ }

func (db *DB) CreateUser(ctx context.Context, username string) (*user.User, error) {
    key := datastore.NameKey("User", username, nil)
    var user user.User
    err := db.client.Get(ctx, key, &user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func init() {
    // 确保初始化 user.DB 变量,
    // 这个变量被 CreateUserPOST 函数访问,否则会出现空引用恐慌。
    user.DB = &DB{}
}
英文:

First things first, you cannot define a method, in pacakge A, on a type declared in package B.

So this...

package user

import (
    "github.com/username/projectname/datastore"
)

func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
	key := datastore.NameKey("User", username, nil)
	var user User
	err := db.client.Get(ctx, key, &user)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

...should not even compile.

This here...

package datastore

import (
    "github.com/username/projectname/user"
)

type DSDatabase interface {
    user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}

...this is also invalid Go code.


As to your question... one thing you could do is to define the Datastore interface inside the user package and have the implementation live in another package, this lends itself nicely for when you need different implementations of one interface. If you do this your user package does not need to know about the datastore package anymore, the datastore package still has to know about the user package though, which is a OK.

An example:

package user

import (
    "context"
)

type DSDatabase interface {
    CreateUser(ctx context.Context, username string, password []byte) (*User, error)
    // ...
}

// This can be set by the package that implements the interface
// or by any other package that imports the user package and
// a package that defines an implementation of the interface.
var DB DSDatabase

type User struct {
    // ...
}

func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
    // get formdata and such
    DB.CreateUser(ctx, "username", []byte("password"))
}

The package with the implementation of the interface:

package datastore

import (
    "context"
    "github.com/username/projectname/user"
)

// DB implements the user.DSDatabase interface.
type DB struct { /* ... */ }

func (db *DB) CreateUser(ctx context.Context, username string) (*user.User, error) {
    key := datastore.NameKey("User", username, nil)
    var user user.User
    err := db.client.Get(ctx, key, &user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func init() {
    // make sure to initialize the user.DB variable that
    // is accessed by the CreateUserPOST func or else you'll
    // get nil reference panic.
    user.DB = &DB{}
}

huangapple
  • 本文由 发表于 2017年4月9日 05:51:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/43300723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定