• 不一样的go语言-不同的OO


    前言

      go语言因为产生时代的原因,大神们在设计go时,不得不考虑业界的流行趋势(编程理念),使得go既可以面向过程编程,也可以面向对象编程。这里不探讨两者的优劣,存在即是合理,面向过程编程经久不衰,而面向对象当今红红火火。如题所示,本文只计划聊一聊go的面向对象编程。

    语法

    面向对象离不开其三大特性,封装、继承、多态。那么go在语法层面是怎么实现这个的呢?

    先来看一下封装,示例如下:

    type House struct {//House是大写开头,公开类型,相当于java的public class
        Name string //大写表示公开属性,即java的public
        height float32 //小写表示私有属性,即java的private
        width float32
    }
    
    //方法名大写表示公开方法,即java的public方法
    func (h *House) Height() float32 {
        return h.height
    }
    
    func (h *House) Width() float32 {
        return h.width
    }
    
    func (h *House) Area() float32 {
        return h.height * h.width
    }
    

    从上述代码可以看出,go语法的封装完全依赖属性名或方法名开头字母的大小写来声明,大写表示公开,小写表示私有。这无不体现go语言大道至简的特点。但在go语言中,它不叫封装,它叫导出。导出的类型、属性、方法在包外可见。

    再看一下继承,示例如下:

    package main
    
    import "fmt"
    
    func main() {
        wh := WhiteHouse{House: House{height: 1.0,  2.0, name: "abc"}, Building: Building{name: "xyz"}}
        //fmt.Println(wh.getName()) //此句会报多义的错误
        fmt.Println(wh.height) //获得了House的属性
        fmt.Println(wh.House.GetName()) //需要显式地指定调用哪个组合类型的方法
        fmt.Println(wh.Building.GetName())
    }
    
    type House struct {
        height float32
        width float32
        name string
    }
    
    func (h *House) GetName() string {
        return h.name
    }
    
    type Building struct {
        name string
    }
    
    func (b *Building) GetName() string {
        return b.name
    }
    
    type WhiteHouse struct {
        House //组合方式的继承,获得了House的属性及方法
        Building //组合方式的继承,获得了Building的属性及方法
    }
    
    

    所谓继承,宽泛的理解应该是获得已有类型的属性或方法,从而达到代码简化及复用的目的。而往具体了说则是具有一定从属关系、父子关系或等级关系的对象之间的一种强关联的关系,目的不仅仅是为获得与复用,而是同时声明了一种关系。

    关于继承,以下这几种语言的实现是最具有代表性的。

    语言 继承方式 关键词 说明
    java 单继承 extends, interface 规避类的复杂性及多义性;但仍可以通过接口实现多继承,可见下面的代码示例
    python 多继承 MRO,菱形继承,C3算法 -
    c++ 多继承 虚基类, virtual -
    javascript 原型链 prototype,constructor -
    go 组合继承 embedded, duck -

    java多继承示例:

    public class Z {
        public void z() {
            
        }
    }
    public interface A {
    	void x();
    }
    
    public interface B {
    	void x();
    }
    
    //AB将会继承得到Z的方法z
    //通过实现A、B两个接口的同名方法,得到自己专属的x方法实现,不存在多义的问题
    public class AB extends Z implements A, B {
        @Override
    	public void x() {
    		
    	}
    }
    
    public interface C {
    	default void y() {
    		System.out.println("C->y");
    	}
    }
    
    public interface D {
    	default void y() {
    		System.out.println("D->y");
    	}
    }
    
    //此处会报编译错误,必须覆盖y方法。这是java从语言规范层面规避多义问题
    public class CD extends Z implements C, D {
    
    }
    
    

    javascript多继承示例:

    //以下代码摘自大神阮一峰《Javascript面向对象编程(二):构造函数的继承》一文
    //在此拜谢
    function Animal() {
        this.species = "动物"
    }
    
    function Cat(name, color) {
        this.name = name;
      this.color = color;
    }
    
    //从这个函数里可以得知,javascript的继承有点简单粗暴的意思,相当于直接复制。
    function Extend(Child, Parent) {
        var F = function{};
        F.prototype = Parent.prototype;
        Child.prototype = F.prototype;
        Child.prototype.constructor = Child;
        Child.uber = Parent.prototype;
    }
    
    Extend(Cat, Animal);
    var cat1 = new Cat("大毛","黄色");
    alert(cat1.species); // 动物
    
    

    而python与C++都支持多继承,只不过两者对多继承多义的处理不一样。前者是通过MRO解决,后者则是通过virtual关键字解决。

    通过比较可以看出,go语言的继承是一种弱关系的继承,专心完成属性与方法的获得,以及代码复用。而这种继承方式也导致不支持继承类型间的转换,即上述代码中的WhiteHouse是不能转换为House或Building类型的。

    在go中,如果要表达类似的强关系,只能通过interface来完成。但请注意,go的interface的概念依然不同于上述的java、c++语言,它不要求"实现了某个接口的类必须要实现接口中未实现的方法",而只是轻松写意地说道:"你只要实现这些个方法,那么你就实现了这些个接口"。 这两个说法有什么不同呢?虽然可以很明显地感觉后者的自由度更大,且看下文分解。

    最后是多态。多态是同一个行为具有多个不同表现形式或形态的能力。体现在编程语言中就是同一个接口,在运行时因为不同的实例而执行不同操作。示例如下:

    pckage main
    
    import "fmt"
    
    func main() {
        //同一个对象g,不同的实例会有不同的表现(输出)
        g := Machinegun{}
        g.Fire()
        g = Handgun{}
        g.Fire()
    }
    
    type Gun interface {
        Fire()
    }
    
    type Machinegun struct {
        
    }
    
    func (m *Machinegun) Fire() {
        fmt.Println("machinegun fire")
    }
    
    type Handgun struct {
        
    }
    
    func (h *Handgun) Fire() {
        fmt.Println("handgun fire")
    }
    
    

    在多态这一点上,存在接口概念的语言都有大体相同的代码写法。只是go还是一种支持鸭子类型的语言,尽管没有python这种动态类型语言做得那么淋漓尽致。请上代码如下:

    python代码示例:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    
    
    class Animal(object):
        def __init__(self, name):
            self.name = name
    
        def walk(self):
            print(self.name + "在散步")
    
    
    class Cat(Animal):
        pass
    
    
    class Dog(Animal):
        pass
    
    
    class Abc(object):
        def walk(self):
            print("abc" + "在走路")
    
    
    def hello(animal):
        animal.walk()
    
    if __name__ == "__main__":
        c = Cat("小猫")
        hello(c)
        d = Dog("小狗")
        hello(d)
        
        //鸭子类型在这里体现得相当完美
        //Abc并没有继承Animal,但却可以作为hello方法的参数
        //而在静态语言中,就必须传递Animal的子类
        abc = Abc()
        hello(abc)
    

    所谓鸭子类型,维基百科解释为:duck typing in computer programming is an application of the duck test — "If it walks like a duck and it quacks like a duck, then it must be a duck"。

    鸭子类型得益于实现时不测试方法或函数中参数的类型,而依赖于文档以及清晰的代码和测试来保证正确使用,当然如果文档不完善,就只能靠查看代码才知道如何使用,这也是其缺点。

    同时,鸭子类型使得Animal即使在后续的版本中增加方法,而旧的实现类仍未实现该新方法,升级版本后,也不会影响已有代码的使用。而这一点在以前的java中是做不到的,因而直接导致java 8接口中default语法的出现。

    鸭子类型可以使得不用太关注对象类型,而转而关心对象的行为或能力。

    那么在go中,鸭子类型是如何体现的呢?

    package main
    
    import "fmt"
    
    func main() {
    	c := &Cat{}
    	hello(c)
    
    	abc := &Abc{}
    	hello(abc)
    }
    
    func hello(animal interface{})  {
    	animal.(Animal).walk()
    }
    
    type Animal interface {
    	walk()
    	//run()
    }
    
    type Cat struct {
    
    }
    
    func (c *Cat) walk() {
    	fmt.Println("小猫在散步")
    }
    
    type Abc struct {
    
    }
    
    func (a *Abc) walk() {
    	fmt.Println("abc在走路")
    }
    
    

    示例中的hello方法,因为interface{}神一般的存在,使得作为静态类型的go语言,可以模拟动态语言的写法。但似乎只是go真的只是鸭子类型语言而已,真的只是实现了鸭子类型的概念而已,其并没有动态语言的那种关于鸭子类型灵活性与自由度。

    请关注公众号

    不一样的go语言

  • 相关阅读:
    Linux内存管理 -- /proc/{pid}/smaps讲解
    link hub(other)
    牛客项目平台管家 | xie_note 学习笔记整理📚 项目来源:https://github.com/Making-It/note ,已获得授权转载
    【Linux】C++后台开发面试
    C++ 后台开发面试时一般考察什么?
    Linux C/C++ 学习路线(已拿腾讯、百度 offer)2
    C++路线图
    【转】C++后台开发校招面试常见问题
    【转】Linux C/C++ 学习路线(已拿腾讯、百度 offer)
    学习经验总结|C++后台开发/云计算方向,offer收割机的学习路线
  • 原文地址:https://www.cnblogs.com/laud/p/go_oo.html
Copyright © 2020-2023  润新知