• 【golang】why 消费者定义接口,生产者返回实体struct?


     1. 场景假设

    一个简单的业务系统归结为生产者和消费者之间的交互,生产者提供服务,消费者使用服务;

    如下代码结构:业务逻辑层和数据库层

    1. ├── db
    2. │ └── db.go
    3. └── user
    4. └── user.go

    2. 消费者定义自己使用的接口

    • 消费者负责定义接口,即消费者所依赖的是一个接口;
    • 消费者定义如下:
    1. //user.go
    2. package user
    3. // 第一对外所依赖的接口
    4. type UserStore interface {
    5. Insert(item interface{}) error
    6. Get(id int) error
    7. }
    8. type UserService struct {
    9. store UserStore
    10. }
    11. // Accepting interface here!
    12. func NewUserService(s UserStore) *UserService {
    13. return &UserService{
    14. store: s,
    15. }
    16. }
    17. func (u *UserService) CreateUser() { ... }
    18. func (u *UserService) RetrieveUser(id int) User { ... }
    19. //db.go
    20. package db
    21. // 生产者实现消费者所需的接口依赖,注意该生产者可以供给多个消费者使用,实现所有消费者所需要的方法;
    22. type Store struct {
    23. db *sql.DB
    24. }
    25. func NewDB() *Store { ... } //func to initialise DB
    26. func (s *Store) Insert(item interface{}) error { ... } //insert item
    27. func (s *Store) Get(id int) error { ... } //get item by id
    消费者 user.go 需要存储依赖才能执行与用户相关的业务逻辑。它不关心存储是什么,只是它有 2 个方法,Insert() 和 Get(),因此它能够创建和检索用户。因此,它定义了自己的接口 UserStore 并将其作为依赖项接受。 db.go 中的 Store 结构实现了这个接口,因此可以用作依赖项。
    
    就这么简单!接受接口就是让消费者定义他们想要的接口。消费者不应该担心依赖是什么,只要它可以执行消费者需要的任务。由于 Go 继承的隐含性质,这也是可能的。这样做会带来一些好处:
    • 1. 松散的耦合,更大的灵活性  Looser coupling, greater flexibility  (​​​​​​​通过接受接口,使用者不会与其依赖项耦合。如果明天我决定使用 mySQL 而不是 Postgres,那么 user.go 根本不需要更改。这保留了使用任何存储的灵活性,只要它满足使用者定义的接口)
    • 2. 方便测试 ( 测试也会变得更加简单,因为我们可以轻松地传入内存中的模拟,而不必为了单元测试而启动实际的数据库实例,这可能会非常昂贵。我们可以只有一个模拟内存存储,其中包含测试用例所需的适当数据)

    3. 生产者返回实体 struct

            生产者应该向消费者提供具体的类型,而不是接口。如果返回了接口还需要cast the interface to struct. 

    1. //db.go
    2. package db
    3. type Store struct {
    4. db *sql.DB
    5. }
    6. // 返回一个实体类型
    7. func NewDB() *Store { ... }
    8. func (s *Store) Insert(item interface{}) error { ... }
    9. func (s *Store) Get(id int) error { ... }
    10. // 后面新增方法,消费者不需要改动 这也是返回实体的便利之处

       4. 如果消费者返回接口会怎么样? (preemptive interfaces  先发制人的接口)     

    1. //postgres.go 生产者
    2. package db
    3. type Store interface {
    4. Insert(item interface{}) error
    5. Get(id int) error
    6. }
    7. type MyStore struct {
    8. db *sql.DB
    9. }
    10. func InitDB() Store { ... } //func to initialise DB
    11. func (s *MyStore ) Insert(item interface{}) error { ... } //insert item
    12. func (s *MyStore ) Get(id int) error { ... } //get item by id
    13. //user.go 消费者
    14. package user
    15. type UserService struct {
    16. store db.Store
    17. }
    18. func NewUserService(s db.Store) *UserService {
    19. return &UserService{
    20. store: s,
    21. }
    22. }
    23. func (u *UserService) CreateUser() { ... }
    24. func (u *UserService) RetrieveUser(id int) User { ... }

           接口现在由生产者定义,使用者使用该接口作为入口点。这就是所谓的先发制人接口。生产者在实际使用接口之前先行定义接口。先发制人的接口也使得测试更加困难。

    5. 小结

    • 让使用者定义它使用的接口
    • 生产者应返回实体类型
  • 相关阅读:
    发布管理工作流程介绍
    ELK 企业级日志分析系统
    springboot(spring)整合redis(集群)、细节、底层配置讲解
    LLVM系列第十七章:控制流语句for
    这10 个很“哇塞”的Web资源,前端必备的神仙级网站
    打造硬核敲门砖——简历
    Excel只读模式的密码如何取消?
    9、Python字符串操作:字符串基本操作、字符串方法、格式化
    C#/.NET/.NET Core优秀项目和框架2023年12月简报
    MySQL主从复制读写分离
  • 原文地址:https://blog.csdn.net/qfzhangwei/article/details/126329133