Golang面向对象思想

简介

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样;
没有封装、继承、多态这些概念,但同样通过别的方式实现这些特性;

封装:通过方法实现;
继承:通过匿名字段实现;
多态:通过接口实现;

  • 抽象
    把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象

封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作

  • 封装的理解和好处
  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理(Age)
  • 如何体现封装
  1. 对结构体中的属性进行封装
  2. 通过方法,包 实现封装
  • 封装的实现步骤
  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
    func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
        //加入数据验证的业务逻辑
        var.字段 = 参数
    }
  1. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
    func (var 结构体类型名) GetXxx() {
        return var.age;
    }
  • 特别说明:
    在 Golang 开发中并没有特别强调封装,这点并不像 Java. 所以提醒学过 java 的朋友,不用总是用 java 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的.

继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个定义相同属性和方法的匿名结构体即可。
在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

  • 嵌套匿名结构体的基本语法
    type Goods struct {
        Name string
        Price int
    }
    type Book struct {
        Goods
        //这里就是嵌套匿名结构体 Goods
        Writer string
    }
  • 继承给编程带来的便利
  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了
  • 深入细节
  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
  2. 匿名结构体字段访问可以简化
        type A struct {
            Name string
            age int
        }
    
        func (a *A) SayOk() {
            fmt.Println("A SayOk", a.Name)
        }
    
        func (a *A) hello() {
            fmt.Println("A hello", a.Name)
        }
    
        type B struct {
            A 
        }
    
        func main() {
            var b B
            <!-- 
            b.A.Name = "tom"
            b.A.age = 19
            b.A.SayOk()
            b.A.hello() 
            -->
    
            //上面的写法可以简化
            b.Name = "smith"
            b.age = 20
            b.SayOk()
            b.hello()
        }
    
        对上面的代码小结
        1. 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name
        2. 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段
        3. 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找..如果都找不到就报错.
    
  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
    type A struct {
        Name string
        age int
    }

    func (a *A) SayOk() {
        fmt.Println("A SayOk", a.Name)
    }

    func (a *A) hello() {
        fmt.Println("A hello", a.Name)
    }

    type B struct {
        A
        Name string 
    }

    func (b *B) SayOk() {
        fmt.Println("B SayOk", b.Name)
    }

    func main() {
        var b B
        b.Name = "jack" // ok
        b.A.Name = "scott"
        b.age = 100  //ok
        b.SayOk()  // B SayOk  jack
        b.A.SayOk() //  A SayOk scott
        b.hello() //  A hello  "scott"
    }
  1. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
    type A struct {
        Name string
        age int
    }
    type B struct {
        Name string
        Score float64
    }
    type C struct {
        A
        B
    }
    func main() {
        var c C
        //如果c 没有Name字段,而A 和 B有Name, 这时就必须通过指定匿名结构体名字来区分
        //所以 c.Name 就会包编译错误, 这个规则对方法也是一样的!
        //c.Name = "tom"     error
        c.A.Name = "tom"
    }
  1. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
    type A struct {
        Name string
        age int
    }
    type B struct {
        a A //有名结构体
    }

    func main() {
        //如果B 中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字
        //比如 b.a.Name 
        var b B 
        b.a.Name = "jack"
    }
  1. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    type Goods struct {
        Name string
        Price float64
    }

    type Brand struct {
        Name string
        Address string
    }

    type TV struct {
        Goods
        Brand	
    }

    type TV2 struct {
        *Goods
        *Brand	
    }

    type Monster struct  {
        Name string
        Age int
    }

    type E struct {
        Monster
        int
        n int
    }

    func main() {
        //类型一
        //方式一
	    tv := TV{ Goods{"电视机001", 5000.99},  Brand{"海尔", "山东"}, }

        //方式二
        tv2 := TV{ 
                Goods{
                    Price : 5000.99,
                    Name : "电视机002", 
                },  
                Brand{
                    Name : "夏普", 
                    Address :"北京",
                }, 
            }

        //类型二
        //方式一
        tv3 := TV2{ &Goods{"电视机003", 7000.99},  &Brand{"创维", "河南"}, }

        //方式二
        tv4 := TV2{ 
                &Goods{
                    Name : "电视机004", 
                    Price : 9000.99,
                },  
                &Brand{
                    Name : "长虹", 
                    Address : "四川",
                }, 
            }

    }
  1. 匿名字段时基本数据类型的使用
    type Monster struct  {
        Name string
        Age int
    }

    type A struct {
        Monster
        int
        n int
    }

    func main() {
        var e E
        e.Name = "狐狸精"
        e.Age = 300
        e.int = 20
        e.n = 40
    }

    说明
    1. 如果一个结构体有 int 类型的匿名字段,就不能第二个。
    2. 如果需要有多个 int 的字段,则必须给 int 字段指定名字
  • 多重继承
    假如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。
    type Goods struct {
        Name string
        Price float64
    }

    type Brand struct {
        Name string
        Address string
    }

    type TV struct {
        Goods
        Brand	
    }

    func main() {
        //演示访问Goods的Name
        fmt.Println(tv.Goods.Name)
        fmt.Println(tv.Price) 
    }

    说明:
    1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
    2. 为了保证代码的简洁性,建议大家尽量不使用多重继承

接口

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

  • 声明
    type 接口名 interface {
        method1(参数列表)返回値列表
        method2(参数列表)返回値列表
    }

    1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
    2. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字
  • 注意事项和细节
  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
  2. 接口中所有的方法都没有方法体,即都是没有实现的方法。
  3. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
  6. 一个自定义类型可以实现多个接口
  7. Golang 接口中不能有任何变量
  8. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现。
  9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  10. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。
  • 实现接口 vs 继承
  1. 实现接口可以看作是对 继承的一种补充
  2. 接口和继承解决的解决的问题不同
    继承的价值主要在于:解决代码的复用性和可维护性。
    接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
  3. 接口比继承更加灵活
    接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
  4. 接口在一定程度上实现代码解耦

多态

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

  • 两种形式
  1. 多态参数
    type Usb interface {
        //声明了两个没有实现的方法
        Start() 
        Stop()
    }

    type Phone struct {

    }  
    func (p Phone) Start() {
        fmt.Println("手机开始工作。。。")
    }
    func (p Phone) Stop() {
        fmt.Println("手机停止工作。。。")
    }

    type Camera struct {

    }
    func (c Camera) Start() {
        fmt.Println("相机开始工作~~~。。。")
    }
    func (c Camera) Stop() {
        fmt.Println("相机停止工作。。。")
    }

    type Computer struct {

    }
    func (c Computer) Working(usb Usb) {
        usb.Start()
        usb.Stop()
    }

    func main() {
        computer := Computer{}
        phone := Phone{}
        camera := Camera{}

        computer.Working(phone)
        computer.Working(camera) //
    }

  1. 多态数组
    type Usb interface {
        Start()
        Stop()
    }

    type Phone struct {
        name string
    }
    func (p Phone) Start() {
        fmt.Println("手机开始工作。。。")
    }
    func (p Phone) Stop() {
        fmt.Println("手机停止工作。。。")
    }

    type Camera struct {
        name string
    }
    func (c Camera) Start() {
        fmt.Println("相机开始工作。。。")
    }
    func (c Camera) Stop() {
        fmt.Println("相机停止工作。。。")
    }

    func main() {
        var usbArr [3]Usb
        usbArr[0] = Phone{"vivo"}
        usbArr[1] = Phone{"小米"}
        usbArr[2] = Camera{"尼康"}
        fmt.Println(usbArr)
    }

类型断言

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言

    //类型断言的其它案例
    var x interface{}
    var b2 float32 = 1.1
    x = b2  //空接口,可以接收任意类型
    // x=>float32 [使用类型断言]
    y := x.(float32)
    fmt.Printf("y 的类型是 %T 值是=%v", y, y)

    在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型.

    //在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic
    //类型断言(带检测的)
	var x interface{}
	var b2 float32 = 2.1
	x = b2  //空接口,可以接收任意类型
	// x=>float32 [使用类型断言]

	//类型断言(带检测的)
	if y, ok := x.(float32); ok {
		fmt.Println("convert success")
		fmt.Printf("y 的类型是 %T 值是=%v", y, y)
	} else {
		fmt.Println("convert fail")
	}
	fmt.Println("继续执行...")
posted @ 2020-09-03 14:18  养诚  阅读(216)  评论(0编辑  收藏  举报