让我们考虑您的典型Web应用程序。在MVC应用程序中,您最终可能希望引入一个“服务”层,该层抽象出诸如用户注册之类的复杂业务逻辑。因此,在您的控制器中,您将传递一个services.User结构实例,并只需对其调用Register()方法。
现在,如果services.User仅仅是一个结构,我们可以拥有一个相对简单的源代码结构,如下所示:
- [other directories here]/
- services/
- user.go
- [other service structs here]
- main.go
Run Code Online (Sandbox Code Playgroud)
并且services/user.go看起来像这样:
package services
type User struct { ... }
func NewUserService(){ ... }
func (u User) Register() { ... }
Run Code Online (Sandbox Code Playgroud)
到目前为止,所有这些都相当容易阅读。假设我们将其进一步发展。本着使我们的Web应用程序易于测试的精神,我们将所有Service结构转换为Service接口。这样,我们可以轻松模拟它们以进行单元测试。为此,我们将创建一个“ 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]
Run Code Online (Sandbox Code Playgroud)
如您所知,这使services程序包和目录变得更加难以处理-您可以轻松想象一下,通过十二种不同的接口看起来会多么混乱,每个接口至少具有至少一种实现。如果我更改了中的User接口user.go,则必须在目录列表中遍历所有内容,以查找要更改的所有实现,这一点都不理想。
另外,当您键入内容services.New(...)并收到50条左右的自动完成建议时,它变得非常疯狂。该services包已成为只是一个蹒跚的庞然大物。
我必须解决的最简单的想法之一就是违背惯例并接受重复:
- services/
- userService/
- app.go // the AppUser struct
- map.go // the MapUser struct
- interface.go // the User interface
- [other services here]
Run Code Online (Sandbox Code Playgroud)
这会将所有与UserService相关的代码保存在一个逻辑独立的程序包中。但是必须不断提及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
Run Code Online (Sandbox Code Playgroud)
在您的特定示例中,至少我会将实现存储在接口定义下的一级:
- services/
- userService/
- interface.go // the User interface
- app
app.go // the AppUser struct
- map
map.go // the MapUser struct
- [other services here]
Run Code Online (Sandbox Code Playgroud)
这样,在map.New()和之间就不会造成混乱app.New(),并且您的内部数据结构不会互相影响等等。