克服Go语言中不允许的导入循环问题

3

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

我的情况是:我曾经把模型和控制器分开,在我的模型包中有一个datastore.go文件,其中包含所有模型函数的接口:

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
}

之前的代码都在models包里,所以我的控制器包中的函数可以自由地调用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) {
    // get formdata and such
    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
}

当然,这会导致一个循环引用问题,我很遗憾无法想出如何解决它...

1
你似乎在寻找技术解决方案,但实际上你需要更好地定义哪个包使用了哪个。从术语上看,数据存储似乎是最底层的,不会知道其他包的情况。但我无法真正了解你所拥有的业务逻辑。此外,在用户包中使用datastoreDB接收器感觉很奇怪。也许这个方法应该在datastore包中?也许代码应该放在同一个包中?如果你看一下现实生活中的项目,它们在同一个包中有大量的文件。 - alexbt
如果数据存储和用户彼此需要,那么它们就不能是单独的包。它们为什么一开始就在不同的包中呢? - JimB
@alexbt 在此之前我使用的是MVC结构,但我喜欢将控制器和模型放在一起的想法正如我早些时候提出的问题所建议的。datastore.go文件还包含配置以创建一个单一的数据存储客户端,然后可以使用datastoreDB接收器访问它。但听起来我需要回去将“用户模型函数”和“用户控制器函数”分开到不同的包中? - fisker
@jimb 我将它们放在不同的包中,因为我觉得有必要将modelscontrollers包中没有任何关联的所有函数分开,而且建议我将与users相关的所有内容放在一起,以避免出现controllers/usermodels/user这样的目录,这对我来说是有意义的。 - fisker
这个回答解决了你的问题吗?不允许导入循环 - Michael Freidgeim
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)
}

关于你的问题... 你可以在user包中定义Datastore接口,并让实现位于另一个包中,这很适用于当你需要一个接口的不同实现。如果你这样做,你的user包就不再需要知道datastore包了,但是datastore包仍然需要知道user包,这没问题。

一个例子:

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

实现该接口的包:

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

这太棒了,非常感谢。但我猜这意味着无法在用户包内定义CreateUser函数?放弃“models”和“controllers”包结构的主要原因是将与用户相关的所有函数放在用户包内,但似乎这个解决方案仍会将所有模型函数放在“datastore”包内?我确实喜欢在“user”包内定义“DSDatabase”接口的想法,但这是否可能而不导入“datastore”包? - fisker
你很可能会在用户包中有CreateUser函数,但是拥有DSDatabase接口似乎毫无意义。你需要它干什么? - mkopriva
但是如果我理解正确的话,将CreateUser函数放在“user”包内,那么您需要导入“datastore”包(否则您最终会创建多个数据存储客户端),从而导致导入循环错误?我正在开发一个已经相当庞大的Web应用程序,因为所有文件都位于“controllers”和“models”包内,所以最终我会使用特定前缀来命名我的函数,因此我正在寻找一种将其拆分的方法。 - fisker
如果您在user包内定义了CreateUserPOSTCreateUser,这是可以的,那么你为什么需要额外使用datastore包呢? DSDatabase接口有什么作用? - mkopriva
你不需要为此创建一个接口,你可以在一个包内设置一个客户端的单个实例变量,比如说你的 datastore 包,就这样... userpost 可以导入 datastore 来使用客户端,而 datastore 只需要导入 cloud.google.com/go/datastore 包来声明该变量即可。 - mkopriva
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接