Go语言中的接口
- 接口有点像是定义,描述了必须具备的方法集合。通过观察接口的代码样例中,Go语言中的接口并不是非常复杂,但是为代码重用提供了高效的实现方式。
- 不同于Java中显式的接口定义,Go中的接口是隐式定义,另外Java中的接口可以定义属性,而Go中的接口则只包含了方法的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // Interface
interface Animal {
public String name = "animal";
public void animalSound(); // interface method (does not have a body)
public void sleep(); // interface method (does not have a body)
}
// Pig "implements" the Animal interface
class Pig implements Animal {
public void animalSound() {
// The body of animalSound() is provided here
System.out.println("The pig says: wee wee");
}
public void sleep() {
// The body of sleep() is provided here
System.out.println("Zzz");
}
}
class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // Create a Pig object
myPig.animalSound();
myPig.sleep();
}
}
|
如果修改为Go语言实现,则为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| type Animal interface {
animalSound();
sleep();
}
type Pig struct {
name string
}
func (p Pig) animalSound() {
fmt.Println("The pig says: wee wee")
}
func (p Pig) sleep() {
fmt.Println("Zzz")
}
func main() {
p := Pig{"pig"}
p.animalSound()
p.sleep()
}
|
与类、继承之间的关系
- stuct和inteface构建了Go语言中的面向对象
- struct相当于其他语言的类,但是并不完全等同,所以不要完全套用传统类的思想来进行开发。在结构体的学习中,我们知道Go语言中并没有像其他语言提供类(Class)的定义方式,而是通过结构体及函数的Reciever间接的使用。本质上,Go中的类就是属性和方法的组合。
- interface相当于实现了动态语言中的多态(Duck Typing)
- Duck Typing是动态类型中常用的一种多态方式,为了便于理解,这里重温了Python Duck Typing的实现方法。
- 多态的引入提高了代码的灵活度,有些变量类型可以在编译或运行时决定
- Go语言里并没有设计诸如虚函数、纯虚函数、继承、多重继承的概念,但是仍然通过struct和interface实现类似继承和多态的特性,使代码能够可重用且优雅,降低传统面向对象语言开发的复杂程度。
Python中的Duck Typing
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
鸭子类型(英语:duck typing)在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试。
简单来说,不关注鸭子,而关注是否会呱呱叫。
在常规类型中,我们能否在一个特定场景中使用某个对象取决于这个对象的类型,而在鸭子类型中,则取决于这个对象是否具有某种属性或者方法——即只要具备特定的属性或方法,能通过鸭子测试,就可以使用。
这不禁让我让我想到两句谚语:狗拿耗子多管闲事和黑猫白猫能抓到耗子就是好猫。显然,这里要表达的意思和Duck Typing类似:只要够能抓住耗子,狗也可以是只猫。
不同于强类型语言,在实际使用Duck Typing时,只需要对象拥有这个方法即可调用,而不需要关心这些对象是否为同一类或继承类,比如Python内置的len()方法,本质上是在类中实现了__len__方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| class DuckTypingDemo:
def __len__(self):
return 4096
duck_typing = DuckTypingDemo()
print("DuckTypingDemo len =", len(duck_typing))
# DuckTypingDemo len = 4096
my_str = "hello, world"
print("String len =", len(my_str))
# String len = 12
my_list = [1, 2, 3, 4, 5, 6]
print("List len =", len(my_list))
# List len = 6
my_dict = {"one" : 1, "two" : 2}
print("Dict len =", len(my_dict))
# Dict len = 2
try:
my_int = 6
print("int class =", my_int.__class__.__name__)
print("int len =", len(my_int))
except Exception as e:
print("Failed Reason:", e)
# int class = int
# Failed Reason: object of type 'int' has no len()
|
当我们为自定义类DuckTypingDemo定义了__len__方法时,就可以调用len方法,同理在内置函数中,对于string, list和dict也可以通过len获取相应类型的长度。
而对于没有定义__len__的int类型来说,就无法使用len方法获取其长度了。当然我们可以通过继承int,实现其__len__后,就可以使用len()方法了。
1
2
3
4
5
6
7
8
| class IntWithLen(int):
def __len__(self):
return 1024
i = IntWithLen()
print("Int with length:", len(i))
# Int with length: 1024
|
命名规范
- 根据惯例,接口名称后缀一般为er,像Reader, Writer, Formatter等
- 避免混淆,例如:Read、Write、Close、Flush、String
- 如果你定义的内容确实与这些类型相同,需要使用相同的名称和签名,例如字符转换方法是String而不是ToString