zoukankan      html  css  js  c++  java
  • Go语言面向对象编程

    引言

    l. Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang支持面向对象编程特性是比较准确的。
    2. Golang没有类(class),Go语言的结构体( struct)和其它编程语言的类class有同等的地位,你可以理解 Golang是基于 struct来实现OOP特性的。
    3. Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
    4. Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不样,比如继承: Golang没有 extends关键字,继承是通过匿名字段来实现。
    5. Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统( type system)的一部分,通过接口( interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang中面向接口编程是非常重要的特性。

    类型系统

    类型系统是指一个语言的类型体系结构。一个典型的类型系统通常包含如下基本内容:

    • 基础类型:int,bool,float等
    • 复合类型,如数组、结构体、指针等
    • 值语义和引用语义
    • 面向对象,即所有具备面向对象特征(比如成员方法)的类型
    • 接口

    值语义和引用语义

    • 值语义:值类型
    • 引用语义:引用类型

    结构体

    基本语法

    type 结构体名称 struct {
        field1 type
        field2 type
    }
    
    type Cat struct {
    	Name string
    	Age int
    	Color string
    	Hobby string
    }
    
    
    // 创建结构体变量的方式
    var cat Cat
    var cat Cat = Cat{"中分", 3, "黑白", "吃鱼"} // 必须字段顺序对应
    var cat Cat = Cat{Name : "中分", Age : 3, Color : "黑白", Hobby : "吃鱼"} // 字段顺序可以不对应
    var cat *Cat = new(Cat) // 返回的结构体指针
    var cat *Cat = &Cat{}   // 返回结构体指针
    
    // 结构体指针的调用方式
    (*cat).Name = "杜甫"  <-----> cat.Name = "杜甫" // 两个等价,因为go设计者为了程序员使用方便,底层会对cat.Name进行处理,加上*,这是一个语法糖
    
    1. 结构体是自定义的数据类型,代表一类事物
    2. 结构体变量是具体的,实际地,代表一个具体变量
    3. 结构体的所有字段在内存中的分布是连续的
    type Point struct {
    	x int
    	y int
    }
    
    type Rect1 struct {	// x, y四个int全部连续
    	leftUp, rightDown Point
    }
    
    type Rect2 struct { // 指针变量内存连续
    	leftUp, rightDown *Point
    }
    
    1. 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型
    2. 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段
    3. 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
    4. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化

    方法

    方法:即结构体的行为
    Golang 中的方法是作用在指定的数据类型上的,因此自定义类型,都可以有方法,而不仅仅是struct

    // 声明
    func (recevier type) methodName (参数列表) (返回值列表) {
    	方法体
    	return 返回值 // 不是必须的
    }
    
    type A struct {
    	Num int
    }
    
    func (a A) test0(){  // a 是副本
    	...
    }
    
    func (a *A) test1(){ // a 是结构体指针,结构体变量调用的时候,会传递地址给a
    	...
    }
    
    1. 方法只能通过结构体变量来调用
    2. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
    3. 如果希望能在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
    4. Golang中的方法作用在指定的数据类型上的(即:和指定数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct
    5. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问
    6. 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

    方法与函数的区别

    1. 调用方式不一样

      • 函数的调用方式:函数名(实参列表)
      • 方法的调用方式:变量.方法名(实参列表)
    2. 对应普通函数,接收者为值类型,不能将指针类型的数据直接传递

    工厂模式(构造函数)

    如果结构体的首字母是大写的,那么我们可以在其他包访问,并且创建它的变量,但是如果结构体首字母是小写,其他包访问不了,就需要用工厂模式来创建变量

    type student struct {
    	Name string
    	Age  int
    }
    
    func NewStudent(name string, age int) *student {
    	return &student{
    		name,
    		age,
    	}
    }
    

    Getter && Setter

    如果结构体的字段是小写的,则其他包就不能正常访问,应该借助Getter && Setter

    type student struct {
    	name string
    	age  int
    }
    
    func (stu *student) GetName() string {
    	return stu.name
    }
    
    func (stu *student) SetName(name string) {
    	stu.name = name
    }
    
    func (stu *student) GetAge() int {
    	return stu.age
    }
    
    func (stu *student) SetAge(age int) {
    	stu.age = age
    }
    

    抽象

    将一个实物,抽象出其特征和行为,形成结构体,这个过程就是抽象

    面向对象编程的三大特性

    • 封装
    • 继承
    • 多态

    封装

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

    封装的理解和好处

    1. 隐藏实现细节
    2. 可以对数据进行雁阵个,保证安全合理

    如何体现封装

    1. 对结构体中的属性进行封装
    2. 通过方法,包实现封装

    封装的实现步骤

    1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
    2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
    3. 提供一个首字母大写的Set方法(类似其它语言的 public,用于对属性判断并赋值

    继承

    当多个结构体存在相同的数字那个和方法时,可以从这些结构体中抽向出结构体,在该结构体中定义这些相同的属性和方法,可以成为父结构体
    其他的结构体不需要重新定义这些属性和方法,只需嵌套一个父结构体的匿名结构体即可
    在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性

    type Goods struct {
    	Name string
    	Price int
    }
    
    type Book struct {
    	Goods
    	Writer string
    }
    
    1. 结构体可以使用嵌套匿名结构体所有字段和方法,即:首字母大写或者小写的字段、方法都可以使用
    2. 匿名结构体字段可以简化
    3. 当结构体和匿名子结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如果希望访问匿名结构体的字段和方法,可以通过匿名结构体来区分
    4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和犯法),在访问时,就必须明确自定匿名结构体名字,否则编译报错
    5. 如果一个struct嵌套了一个有名结构体,这种模式就是组合。如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
    6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    // 含匿名结构体的初始化
    type Address struct {
    	province string
    	city string
    }
     
    type User struct {
    	name string
    	age int
    	Address
    }
     
    // 方法一:正常直观方式定义
    u1 := &User{
    	name: "Ming",
    	age: 30,
    	Address: Address{
    		province: "Jiangsu",
    		city: "Nanjing",
    	},
    }
    fmt.Printf("%+v
    ", u1)  // &{name:Ming age:30 Address:{province:Jiangsu city:Nanjing}}
     
    // 同上
    var u2 User
    u2.name = "Qiang"
    u2.age = 35
    u2.Address = Address{province: "Jiangsu", city: "Suzhou"}
    fmt.Printf("%+v
    ", u2)  // {name:Qiang age:35 Address:{province:Jiangsu city:Suzhou}}
     
    // 方法二:匿名嵌入时可以直接访问叶子属性而不需要给出完整的路径,也可以给出完整路径
    var u3 User
    u3.name = "A"
    u3.age = 40
    u3.province = "Jiangsu"
    u3.city = "Wuxi"
    fmt.Printf("%+v
    ", u3)  // {name:A age:40 Address:{province:Jiangsu city:Wuxi}}
     
    // 但下面的方式是错误的,编译不能通过
    // cannot use promoted field Address.province in struct literal of type User
    // cannot use promoted field Address.city in struct literal of type User
    u4 := User{
    	name: "A",
    	age: 29,
    	province: "Jiangsu",
    	city: "Wuxi",
    }
    fmt.Printf("%+v
    ", u4)
    
    1. 结构体内不仅仅可以嵌套结构体,还可以嵌套int,float64等
    // 如果一个结构体有int类型的匿名字段,就不能有第二个
    // 如果需要有多个int类型字段,则必须给int字段指定名字
    type M struct {
    	Name string
    }
    
    type E struct {
    	M
    	int
    	n int
    }
    
    func main() {
    	var e E
    	e.Name = "狐狸精"
    	e.int = 20
    	e.n = 40
    	fmt.Println(e)
    }
    
    1. 以指针方式从一个类型“派生”
      这段Go代码仍然有“派生”的效果,只是Foo创建实例的时候,需要外部提供一个Base类实例的指针。
    type Foo struct {
    	*Base
    	...
    }
    

    多重继承

    如果一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承

    type M struct {
    	Name string
    }
    
    type N struct {
    	ability string
    }
    
    type E struct {
    	M
    	N
    }
    

    接口

    讲多态之前先讲接口,因为Golang中的多态一般体现在接口的实现上

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

    1. 基本语法
      • 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想
      • Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有implement这样的关键字
    type interfaceName interface {
    	method1(参数列表) 返回值列表
    	method2(参数列表) 返回值列表
    	...
    }
    
    
    func (t 自定义类型) method1(参数列表) 返回值列表{}
    func (t 自定义类型) method2(参数列表) 返回值列表{}
    

    接口注意事项与细节

    1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
    type AInterface interface {
    	Say()
    }
    
    type Stu struct {
    	Name string
    }
    
    func (stu Stu) Say() {
    	fmt.Println("Stu Say(")
    }
    
    func main() {
    
    	var stu Stu
    	var a AInterface = stu
    	a.Say()
    }
    
    1. 接口中所有的方法都没有方法体,即都是没有实现的方法

    2. 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口

    type AInterface interface {
    	Say()
    	Call()
    }
    
    type Stu struct {
    	Name string
    }
    
    func (stu Stu) Say() {
    	fmt.Println("Stu Say(")
    }
    
    func main() {
    
    	var stu Stu
    	var a AInterface = stu  // 报错
    	a.Say()
    }
    
    1. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

    2. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型

    type AInterface interface {
    	Say()
    }
    
    type intteger int
    
    func (i intteger) Say() {
    	fmt.Println("integer Say i = ", i)
    }
    
    1. 一个自定义类型可以实现多个接口

    2. Golang接口中不能有任何变量

    3. 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现

    type A interface {
    	test01()
    }
    
    type B interface {
    	test02()
    }
    
    type C interface {
    	A
    	B
    	test03()
    }
    
    type Stu struct {
    
    }
    
    func (stu Stu) test01() {
    
    }
    
    func (stu Stu) test02() {
    	
    }
    
    func (stu Stu) test03() {
    	
    }
    
    func main() {
    
    	var stu Stu
    	var a A = stu
    	a.test01()
    	
    }
    
    1. interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil

    2. 空接口 interface{} 没有任何方法,所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口

    3. 结构体指针绑定接口

    type A interface {
    	test01()
    }
    
    type AI struct {
    }
    
    func (this *AI) test01() {
    	fmt.Println("test01")
    }
    
    func main() {
    
    	var a A = &AI{} // 如果这里没有&,则会报错
    	a.test01()
    	fmt.Println(a)
    
    }
    
    1. 侵入式接口和非侵入式接口的区别

      • 侵入式接口:主要表现在于实现类需要明确声明自己实现了某个接口。向Java、C++
      • 非侵入式接口:不需要显示的声明自己实现了哪个接口
    2. 接口赋值讨论:将对象实例赋值给接口
      假设我们定义一个Integer类型的对象实例,代码如下,怎么将其赋值给LessAdder接口呢?应该用下面的语句(1),还是语句(2)呢?

    type LessAdder interface {
    	Less(b Integer) bool
    	Add(b Integer)
    }
    
    type Integer int
    
    func (a Integer) Less(b Integer) bool {
    	return a < b
    }
    
    func (a * Integer) Add(b Integer){
    	*a += b
    }
    
    func main() {
    	var a Integer = 1
    	var b LessAdder = &a	... (1)
    	var c LessAdder = a		... (2)
    }
    

    答案是应该用语句(1)
    原因在于, Go语言可以根据下面的函数:func (a Integer) Less(b Integer) bool 自动生成一个新的Less()方法:

    func (a *Integer) Less(b Integer) bool {
    	return (*a).Less(b)
    }
    

    类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。而从另一方面来说,根据func (a *Integer) Add(b Integer)这个函数无法自动生成以下这个成员方法:

    func (a Integer) Add(b Integer) {
    	(&a).Add(b)
    }
    

    因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响,这不符合用户的预期。所以, Go语言不会自动为其生成该函数。因此,类型Integer只存在Less()方法,缺少Add()方法,不满足LessAdder接口,故此上面的语句(2)不能赋值。

    1. 接口赋值讨论:接口赋值给另一个接口
      在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。
    package one
    type ReadWriter interface {
    Read(buf []byte) (n int, err error)
    Write(buf []byte) (n int, err error)
    }
    
    /////////////////
    package two
    type IStream interface {
    Write(buf []byte) (n int, err error)
    Read(buf []byte) (n int, err error)
    }
    
    // 下面代码都通过
    var file1 two.IStream = new(File)
    var file2 one.ReadWriter = file1
    var file3 two.IStream = file2
    

    接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。

    type Writer interface {
    Write(buf []byte) (n int, err error)
    }
    

    就可以将上面的one.ReadWriter和two.IStream接口的实例赋值给Writer接口:

    var file1 two.IStream = new(File)
    var file4 Writer = file1
    

    但是反过来并不成立:

    var file1 Writer = new(File)
    var file5 two.IStream = file1 // 编译不能通过
    

    这段代码无法编译通过,原因是显然的: file1并没有Read()方法。

    1. 类型断言: 接口查询
    var file1 Writer = ...
    if file5, ok := file1.(two.IStream); ok {
    ...
    }
    
    1. 类型断言:类型查询
      在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型,例如:
    var v1 interface{} = ...
    	switch v := v1.(type) {
    	case int: // 现在v的类型是int
    	case string: // 现在v的类型是string
    	...
    }
    

    对于内置类型, Println()采用穷举法,将每个类型转换为字符串进行打印。对于更一般的情况,首先确定该类型是否实现了String()方法,如果实现了,则用String()方法将其转换为字符串进行打印。

    1. 接口也支持继承

    2. Any类型:interface{},可以指向任何对象

    多态

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

    1. 接口体现多态的两种形式
    • 多态参数:方法参数的体现
    1. 多态数组
    • 接口数组中,存放实现接口的变量

    类型断言

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

    var x interface{}
    	var b2 float32 = 1.1
    	x = b2	// 空接口,可以接受任意类型
    	// x => float32 [使用类型断言]
    	y := x.(float32)	// 这里如果不是float32就会panic
    	fmt.Printf("y 的类型是 %T 值是 %v", y, y)
    

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

    var x interface{}
    	var b2 float32 = 1.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("继续执行....")
    
  • 相关阅读:
    七牛大数据平台的演进与大数据分析实践--转
    Re:从0开始的微服务架构:(一)重识微服务架构--转
    Re:从 0 开始的微服务架构--(三)微服务架构 API 的开发与治理--转
    Java7里try-with-resources分析--转
    线上服务CPU100%问题快速定位实战--转
    Windows下本机简易监控系统搭建(Telegraf+Influxdb+Grafana)--转
    Scalable, Distributed Systems Using Akka, Spring Boot, DDD, and Java--转
    ES Segment Memory——本质上就是segment中加到内存的FST数据,因此segment越多,该内存越大
    Self Organizing Maps (SOM): 一种基于神经网络的聚类算法
    RBF网络——核心思想:把向量从低维m映射到高维P,低维线性不可分的情况到高维就线性可分了
  • 原文地址:https://www.cnblogs.com/lxlhelloworld/p/14286032.html
Copyright © 2011-2022 走看看