zoukankan      html  css  js  c++  java
  • Golang的“面向对象”

    在网上看到有位仁兄解释的很到位很经典,所以记录下来。

    Go中的“对象”

    要探讨Go语言中的对象,我们先搞清楚一个问题:

    Go语言有对象吗?

    从语法上来说,

    • Go中没有类(Classes)
    • Go中没有“对象”(Objects)

    到底什么是对象?

    对象是一种抽象的数据类型,拥有状态(数据)和行为(代码)。 —— Steve Francia

    在Go语言中,我们这样声明一个类型:

    类型声明(Struct)
    type Rect struct {
        width  int
        height int
    }
    然后我们可以给这个Struct声明一个方法
    func (r *Rect) Area() int {
        return r.width * r.height
    }
    用起来就像这样
    func main() {
        r := Rect{ 10, height: 5}
        fmt.Println("area: ", r.Area())
    }

    我们不光可以声明结构体类型,我们可以声明任何类型。比如一个切片:

    类型声明(Slice)
    type Rects []*Rect
    同样也可以给这个类型声明一个方法
    func (rs Rects) Area() int {
        var a int
        for _, r := range rs {
            a += r.Area()
        }
        return a
    }
    用起来
    func main() {
        r := &Rect{ 10, height: 5}
        x := &Rect{ 7, height: 10}
        rs := Rects{r, x}
        fmt.Println("r's area: ", r.Area())
        fmt.Println("x's area: ", x.Area())
        fmt.Println("total area: ", rs.Area())
    }

    我们甚至可以声明一个函数类型

    类型声明(Func)
    type Foo func() int
    同样的,给这个(函数)类型声明一个方法
    func (f Foo) Add(x int) int {
        return f() + x
    }
    然后用起来
    func main() {
        var x Foo
    
        x = func() int { return 1 }
    
        fmt.Println(x())
        fmt.Println(x.Add(3))
    }

    通过上边的例子,这样看来,其实

    Go有“对象”

    那么我们来看看

    “面向对象”的Go

    如果一种语言包含对象的基本功能:标识、属性和特性,则通常认为它是基于对象的。
    如果一种语言是基于对象的,并且具有多态性和继承性,那么它被认为是面向对象的。 —— Wikipedia

    第一条,我们在上边的例子看到了,go中的type declaration其实满足了Go语言是基于对象的。那么,

    Go是基于对象的,它是面向对象的吗?

    我们来看看关于第二条,继承性和多态性

    继承

    • 提供对象的复用
    • 类是按层级创建的
    • 继承允许一个类中的结构和方法向下传递这种层级

    Go中实现继承的方式

    • Go明确地避免了继承
    • Go严格地遵循了符合继承原则的组合方式
    • Go中通过嵌入类型来实现组合

    组合

    • 提供对象的复用
    • 通过包含其他的对象来声明一个对象
    • 组合使一个类中的结构和方法被拉进其他类中

    继承把“知识”向下传递,组合把“知识”向上拉升 —— Steve Francia

    嵌入类型
    type Person struct {
        Name string
        Address
    }
    
    type Address struct {
        Number string
        Street string
        City   string
        State  string
        Zip    string
    }
    给被嵌入的类型声明一个方法
    func (a *Address) String() string {
        return a.Number + " " + a.Street + "
    " + a.City + ", " + a.State + " " + a.Zip + "
    "
    }
    使用组合字面量声明一个Struct
    func main() {
        p := Person{
            Name: "Steve",
            Address: Address{
                Number: "13",
                Street: "Main",
                City:   "Gotham",
                State:  "NY",
                Zip:    "01313",
            },
        }
    }
    跑起来试试
    func main() {
        p := Person{
            Name: "Steve",
            Address: Address{
                Number: "13",
                Street: "Main",
                City:   "Gotham",
                State:  "NY",
                Zip:    "01313",
            },
        }
        fmt.Println(p.String())
    }

    升级

    • 升级会检查一个内部类型是否能满足需要,并“升级”它
    • 内嵌的数据域和方法会被“升级”
    • 升级发生在运行时而不是声明时
    • 被升级的方法被认为是符合接口的
    升级不是重载
    func (a *Address) String() string {
        return a.Number + " " + a.Street + "
    " + a.City + ", " + a.State + " " + a.Zip + "
    "
    }
    
    func (p *Person) String() string {
        return p.Name + "
    " + p.Address.String()
    }

    外部结构的方法和内部结构的方法都是可见的

    func main() {
        p := Person{
            Name: "Steve",
            Address: Address{
                Number: "13",
                Street: "Main",
                City:   "Gotham",
                State:  "NY",
                Zip:    "01313",
            },
        }
        fmt.Println(p.String())
        fmt.Println(p.Address.String())
    }
    这两个类型仍然是两个不同的类型
    func isValidAddress(a Address) bool {
        return a.Street != ""
    }
    
    func main() {
        p := Person{
            Name: "Steve",
            Address: Address{
                Number: "13",
                Street: "Main",
                City:   "Gotham",
                State:  "NY",
                Zip:    "01313",
            },
        }
    
        // 这里不能用 p (Person类型) 作为 Address类型的IsValidAddress参数
        // cannot use p (type Person) as type Address in argument to isValidAddress
        fmt.Println(isValidAddress(p))
        fmt.Println(isValidAddress(p.Address))
    }
    升级不是子类型

    多态

    为不同类型的实体提供单一接口

    通常通过泛型、重载和/或子类型实现

    Go中实现多态的方式

    • Go明确避免了子类型和重载
    • Go尚未提供泛型
    • Go的接口提供了多态功能

    接口

    • 接口就是(要实现某种功能所需要提供的)方法的列表
    • 结构上的类型 vs 名义上的类型
    • “如果什么东西能做这件事,那么就可以在这使用它”
    • 惯例上就叫它 某种东西

    Go语言采用了鸭式辩型,和JavaScript类似。鸭式辩型的思想是,只要一个动物走起路来像鸭子,叫起来像鸭子,那么就认为它是一只鸭子。
    也就是说,只要一个对象提供了和某个接口同样(在Go中就是相同签名)的方法,那么这个对象就可以当做这个接口来用。并不需要像Java中一样显式的实现(implements)这个接口。

    接口声明
    type Shaper interface{ 
        Area() int 
    }
    然后把这个接口作为一个参数类型
    func Describe(s Shaper) {
        fmt.Println("Area is: ", s.Area())
    }
    这样用
    func main() {
        r := &Rect{ 10, height: 5}
        x := &Rect{ 7, height: 10}
        rs := &Rects{r, x}
        Describe(r)
        Describe(x)
        Describe(rs)
    }

    “如果你可以重新做一次Java,你会改变什么?”
    “我会去掉类class,” 他回答道。
    在笑声消失后,他解释道,真正的问题不是类class本身,而是“实现”的继承(类之间extends的关系)。接口的继承(implements的关系)是更可取的方式。
    只要有可能,你就应该尽可能避免“实现”的继承。
    —— James Gosling(Java之父)

    Go的接口是基于实现的,而不是基于声明的

    这也就是上边所说的鸭式辩型

    接口的力量

    io.Reader
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    • Interface
    • Read方法读取最多len(p) bytes的数据到字节数组p中
    • 返回读取的字节数和遇到的任何error
    • 并不规定Read()方法如何实现
    • 被诸如 os.File, bytes.Buffer, net.Conn, http.Request.Body等等使用
    io.Writer
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    • Interface
    • Write方法写入最多len(p) bytes的数据到字节数组p中
    • 返回写入的字节数和遇到的任何error
    • 并不规定Write()方法如何实现
    • 被诸如 os.File, bytes.Buffer, net.Conn, http.Request.Body等等使用
    io.Reader 使用
    func MarshalGzippedJSON(r io.Reader, v interface{}) error {
        raw, err := gzip.NewReader(r)
        if err != nil {
            return err
        }
        return json.NewDecoder(raw).Decode(&v)
    }
    读取一个json.gz文件
    func main() {
        f, err := os.Open("myfile.json.gz")
        if err != nil {
            log.Fatalln(err)
        }
        defer f.Close()
        m := make(map[string]interface{})
        MarshalGzippedJSON(f, &m)
    }
    实用的交互性
    • Gzip.NewReader(io.Reader) 只需要传入一个io.Reader接口类型即可
    • 在files, http requests, byte buffers, network connections, ...任何你创建的东西里都能工作
    • 在gzip包里不需要任何特殊处理。只要简单地调用Read(n),把抽象的部分留给实现者即可
    将 http response 写入文件
    func main() {
        resp, err := http.Get("...")
        if err != nil {
            log.Fatalln(err)
        }
        defer resp.Body.Close()
        out, err := os.Create("filename.ext")
        if err != nil {
            log.Fatalln(err)
        }
        defer out.Close()
        io.Copy(out, resp.Body) // out io.Writer, resp.Body io.Reader 
    }
  • 相关阅读:
    扩展的局域网
    参数估计
    以太网的 MAC 层
    poj 1523Tarjan算法的含义——求取割点可以分出的连通分量的个数
    tarjan算法--求解无向图的割点和桥
    spfa负环判断
    codeforce 489d bfs分层处理
    并查集优化——压缩路径——秩优化
    SPFA_queue_链式前向星最短路 & HDU2433
    POJ3046选蚂蚁创建集合_线性DP
  • 原文地址:https://www.cnblogs.com/xhhgo/p/10912803.html
Copyright © 2011-2022 走看看