Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object- oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general.
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
来自Go语言官网的解释:是又不是。毕竟Go是不支持继承的,接口的实现用的也是类似于Duck Type。下面会谈到Go如何类似于继承的实现。
封装数据和行为
结构体定义
type Employee struct {
Id string
Name string
Age int
}
实例创建及初始化
e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}
e2 := new(Employee) //注意这里返回的引用/指针,相当于 e := &Employee{}
e2.Id = "2" //与其他主要编程语言的差异:通过实例的指针访问成员不需要使用-> e2.Age = 22
e2.Name = "Rose"
⾏为(⽅法)定义
//第⼀种定义⽅式在实例对应⽅法被调⽤用时,实例的成员会进行值复制
func (e Employee) String() string {
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
//通常情况下为了避免内存拷⻉我们使⽤第⼆种定义⽅式
func (e *Employee) String() string {
return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}
建议用第二种方式定义行为(方法),因为用第一种会出现值的复制。值复制可以用代码打印指针就能发现
定义交互协议
先看一段java接口的实现
//Programmer.java
public interface Programmer {
String WriteCodes() ;
}
//GoProgrammer.java
public class GoProgrammer implements Programmer {
@Override
public String WriteCodes() {
return "fmt.Println(\"Hello World\")";
}
}
//Task.java
public class Task{
public static void main(String[] args) {
Programmer prog = new GoProgrammer();
String codes = prog.WriteCodes();
System.out.println(codes);
}
}
Go语言的实现,利用的是Duck Type式接口实现
什么是鸭子类型
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "fmt.Println(\"Hello World\")"
}
func TestClient(t *testing.T) {
var p Programmer
p = new(GoProgrammer)
t.Log(p.WriteHelloWorld())
}
Go 接⼝与其他主要编程语言的差异
- 接口为⾮⼊侵性,实现不依赖于接口定义
- 所以接口的定义可以包含在接⼝使用者包内
Go的接口变量结构
Coder是接口名,GoProgrammer是接口的实现(图中接口定义方式和上面代码有所差异,两种方式都可以),prog是变量。prog初始化后有两部分,第一部分是类型,实现接口的类型,第二部分是数据,真正实现接口的实例。
组合(“继承”)
"Go语言的面向对象机制与一般语言不同。 它没有类层次结构, 甚至可以说没有类; 仅仅通过组合( 而不是继承) 简单的对象来构建复杂的对象。" -- 《Go语言圣经》
带引号是因为它并不是真正意义上的继承。继承是面向对象的重要特性,Go并不支持继承,但可以通过组合来类似的实现。
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
p *Pet
}
func (d *Dog) Speak() {
d.p.Speak()
}
func (d *Dog) SpeakTo(host string) {
d.p.SpeakTo(host)
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("wang!") //打印结果 ... wang!
}
复合并不是真正意义上的继承,继承是直接在子类就拥有父类的方法,但是这里并不是直接拥有,一切尽在上面代码中,懂得继承的同学应该一目了然啦。
匿名组合(匿名嵌套)
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
//此为重点
type Dog struct {
Pet
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("wang!") //打印结果 ... wang!
}
用匿名组合是一种更佳的实现伪继承的方式,如上述代码定义后dog直接可以使用Pet中SpeakTo的方法,那这是真正的继承了父类的方法了吗?那我们来试验下重写父类方法,看Dog调用的时候是否被修改了Pet中SpeakTo的方法。
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
Pet
}
func (d *Dog) Speak() {
fmt.Print("wang wang!")
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("wang!")//打印结果 ... wang!
}
大家可以看到上述代码中Dog自己实现了Speak的方法,如果它满足继承的特性,那在调用SpeakTo的时候应该执行的是Dog自己的Speak方法而不是Pet,但实际情况是仍旧执行的是Pet中的Speak方法,因为它仍旧不是继承!
多态
Go语言没有继承,那我们如何实现多态?
type Code string
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
return "System.out.Println(\"Hello World!\")"
}
func writeFirstProgram(p Programmer) {
fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T) {
goProg := &GoProgrammer{}
javaProg := new(JavaProgrammer)
writeFirstProgram(goProg)
writeFirstProgram(javaProg)
}
/*
输出结果:
*polymorphism.GoProgrammer fmt.Println("Hello World!")
*polymorphism.JavaProgrammer System.out.Println("Hello World!")
*/
仍旧是很重要的Duck Type的概念,也是通过Duck Type实现了多态。大家可以看到定义了一个Programmer接口,接口中定义了WriteHelloWorld的方法。定义了两个结构体分别是GoProgrammer和JavaProgrammer,他们各自实现了与接口同名的WriteHelloWorld方法,注意和接口定义的方法同名。这样定义接口变量后,就可以初始化成具体实现的对象,也就是GoProgrammer和JavaProgrammer,直接调用接口定义的方法。下面是一张更清晰的结构图: