美文网首页Golang 入门资料+笔记
GO语言学习笔记六(⾯向对象编程)

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

作者: Jabir_Zhang | 来源:发表于2019-07-19 00:58 被阅读16次

    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的接口变量结构

    接口变量

    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语言学习笔记六(⾯向对象编程)

        本文链接:https://www.haomeiwen.com/subject/kbjflctx.html