【发布时间】:2017-09-11 15:48:45
【问题描述】:
让我们考虑一下您的典型 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 应用程序易于测试的精神,我们将把所有的 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]
如您所知,这使得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.UserService 实在是太丑陋了。
我查看了各种 Web 应用程序模板,但没有一个(除了那些令人难以置信的准系统)对这种结构有一个优雅的解决方案。他们中的大多数(如果不是全部)只是完全省略了接口来解决它,这是不可接受的。
【问题讨论】: