让我们考虑您的典型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()
的单独抽象,或者?