接口及其实现的软件包/目录结构



让我们考虑您的典型Web应用程序。在MVC应用程序中,您可能最终想引入一个"服务"层,该层抽象了复杂的业务逻辑,例如用户注册。因此,在您的控制器中,您将传递services.User结构的实例,然后在其上调用Register()方法。

现在,如果services.User只是一个结构,我们可以具有相对简单的源代码结构,例如:

- [other directories here]/
- services/
    - user.go
    - [other service structs here]
- main.go

services/user.go看起来像:

package services
type User struct { ... }
func NewUserService(){ ... }
func (u User) Register() { ... }

到目前为止,这都很容易阅读。假设我们将其进一步走了一步。本着使我们的Web应用程序可以轻松测试的精神,我们将所有服务结构变成服务界面。这样,我们可以轻松地模拟它们进行单位测试。为此,我们将创建一个" appuser"结构(用于实际应用程序)和" mapuser"结构(用于模拟目的)。将接口和实现放置在同一services目录中是有道理的 - 它们都是service代码,毕竟。

我们的services文件夹现在看起来像这样:

- services/
    - app_user.go // the AppUser struct
    - [other services here]
    - map_user.go // the MapUser struct
    - [other services here]
    - user.go  // the User interface
    - [other service structs here]

您可以看出,这使得services软件包和目录更加困难 - 您可以轻松地想象它与十二个不同的接口看起来会变得混乱,每个接口至少具有1个实现。如果我更改user.go中的User接口,我必须在目录列表中全部飞镖以查找其所有更改的实现,这根本不是理想的。

此外,当您键入services.New(...)时,它会变得非常疯狂,并且您可能会提出50个左右的自动完整建议。services软件包不过是一个虚假的怪物。

我必须解决的最简单的想法之一就是违反惯例并接受重复:

- services/
    - userService/
        - app.go // the AppUser struct
        - map.go // the MapUser struct
        - interface.go  // the User interface
    - [other services here]

这将所有与用户服务相关的代码保持在逻辑,自包含的软件包中。但是必须不断地指userService.UserService非常丑陋。

我已经查看了各种Web应用程序模板,并且没有一个(除了难以置信的准骨架)可以很好地解决该结构。它们中的大多数(如果不是全部)完全省略了界面以解决它,这是不可接受的。

您的接口实现应(通常)生活在单独的软件包中。这不是一个艰难而快速的规则,您通常可能在接口定义旁边有一个默认实例。

但是想想一个更抽象的示例:一个键/值存储的接口。它可以由文件系统,SQL数据库,Amazon S3或内存数据结构来支持。

您通常会在一个地方定义界面,例如myproject/kvstore/kvstore.go

然后,您将在其他地方定义实现。甚至可能在完全不同的存储库中。

- myproject
    - kvstore
        kvstore.go
        memory.go    -- A default implementation, non-persistent
        - filesystem
            filesystem.go -- A file-persistent implementation
- yourproject
    - sqlite    -- An implementation backed by sqlite

在您的具体示例中,至少我将在接口定义下存储一个级别的实现:

- services/
    - userService/
        - interface.go  // the User interface
        - app
            app.go // the AppUser struct
        - map
            map.go // the MapUser struct
    - [other services here]

那么map.New()app.New()之间没有混淆,您的内部数据结构不会彼此踩,等等。

另一种可能的方法可能是重新安排包装。与services相比,反映在目录结构中:

中反映了更多的对象。
- users/
   - app.go
   - map.go
   - interface.go

可以支持users.New()方法,该方法将注册以委派给默认值。这可以使您可以将基于存根/内存的接口与生产实现的单独文件保持。

如果您需要服务注册表或可能是已注册users.New()的单独抽象,或者?

最新更新