GO语言学习笔记六(⾯向对象编程)

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 接⼝与其他主要编程语言的差异

  1. 接口为⾮⼊侵性,实现不依赖于接口定义
  2. 所以接口的定义可以包含在接⼝使用者包内

Go的接口变量结构

GO语言学习笔记六(⾯向对象编程)_第1张图片
接口变量

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的方法。定义了两个结构体分别是GoProgrammerJavaProgrammer,他们各自实现了与接口同名的WriteHelloWorld方法,注意和接口定义的方法同名。这样定义接口变量后,就可以初始化成具体实现的对象,也就是GoProgrammerJavaProgrammer,直接调用接口定义的方法。下面是一张更清晰的结构图:

GO语言学习笔记六(⾯向对象编程)_第2张图片
多态

你可能感兴趣的:(GO语言学习笔记六(⾯向对象编程))