• 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("继续执行...")
    
  • 相关阅读:
    【YbtOJ#20064】预算缩减
    【GMOJ6805】模拟speike
    【洛谷P5675】取石子游戏
    【YbtOJ#20061】波动序列
    【洛谷P4302】字符串折叠
    flash 上传文件
    HTTP 客户端发送的 头 格式
    FLEX 在本地使用 只访问本地文件
    as3 重写
    iis7 上传限制问题
  • 原文地址:https://www.cnblogs.com/KylinBlog/p/13607269.html
Copyright © 2020-2023  润新知