文章目录
Golang 的 Interface 是一种派生数据类型,使用 type 和 interface 关键字来声明,它定义了一组方法(Method)的签名(函数名、形参列表、返回值)。
Go 不是一种典型的 OOP 编程语言,所以 Golang 在语法上不支持类和继承的概念。没有继承是否就无法拥有多态特性呢?Golang 通过 Interface 实现了多态。
从语法上看,Interface 定义了若干个 Method,这些 Method 只有函数签名,没有具体的函数体。如果某个数据类型实现了 Interface 中定义的所有 Method,则称该数据类型实现了这个 Interface。这是典型的面向对象思想。
格式:
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
可见,相对于基本数据类型所具备的实际操作意义,Golang Interface 的价值内涵更倾向于一种 Duck Typing(鸭子类型)编程思想的体现,是一种编程方式的实现机制。
Interface 实例存储的是实现者的值
示例:
type I interface {
Get() int
Set(int)
}
type S struct {
Age int
}
// S 实现了 I 的两个方法(Set、Get),就说 S 是 I 的实现者。
func(s S) Get() int {
return s.Age
}
func(s *S) Set(age int) {
s.Age = age
}
func f(i I){
i.Set(10)
fmt.Println(i.Get())
}
func main() {
s := S{}
// 若结构体实例实现了某个 interface,那么就可以将其传递到接受这个 interface 为实参的函数。
// 执行 f(&s) 就做完了一次 interface 类型的使用。
f(&s)
}
上例中,如果有多种数据类型实现了某个 Interface,这些数据类型的实例对象都可以直接使用 function f 中的代码实现,这就是泛式编程的思想,也称为 Duck Typing。
不难看出 Interface 实例中存储的值,其实就是实现了这个 Interface 的数据类型的实例的值。Golang 会自动进行 Interface 的检查,并在运行时隐式的完成从其他数据类型到该 Interface 类型的自动转换。
如何判断某个 Interface 实例的实际类型
因为 Interface 实例存储的是实现者的值,即 Interface 实例实际上就是式实现者数据类型的实例。而且,一个 Interface 可以被多种不同数据类型多实现。所以,我们在编程的过程中,会遇见需要判断一个 Interface 实例的实际数据类型的情况。
Golang 中可以使用 Type assertions(类型断言)语句来进行判断,e.g. value, ok := em.(T)
,其中:
- em 是一个 Interface 实例。
- T 代表要进行断言的数据类型
- value 则是 Interface 的实现者。
- ok 是 bool 类型,表示断言的结果。
示例:
if t, ok := i.(*S); ok {
fmt.Println("s implements I", t)
}
如果需要区分多种类型,可以使用 switch 断言,更简单直接,这种断言方式只能在 switch 语句中使用。
示例:
switch t := i.(type) {
case *S:
fmt.Println("i store *S", t)
case *R:
fmt.Println("i store *R", t)
}
Empty Interface
不包含任何 Method 的 Interface,即:interface{},称之为 Empty Interface。
根据前文的定义:一个数据类型如果实现了一个 Interface 的所有 Methods 就称该数据类型实现了这个 Interface。那么,Empty Interface 由于没有包含任何 Method,所以可以认为所有的数据类型都实现了 interface{}。
也就是说,如果一个函数的形参为 interface{} 类型,那么这个形参可以接受任何数据类型的实例对象作为实参数。
示例:
func doSomething(i interface{}){
...
}
需要注意的是,上述例子中,虽然形参 i 可以接受任何数据类型的实参,但并不代表着 v 就是任何数据类型,在函数体中,v 仅仅是 interface{} 类型。
另外,需要注意 slice interface{} 类型的特殊之处,例如:
func printAll(vals []interface{}) { //1
for _, val := range vals {
fmt.Println(val)
}
}
func main(){
names := []string{"stanley", "david", "oscar"}
printAll(names)
}
编译上述代码会得到错误:cannot use names (type []string) as type []interface {} in argument to printAll.
说明 Golang 并没有隐式的将 slice []string 数据类型转换为 slice []interface,因为 interface{} 会占用 2Byte 的存储空间,一个 Byte 存储自身的 Methods 数据,一个 Byte 存储指向其存储值的指针,也就是 Interface 实例存储的实现者的值。
因而 slice []interface{} 其长度是固定的 N2,但是 []T 的长度是 Nsizeof(T),两种 slice 实际存储值的大小是有区别的,所以就无法完成自动数据类型转换了。但是我们可以手动地进行转换,即:显示的完成 Slice 长度匹配,例如:
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
Interface 与多态
在 Java、C++ 等静态类型语言中,在定义变量必须要显示声明其数据类型。但有些时候,如果我们希望它可以宽松一点,比如说我们用父类指针或引用去调用一个方法,在执行的时候,能够根据子类的类型去执行子类当中的方法。也就是说实现我们用相同的调用方式调出了不同结果或者是功能的情况,这种情况就叫做多态。
实现多态的技术称为:动态绑定(Dynamic Binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,在 Flash 界面下就是弹出 AS 3 帮助文档;在 Word 下就是弹出 Word 帮助;在 Windows 下就是弹出 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
再举个非常经典的例子,比如说猫、狗和人都是哺乳动物。这三个类都有一个 say 方法,但结果是:猫是喵喵叫,狗是汪汪叫,人类则是说话。
class Mammal {
public void say() {
System.out.println("do nothing")
}
}
class Cat extends Mammal{
public void say() {
System.out.println("meow");
}
}
class Dog extends Mammal{
public void say() {
System.out.println("woof");
}
}
class Human extends Mammal{
public void say() {
System.out.println("speak");
}
}
class Main {
public static void main(String[] args) {
List<Mammal> mammals = new ArrayList<>();
mammals.add(new Human());
mammals.add(new Dog());
mammals.add(new Cat());
for (Mammal mammal : mammals) {
mammal.say();
}
}
}
这三个类都是 Mammal 的子类,虽然我们用的是父类的引用来调用的方法,但是它可以自动根据子类的类型调用对应不同子类当中的方法,得到的结果是:
speak
woof
meow
简而言之,多态的作用同样是消除了 Method 与(父、子)数据类型之间的耦合关系,在父类当中定义方法,在子类中可以创建不同的实现,这就是抽象类和抽象方法的来源。
我们可以把 Mammal 做成一个抽象类,声明 say 是一个抽象方法。抽象类是不能直接创建实例的,只能创建子类的实例,并且抽象方法也不用实现,只需要标记好参数和返回就行了,具体的实现都在子类当中进行:
abstract class Mammal {
abstract void say();
}
Java Interface 的本质就是一个抽象类,与抽象类的区别在于 Interface 是只有抽象方法的抽象类。
interface Mammal {
void say();
}
Interface 的好处是很明显的,我们可以用 Interface(“父类”)的实例来调用所有实现了这个接口的类(“子类”)。如此的,Interface 和它的具体实现是一种要宽泛许多的继承关系,大大增加了灵活性。
而 Golang Interface 则做得更加彻底,因为 Golang 不是一门面向对象编程语言,所以 Golang 完全没有了 Interface 本身和实现者之间的继承关系。只要 Interface 中定义的 Method 能对应的上,那么就可以认为这个数据类型实现了这个接口。
type Dog struct{}
type Cat struct{}
type Human struct{}
func (d Dog) Say() {
fmt.Println("woof")
}
func (c Cat) Say() {
fmt.Println("meow")
}
func (h Human) Say() {
fmt.Println("speak")
}
func main() {
var m Mammal
m = Dog{}
m.Say()
m = Cat{}
m.Say()
m = Human{}
m.Say()
}
Interface 与 Duck Typing
对于 Duck Typing(鸭子类型)有一个简单的说明:“If it walks like a duck and it quacks like a duck, then it must be a duck”。
对于计算机而言,当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
计算机以此来判断 it 是否可以用于特定的目的。即:一个东西究竟是不是鸭子,取决于它能不能满足鸭子的工作。
在面向对象编程语言中,当某个地方(e.g. 函数的形参)需要符合某个条件的变量(e.g. 要求这个变量实现了某种方法)时,什么是判断这个变量是否 “符合条件” 的标准呢?如果这个标准是:这个变量的数据类型是否实现了这个要求的方法(并不要求显式地声明),那么这种语言的类型系统就可以称为 Duck Typing。
可见,Duck Typing 是一种编程风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类,而是由 “当前方法和属性的集合” 来决定。
简而言之,Duck Typing 将对象的语义和其继承的类进行了解偶,程序判断一个对象是声明并不是通过它的数据类型定义来判断的,而是判断它是否满足了某些特定的方法和属性定义,以此加大了编程的灵活度 —— 虽然我不继承自某个类,但我依然可以完成某些工作。
Duck Typing 多见于动态语言,例如:Python 等,因为动态语言不需要在定义一个变量的时候就声明其不可更改的数据类型,即:变量类型可被随意更改,所以在函数传递中支持灵活的类型传递,继而实现 “任务” 函数的定义。
Duck Typing 在静态语言中比较罕见,Golang Interface 就是 Duck Typing 的一种实现,因为 Golang 不是一种面向对象编程语言,同样没有类型一致的约束。
示例:
#!/usr/local/bin/python3
# coding=utf8
# 使用的对象和方法
class PsyDuck(object):
def gaga(self):
print("这是可达鸭")
# 使用的对象和方法
class DoningdDuck(object):
def gaga(self):
print("这是唐老鸭")
# 被调用的 “任务” 函数
def duckSay(func):
return func.gaga()
# 限制调用方式
if __name__ != '__main__':
print("must __main__")
# 实例化对象
duck = PsyDuck()
person = DoningdDuck()
# 调用函数
duckSay(duck)
duckSay(person)
示例:
package main
import "fmt"
// 定义一个鸭子接口。
// Golang 中的接口是一组方法的集合,可以理解为抽象的类型。它提供了一种非侵入式的接口。任何类型,只要实现了该接口中方法集,那么就属于这个类型。
type Duck interface {
Gaga()
}
// 假设现在有一个可达鸭类型。
type PsyDuck struct{}
// 可达鸭声明方法,满足鸭子会嘎嘎叫的特性。
func (pd PsyDuck) Gaga() {
fmt.Println("this is PsyDuck")
}
// 假设现在有一个唐老鸭类型。
type DonaldDuck struct{}
// 唐老鸭声明方法,满足鸭子会嘎嘎叫的特性。
func (dd DonaldDuck) Gaga() {
fmt.Println("this is DoningdDuck")
}
// 要调用的 “任务” 函数,负责执行鸭子能做的事情,注意这里的参数,有类型限制为 Duck 接口。
func DuckSay(d Duck) {
d.Gaga()
}
func main() {
fmt.Println("duck typing")
//实例化对象
var pd PsyDuck //可达鸭类型
var dd DonaldDuck //唐老鸭类型
//调用方法
//pd.Gaga() // 因为可达鸭实现了所有鸭子的函数,所以可以这么用。
//dd.Gaga() // 因为唐老鸭实现了所有鸭子的函数,所以可以这么用。
DuckSay(pd) // 因为可达鸭实现了所有鸭子的函数,所以可以这么用。
DuckSay(dd) // 因为唐老鸭实现了所有鸭子的函数,所以可以这么用。
}
Interface 与泛型编程
In the simplest definition, generic programming is a style of computer programming in which algorithm are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.
– From Wikipedia.
简而言之,泛式编程,就是编写的代码不针对某一特定类型(比如适用于 Int,不适用于 String)生效,而是面向大部分数据类型都可以工作的。
示例:
#include <algorithm>
#include <vector>
int main() {
std::vector<int> A{3,1,2,4,5};
std::vector<string> B{"golang", "I", "am"};
std::sort(A.begin(), A.end()); // after this, A={1,2,3,4,5}
std::sort(B.begin(), B.end()); // after this, B={"I", "am", "golang"}
return 0;
}
上述的 sort 函数就是一个泛式编程的例子,它可以处理 Int 和 String 类型的变量。
总所周知,Golang 目前是没有泛式编程的,但可以通过 Interface 特性来实现,虽然不尽完美。Golang 中也提供了 sort 函数:
package sort
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
...
// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
// Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
n := data.Len()
maxDepth := 0
for i := n; i > 0; i >>= 1 {
maxDepth++
}
maxDepth *= 2
quickSort(data, 0, n, maxDepth)
}
可以看见,package sort 中定义了一个 Interface,包含了 Len、Less、Swap 方法,这个 Interface 类型作为 Sort 函数的形参。如果我们要使用 Sort,就只需要定义这个 Interface 的实现者就可以了。
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s: %d", p.Name, p.Age)
}
// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
fmt.Println(people)
sort.Sort(ByAge(people))
fmt.Println(people)
}