Part 18: 接口 - I

欢迎来到Golang系列教程 的第18部分,这是接口教程 2 小部分的第 1 部分。

什么是接口?

在面向对象世界接口的一般定义是“接口定义一个对象的行为”。它只指定对象应该做什么。获取这个行为(实现细节)的方式是由对象决定。

在Go中,接口是一组方法的签名。当类型提供了接口中所有方法的定义时,我们说实现了这个接口。这个面向对象世界很相似。接口指定类型应该有什么方法和类型决定如何实现这些方法

例如WashingMachine可以是拥有 Cleaning()Drying() 方法签名的接口。任何提供 Cleaning()Drying() 方法签名定义的类型,就叫实现了 WashingMachine 接口。

声明和实现接口

让我们直接进行程序,创建一个接口并实现它。

package main

import (  
    "fmt"
)

//interface definition
type VowelsFinder interface {  
    FindVowels() []rune
}

type MyString string

//MyString implements VowelsFinder
func (ms MyString) FindVowels() []rune {  
    var vowels []rune
    for _, rune := range ms {
        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
            vowels = append(vowels, rune)
        }
    }
    return vowels
}

func main() {  
    name := MyString("Sam Anderson")
    var v VowelsFinder
    v = name // possible since MyString implements VowelsFinder
    fmt.Printf("Vowels are %c", v.FindVowels())
    
}

在上面程序的第 8 行创建了一个名为 VowelsFinder 的接口类型,它有一个方法 FindVowels() []rune

在下一行,创建了 MyString 类型。

在第 15 行我们给接口器类型 MyString 增加方法 FindVowels() []rune 。现在就说 MyString 实现了 VowelsFinder 接口。这和其他语言截然不同,比如Java,类必须使用关键字 implements 显式声明它实现一个接口。这在 go 中是不需要的,如果一个类型包含在接口中声明的所有方法,go 接口被隐式实现

在第 28 行,我们将 MyString 类型的 name 赋值给 VowelFinder 类型的 v 。这是可以的,因为 MyString 实现了 VowelFinder 接口。在下一行在 MyString 类型上调用 FindVowels 方法打印字符串 Sam Anderson 中所有的元音。这个程序输出 Vowels are [a e o]

恭喜,你已经创建并实现了你第一个接口。

接口的实际使用

上面程序教了我们如何创建并实现接口,但它并没有真正的展示接口的使用。如果我们在上面的程序中使用 name.FindVowels() 代替 v.FindVowels() ,它也会工作但没有使用已经创建的接口。

现在让我们来看一下接口的实际用例。

我们将写一个简单的程序,来基于一个公司雇员的个人薪水计算公司的总费用。为了简短,我们假设所有的花费是美元。

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    CalculateSalary() int
}

type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

type Contract struct {  
    empId  int
    basicpay int
}

//salary of permanent employee is sum of basic pay and pf
func (p Permanent) CalculateSalary() int {  
    return p.basicpay + p.pf
}

//salary of contract employee is the basic pay alone
func (c Contract) CalculateSalary() int {  
    return c.basicpay
}

/*
total expense is calculated by iterating though the SalaryCalculator slice and summing  
the salaries of the individual employees  
*/
func totalExpense(s []SalaryCalculator) {  
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("Total Expense Per Month $%d", expense)
}

func main() {  
    pemp1 := Permanent{1, 5000, 20}
    pemp2 := Permanent{2, 6000, 30}
    cemp1 := Contract{3, 3000}
    employees := []SalaryCalculator{pemp1, pemp2, cemp1}
    totalExpense(employees)

}

在上面程序的第 7 行声明了包含一个方法 CalculateSalary() intSalaryCalculator 接口类型。

在公司中,有两类雇员,在第 11 行和 17 行被结构体定义的 PermanentContract。长期雇员的薪水是 basicpaypf 的和,而合同雇员的薪水只有其他的 basicpay。它们相应的 CalculateSalary 方法分别在第 23 行和 28 行表述。通过声明这个方法,PermanentContract 都实现了 SalaryCalculator 接口。

在 36 行声明的 totalExpense 函数表达了使用接口的美妙之处。这个方法使用一个 SalaryCalculator 接口切片作为参数,在 49 行,我们向 totalExpense 函数传递包含 PermanentContract 类型的切片。totalExpense 函数通过调用相应类型的 CalculateSalary 方法计算花费。这在 39 行完成。

这个程序的最大优势是 totalExpense 可以通过增加任何新的雇员类型来扩展而不用任何代码。我们说公司增加了一个使用不同薪水结构的新的雇员类型 FreelancerFreelancer 可以公通过切片参数传给 totalExpense,甚至不用修改 totalExpense 函数的一行代码。这个方法将执行它就的操作,因为 Freelancer 也实现了 SalaryCalculator 接口。

这个程序输出 Total Expense Per Month $14050

接口内部表示

接口可以被认为是元组 (type, value) 的内部表示。 type 是接口的基础具体类型,value 保存具体类型的值.

我们来写一个程序以更好的理解它。

package main

import (  
    "fmt"
)

type Tester interface {  
    Test()
}

type MyFloat float64

func (m MyFloat) Test() {  
    fmt.Println(m)
}

func describe(t Tester) {  
    fmt.Printf("Interface type %T value %v\n", t, t)
}

func main() {  
    var t Tester
    f := MyFloat(89.7)
    t = f
    describe(t)
    t.Test()
}

Tester 接口有一个方法 Test()MyFloat 类型实现了这个接口。在 24 行,我们将 MyFloat 类型的变量 f 赋值给 Tester 类型的 t 。现在 t 的具体类型是 MyFloat ,t 的值是 89.7。第 17 行的 describe 函数打印它的值和接口的具体类型。程序输出

Interface type main.MyFloat value 89.7  
89.7  

空接口

没有任何方法的接口被称为空接口。它表示为 interface{}。由于空接口没有方法,所有的类型实现空接口。

package main

import (  
    "fmt"
)

func describe(i interface{}) {  
    fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main() {  
    s := "Hello World"
    describe(s)
    i := 55
    describe(i)
    strt := struct {
        name string
    }{
        name: "Naveen R",
    }
    describe(strt)
}

在上面程序中,在 7 行,函数 describe(i interface{}) 使用一个空接口作为参数,因此它可以被传递任何参数。

我们分别在 13, 15 和 21 行向 describe 函数传递 string, int 和 结构体,程序打印

Type = string, value = Hello World  
Type = int, value = 55  
Type = struct { name string }, value = {Naveen R}  

类型断言

类型断言用于提取接口底层的值。

**i.(T)**是用来获取具体类型为 T 的接口 i 的底层值的语法。

程序胜千言,我们写一个类型断言的程序

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) //get the underlying int value from i
    fmt.Println(s)
}
func main() {  
    var s interface{} = 56
    assert(s)
}

在 12 行 s 的具体类型是 int。我们在第 8 行使用语法 i.(int) 来获取 i 的底层的值。这个程序打印 56

如果上面程序的具体类型不是 int 会发生什么?我们来找出它。

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) 
    fmt.Println(s)
}
func main() {  
    var s interface{} = "Steven Paul"
    assert(s)
}

在上面程序我们向函数 assert 传递具体类型为 strings,试图从它提取 int 值。这个程序将使用消息 panic: interface conversion: interface {} is string, not int 的恐慌。

为了解决这个总是我们使用语法

v, ok := i.(T)

如果 i 的具体类型为 T ,那么 v 将会有 i 的底层的值,同时 ok 的值是 true。

如果 i 的具体类型不是 Tok 是 false,同时 v 将有一个类型 T 的零值,程序没有恐慌(panic)

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    v, ok := i.(int)
    fmt.Println(v, ok)
}
func main() {  
    var s interface{} = 56
    assert(s)
    var i interface{} = "Steven Paul"
    assert(i)
}

当将 Steven Paul 传递给函数 assertok将是 false,由于 i 的具体类型不是 int,同时 v 的值将是 0 ,它是 int 的零值,程序将打印

56 true
0 false

类型开关(Switch)

类型开关用来将接口的具体类型与各个在 case 语句指定的多个类型相比较。它和 switch case 很相。唯一不同的是 cases 指定的是类型而不是正常 switch 中的值。

类型开关的语法类似于类型断言。在类型断言 i.(T) 的语法中,类型 T 应该被类型开关的关键字 type 替换。让我们在下面的程序中看下它是如何工作的。

package main

import (  
    "fmt"
)

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("I am a string and my value is %s\n", i.(string))
    case int:
        fmt.Printf("I am an int and my value is %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
func main() {  
    findType("Naveen")
    findType(77)
    findType(89.98)
}

在上面程序的第 8 行,switch i.(type) 指定一个类型开关。每个 case 语句将 i 的具体类型与指定类型相比较。如果匹配任何 case,相应的语句被打印。程序输出

I am a string and my value is Naveen  
I am an int and my value is 77  
Unknown type  

在 20 行89.98float64 类型,与任何 case 都不匹配,因此在最下面的那行打印 Unknown type

比较一个类型与接口也是可以的。如果我们有一个类型并且类型实现了一个接口。可以比较这个类型与它实现的接口

程序可以更加清晰

package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}

func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown type\n")
    }
}

func main() {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}

在上面程序中,结构体 Person 实现了 Describer 接口。在 19 行的 case 语句,vDescriber 接口类型比较。p 实现了 Describer。因此在 32 行,控制命令 findType(p),这个 case 满足这种情况,Describe() 方法被调用。

这个程序输出

unknown type  
Naveen R is 25 years old  

这将我们带到了接口第一部分的结尾,我们将在接口第二部分继续讨论。

**下一教程 - 接口 - II

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值