• 【golang】interface 如此动人,却被遗忘


     

    1. 不用接口,做普通的数据封装和访问,觉得很方便啊?

    1. package demo
    2. import "fmt"
    3. type name struct {
    4. firstname string
    5. lastname string
    6. }
    7. // Go 中有一个习惯用法,用于创建一个“构造函数”函数,该函数的名称前面带有单词“ New”,如下所示:
    8. func NewName(first, last string) name {
    9. return name{firstname: first, lastname: last}
    10. }
    11. // 我现在有一个“构造函数”函数,但是我需要能够访问这些字段。为此,我需要一种“方法”:
    12. func (n name) Fullname() string {
    13. return fmt.Sprintf("name: %s %s", n.firstname, n.lastname)
    14. }
    15. // main 包
    16. func main() {
    17. n := demo.NewName("Joe", "Soap")
    18. fmt.Println(n.Fullname())
    19. }

    2. 嵌入类型,假设我有两种类型的人,一个雇员和一个客户。我可以做出这样的东西(也不需要接口):

    1. type employee struct {
    2. employeeID string
    3. firstname string
    4. lastname string
    5. }
    6. func NewEmployee(id, first, last string) employee {
    7. return employee{employeeID: id, firstname: first, lastname: last}
    8. }
    9. func (e employee) ID() string {
    10. return e.employeeID
    11. }
    12. func (e employee) Fullname() string {
    13. return fmt.Sprintf("name: %s %s", e.firstname, e.lastname)
    14. }
    15. type client struct {
    16. clientID string
    17. firstname string
    18. lastname string
    19. }
    20. func NewClient(id, first, last string) client {
    21. return client{clientID: id, firstname: first, lastname: last}
    22. }
    23. func (c client) ID() string {
    24. return c.clientID
    25. }
    26. func (c client) Fullname() string {
    27. return fmt.Sprintf("name: %s %s", c.firstname, c.lastname)
    28. }
    29. // main 包
    30. func main() {
    31. employee1 := NewClient("C0001234", "Joe", "Soap")
    32. client1 := NewEmployee("1122", "Fred", "Bloggs")
    33. fmt.Println(employee1.ID())
    34. fmt.Println(employee1.Fullname())
    35. fmt.Println(client1.ID())
    36. fmt.Println(client1.Fullname())
    37. }
    38. // 输出
    39. C0001234
    40. name: Joe Soap
    41. 1122
    42. name: Fred Bloggs

    但这样非常死板,如果我想添加一个字段,会发生什么?现在我有三个结构和三个方法要更新。语法很快变得复杂,有些东西可能会被遗漏

    好了,是时候简化代码了。我只需要将name嵌入到雇员employee和客户client中,然后只需要一个 FullName ()函数:(注意:代码没有拆包,为了测试方便放到一个包里面了,后面一样。)

    1. package main
    2. import "fmt"
    3. type name struct {
    4. firstname string
    5. lastname string
    6. }
    7. func NewName(first, last string) name {
    8. return name{firstname: first, lastname: last}
    9. }
    10. func (n name) Fullname() string {
    11. return fmt.Sprintf("name: %s %s", n.firstname, n.lastname)
    12. }
    13. type employee struct {
    14. employeeID string
    15. name
    16. }
    17. func NewEmployee(id, first, last string) employee {
    18. return employee{
    19. employeeID: id,
    20. name: name{
    21. firstname: first,
    22. lastname: last,
    23. },
    24. }
    25. }
    26. type client struct {
    27. clientID string
    28. name
    29. }
    30. func NewClient(id, first, last string) client {
    31. return client{
    32. clientID: id,
    33. name: name{
    34. firstname: first,
    35. lastname: last,
    36. },
    37. }
    38. }
    39. func (e employee) ID() string {
    40. return e.employeeID
    41. }
    42. func (c client) ID() string {
    43. return c.clientID
    44. }
    45. func main() {
    46. employee1 := NewClient("C0001234", "Joe", "Soap")
    47. client1 := NewEmployee("1122", "Fred", "Bloggs")
    48. fmt.Println(employee1.ID())
    49. fmt.Println(employee1.Fullname())
    50. fmt.Println(client1.ID())
    51. fmt.Println(client1.Fullname())
    52. }

    上面可以进一步简化,上面代码仍然需要构造三次 name。我已经有了一个名称构造函数,所以我可以像这样直接将名称传递给 NewEmployee ()和 NewClient () :

    func NewEmployee(id string, name name) employee {
     return employee{
      employeeID: id,
      name:       name,
     }
    }func NewClient(id string, name name) client {
     return client{
      clientID: id,
      name:     name,
     }
    }

    修改后:

    1. package main
    2. import "fmt"
    3. type name struct {
    4. firstname string
    5. lastname string
    6. }
    7. func NewName(first, last string) name {
    8. return name{firstname: first, lastname: last}
    9. }
    10. func (n name) Fullname() string {
    11. return fmt.Sprintf("name: %s %s", n.firstname, n.lastname)
    12. }
    13. type employee struct {
    14. employeeID string
    15. name
    16. }
    17. type client struct {
    18. clientID string
    19. name
    20. }
    21. func NewEmployee(id string, name name) employee {
    22. return employee{
    23. employeeID: id,
    24. name: name,
    25. }
    26. }
    27. func NewClient(id string, name name) client {
    28. return client{
    29. clientID: id,
    30. name: name,
    31. }
    32. }
    33. func (e employee) ID() string {
    34. return e.employeeID
    35. }
    36. func (c client) ID() string {
    37. return c.clientID
    38. }
    39. func main() {
    40. employee1 := NewClient("C0001234", NewName("Joe", "Soap"))
    41. client1 := NewEmployee("1122", NewName("Fred", "Bloggs"))
    42. fmt.Println(employee1.ID())
    43. fmt.Println(employee1.Fullname())
    44. fmt.Println(client1.ID())
    45. fmt.Println(client1.Fullname())
    46. }

    3. name如果需要增加一个参数会怎么样?

    需要修改的地方:体验一般 ...7处改动,少改一个地方也不行

    type name struct {  // struct  
     salutation string
     firstname  string
     lastname   string
    }
    func NewName(sal, first, last string) name { // 构造函数 
     return name{
      salutation: sal,
      firstname:  first,
      lastname:   last,
     }
    }func (n name) Fullname() string {  // 函数
     return fmt.Sprintf("name: %s %s %s", n.salutation, n.firstname, n.lastname)
    }
    还有:main方法的入参也要修改     
         employee1 := NewClient("C0001234", NewName("Dr","Joe", "Soap"))
         client1   := NewEmployee("1122", NewName("Mr","Fred", "Bloggs"))
    

    4.  继续抽象ID()方法

    看到ID方法做同样的事情。代码中有两个 ID:ClientID和 Employee.EmployeeID。它们都是字符串类型。因此,让我们创建一个带有构造函数和打印方法的通用 Type:

    效果就是:只保留一个ID函数即可。清理了每个type自己定义的ID()函数。

    1. package main
    2. import "fmt"
    3. // add new Type id
    4. type id string
    5. func NewID(id id) id {
    6. return id
    7. }
    8. func (i id) ID() string {
    9. return string(i)
    10. }
    11. type name struct {
    12. firstname string
    13. lastname string
    14. }
    15. func NewName(first, last string) name {
    16. return name{firstname: first, lastname: last}
    17. }
    18. func (n name) Fullname() string {
    19. return fmt.Sprintf("name: %s %s", n.firstname, n.lastname)
    20. }
    21. type employee struct {
    22. id
    23. name
    24. }
    25. type client struct {
    26. id
    27. name
    28. }
    29. func NewEmployee(id id, name name) employee {
    30. return employee{
    31. id: id,
    32. name: name,
    33. }
    34. }
    35. func NewClient(id id, name name) client {
    36. return client{
    37. id: id,
    38. name: name,
    39. }
    40. }
    41. func main() {
    42. employee1 := NewClient(NewID("C0001234"), NewName("Joe", "Soap"))
    43. client1 := NewEmployee(NewID("1122"), NewName("Fred", "Bloggs"))
    44. fmt.Println(employee1.ID())
    45. fmt.Println(employee1.Fullname())
    46. fmt.Println(client1.ID())
    47. fmt.Println(client1.Fullname())
    48. }

    5. 使用接口 interface 来进行优化

    前面几步都是使用Type做的简化;接口就是对一些共性的抽象;上面的我们定义了id类型(type id string),现在需求变更了,我们希望我们的 EmployeeID 是一个数字,并且我们希望以一个部门作为前缀。使用简单的字符串 Type 不再能cover住我们的变化了。我们希望能够存储代码和部门。我们还希望将 EmployeeID 显示为两个字段,用连字符分隔。为此,我们需要一个 struct 和一些新方法。 这意味着,我们前面定义的id类型将被废弃掉!(但是Client并没有需要扩展的需求,即ClientID还是一个字符串)换成如下 employeeID struct ....这样其实和name struct没有啥区别了.... 

    1. type employeeID struct {
    2. department string
    3. id int
    4. }
    5. func NewEmployeeID(department string, id int) employeeID {
    6. return employeeID{
    7. department: department,
    8. id: id,
    9. }
    10. }
    11. func (eid employeeID) ID() string {
    12. return fmt.Sprintf("%s-%d", eid.department, eid.id)
    13. }

    思考下,如果我们能不能换成一个接口呢?需要及兼容clientID 的原有逻辑,又能承接employeeID新的数据类型,什么样的接口呢? 这个接口肯定有ID()的输出能力...整理代码如下:

    1. package main
    2. import "fmt"
    3. type name struct {
    4. firstname string
    5. lastname string
    6. }
    7. func NewName(first, last string) name {
    8. return name{firstname: first, lastname: last}
    9. }
    10. func (n name) Fullname() string {
    11. return fmt.Sprintf("name: %s %s", n.firstname, n.lastname)
    12. }
    13. // new Person interface
    14. type Person interface {
    15. ID() string
    16. }
    17. //- ----------------------通用ID type begin--------------------------------------
    18. // add new Type id
    19. type id string
    20. func NewID(id id) id {
    21. return id
    22. }
    23. func (i id) ID() string {
    24. return string(i)
    25. }
    26. //- ----------------------通用ID type end --------------------------------------
    27. // ------------------- employeeID begin------------------
    28. type employeeID struct {
    29. department string
    30. id int
    31. }
    32. func NewEmployeeID(department string, id int) employeeID {
    33. return employeeID{
    34. department: department,
    35. id: id,
    36. }
    37. }
    38. func (eid employeeID) ID() string {
    39. return fmt.Sprintf("%s-%d", eid.department, eid.id)
    40. }
    41. // ------------------- employeeID end ------------------
    42. // add person to employee and client structs and constructors
    43. type employee struct {
    44. Person
    45. name
    46. }
    47. type client struct {
    48. Person
    49. name
    50. }
    51. func NewEmployee(person Person, name name) employee {
    52. return employee{
    53. Person: person,
    54. name: name,
    55. }
    56. }
    57. func NewClient(person Person, name name) client {
    58. return client{
    59. Person: person,
    60. name: name,
    61. }
    62. }
    63. func main() {
    64. employee1 := NewClient(NewEmployeeID("Sales", 1234), NewName("Joe", "Soap"))
    65. client1 := NewEmployee(NewID("1122"), NewName("Fred", "Bloggs"))
    66. fmt.Println(employee1.ID())
    67. fmt.Println(employee1.Fullname())
    68. fmt.Println(client1.ID())
    69. fmt.Println(client1.Fullname())
    70. }

    现在,看看这段代码,我们已经在员工和客户端中嵌入了 Person 接口,GOOD!但是我们可以进一步简化。首先,我们可以对 name struct 做同样的事情,就像我们对 id 所做的那样,添加一个接口,然后将这两个接口都嵌入。go语言定义接口,通常使用动词作为名称,例如,一个读取的接口是一个 Reader,一个写入的接口是一个 Writer,嵌入了 Reader 和 Writer 接口的接口被称为 ReadWriter。

    6. 全部接口化 interface 

    注意:NewEmployee ()和 NewClient 现在返回的接口是 Person,而不是像以前一样返回 Employee 和 client,这对于外面的框架使用提供了非常友好的便利。比如,外面统一接受person类型的参数,做通用的业务处理逻辑下面的 Details(p Person) 方法。现在我的代码更短、更清晰,希望更具描述性了。

    1. package main
    2. import "fmt"
    3. // new interface embedding the interfaces Identifier and Namer 合并了所有接口
    4. type Person interface {
    5. Identifier
    6. Namer
    7. }
    8. // renaming Person as Namer 接口:实现 --------
    9. type Namer interface {
    10. Fullname() string
    11. }
    12. type name struct {
    13. salutation string
    14. firstname string
    15. lastname string
    16. }
    17. func NewName(sal, first, last string) name {
    18. return name{
    19. salutation: sal,
    20. firstname: first,
    21. lastname: last,
    22. }
    23. }
    24. func (n name) Fullname() string {
    25. return fmt.Sprintf("name: %s %s %s", n.salutation, n.firstname, n.lastname)
    26. }
    27. // renaming Person as Namer 接口:实现 --------
    28. // ------ Identifier 接口 :实现 -----------
    29. type Identifier interface {
    30. ID() string
    31. }
    32. type employeeID struct {
    33. department string
    34. id int
    35. }
    36. func NewEmployeeID(department string, id int) employeeID {
    37. return employeeID{
    38. department: department,
    39. id: id,
    40. }
    41. }
    42. func (eid employeeID) ID() string {
    43. return fmt.Sprintf("%s-%d", eid.department, eid.id)
    44. }
    45. type id string
    46. func NewID(id id) id {
    47. return id
    48. }
    49. func (i id) ID() string {
    50. return string(i)
    51. }
    52. // ------ Identifier 接口 :实现 -----------
    53. type employee struct {
    54. Identifier
    55. Namer
    56. }
    57. func NewEmployee(id Identifier, name Namer) Person {
    58. return employee{
    59. Identifier: id,
    60. Namer: name,
    61. }
    62. }
    63. type client struct {
    64. Identifier
    65. Namer
    66. }
    67. func NewClient(id Identifier, name Namer) Person {
    68. return client{
    69. Identifier: id,
    70. Namer: name,
    71. }
    72. }
    73. func Details(p Person) {
    74. fmt.Printf("ID:[%s] Name: %s\n", p.ID(), p.Fullname())
    75. }
    76. func main() {
    77. employee1 :=NewEmployee(NewEmployeeID("Sales", 1234), NewName("Dr", "Joe", "Soap"))
    78. client1 := NewClient(NewID("1122"), NewName("Mr", "Fred", "Bloggs"))
    79. id := NewID("foo")
    80. fmt.Println(employee1.ID())
    81. fmt.Println(employee1.Fullname())
    82. fmt.Println(client1.ID())
    83. fmt.Println(client1.Fullname())
    84. fmt.Printf("%s\n", id.ID())
    85. Details(employee1)
    86. Details(client1)
    87. }
    88. // output
    89. Sales-1234
    90. name: Dr Joe Soap
    91. 1122
    92. name: Mr Fred Bloggs
    93. foo
    94. ID:[Sales-1234] Name: name: Dr Joe Soap
    95. ID:[1122] Name: name: Mr Fred Bloggs

  • 相关阅读:
    超大规模和隐私保护,融云如何助力 Web3 社交
    有什么自定义表单工具功能较好?
    深度学习中的图像处理(基本介绍+示例代码)
    Java根据身份证号码提取出省市区,JSON数据格式
    Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机
    《Linux》day5--ssh——ssh登录与scp传文件
    Docker-desktop(Docker桌面版)——入门篇
    免费,C++蓝桥杯等级考试真题--第5级
    SpringCloud无介绍快使用,Nacos集群和Nginx代理(二十)
    2022年最新全国各省五级行政区划代码(省/市/区县/乡镇/村)
  • 原文地址:https://blog.csdn.net/qfzhangwei/article/details/126570068