【启程Golang之旅】从结构到接口揭秘Go的“面向对象”面纱

欢迎来到Golang的世界!在当今快节奏的软件开发领域,选择一种高效、简洁的编程语言至关重要。而在这方面,Golang(又称Go)无疑是一个备受瞩目的选择。在本文中,带领您探索Golang的世界,一步步地了解这门语言的基础知识和实用技巧。

目录

初识面向对象

方法的引入

封装的引入

继承的引入

接口的引入

多态的引入

断言的引入


初识面向对象

在Go语言中,虽然它并没有像Java或C++那样显式地支持类和继承这样的传统面向对象编程(OOP)特性,但Go语言仍然支持面向对象编程的许多核心概念,如封装、继承(通过组合和接口实现)和多态。以下是Go语言中面向对象使用结构体的简单案例:

package main
import "fmt"
// Student 定义学生的结构体,将学生中的各个属性统一放入结构体中管理
type Student struct {
	// 变量名字大写外界可以访问到这个属性
	Name   string
	Age    int
	School string
}

func main() {
	// 直接创建
	// 创建学生结构体的实例、对象、变量;
	var t1 Student
	fmt.Println(t1) // 在未赋值时,结果为 { 0 }
	// 开始赋值
	t1.Name = "张三"
	t1.Age = 18
	t1.School = "北大"
	fmt.Println(t1) // {张三 18 北大}
	// 第二种
	var t2 Student = Student{"张三", 18, "北大"}
	fmt.Println(t2) // {张三 18 北大}
	//第三种:返回的是一个指针
	var t3 *Student = new(Student)
	// t3是指针,t3指向的就是地址,应该给这个地址的指向的对象的字段赋值
	(*t3).Name = "张三"
	(*t3).Age = 45   // *的作用是根据地址取值
	fmt.Println(*t3) // {张三 45 }
	// 为了符合程序员的编程习惯,go提供了简化的赋值方式
	t3.School = "北大" // go编译器底层对t3.School转化 (*t3).School = "北大"
	fmt.Println(*t3) // {张三 45 北大}
	//第四种
	var t4 *Student = &Student{"张三", 18, "北大"}
	fmt.Println(*t4) // {张三 18 北大}
}

最终实现的效果如下:

当然我们也可以对结构体之间进行转换,因为结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型):

package main
import "fmt"
type Student struct {
	Age int
}
type Teacher struct {
	Age int
}

func main() {
	var a Student = Student{Age: 18}
	var b Teacher = Teacher{Age: 20}
	a = Student(b) // 类型转换
	fmt.Println(a) // {20}
	fmt.Println(b) // {20}
}

结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

package main
import "fmt"
type Student struct {
	Age int
}
type Stu Student

func main() {
	var s1 Student = Student{Age: 1}
	var s2 Stu = Stu{Age: 10}
	s1 = Student(s2) // 类型转换
	fmt.Println(s1) // {10}
	fmt.Println(s2) // {10}
}

方法的引入

在go语言中,虽然没有类的概念,但是可以通过定义结构体和与结构体关联的方法来实现面向对象的编程,在这种方式下,方法是与特定类型关联的函数,方法是作用在指定的数据类型上,和指定的数据类型绑定,因此自定义类型都可以有方法,而不仅仅是struct,方法的声明和调用的格式如下:

type A struct {
	Name string
}

func (a A) test() {
	println(a.Name)
}

ok,接下来通过具体的代码示例进行简单的讲解,如下:

package main
import "fmt"
type Person struct {
	Name string
}

func (p Person) test() {
	p.Name = "李四"
	fmt.Println(p.Name) // 李四
}
func main() {
	var p Person
	p.Name = "张三"
	p.test()
	fmt.Println(p.Name) // 张三
}

注意:根据上面的示例代码,我们注意到以下几点

1)test方法中的参数名字随意起

2)结构体person和test方法绑定,调用test方法必须靠指定的类型,person

3)如果其他类型变量调用test方法一定会报错

4)结构体对象传入test方法中,值传递和函数参数传递一致

5)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式:

如果程序员希望在方法中改变结构体变量的值,可以通过结构体指针的方式来处理:

指针和不加指针的区别在于方法对原始变量的影响。使用指针接收者的方法可以直接修改原始变量的值,而不使用指针接收者的方法只能修改方法内部的副本,不会影响原始变量的值:

package main
import "fmt"
type integer int
func (i integer) print() {
	i = 30
	fmt.Println("i = ", i) // 30
}

func (i *integer) change() {
	*i = 15
	fmt.Println("i = ", *i) // 15
}
func main() {
	var i integer = 10
	i.print()
	i.change()
	fmt.Println(i) // 15
}

如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出,以后定义结构体的话,常定义String()作为输出结构体信息的方法,会自动调用。

方法与函数的区别

1)绑定指定类型

方法:需要绑定指定数据类型;函数:不需要绑定数据类型

2)调用方式不一样

函数的调用方式:函数名(实参列表);方法的调用方式:变量.方法名(实参列表)

对于函数来说,参数类型对应是什么就要传入什么;对于方法来说,接收者为值类型可以传入指针类型,接收者为指针类型,可以传入值类型,示例代码如下:

package main
import "fmt"
type Student struct {
	Name string
}

// 定义方法
func (s Student) test01() {
	fmt.Println(s.Name) // 张三
}

// 定义函数
func method01(s Student) {
	fmt.Println(s.Name) // 张三
}

func main() {
	// 调用函数
	var s Student = Student{"张三"}
	method01(s)
	// 调用方法
	s.test01()
}

如果想跨包创建实例的话,和以前的方法一致,如下:

要知道我们跨包访问变量的话,变量的首字母必须大写,对于结构体来说也是一样的,那有没有办法让结构体首字母小写也能跨包呢?这里需要采用工厂模式:

封装的引入

在go语言中,封装是面向对象编程(OOP)的一个重要概念,尽管go语言并没有明确地支持类和继承这样的传统OOP特性,但它仍然提供了封装的能力。封装主要是指将数据(字段)和与这些数据相关的操作(方法)组合在一个结构体(struct)中,并通过控制对结构体的访问权限来保护数据的完整性:

golang中如何实现封装:

1)建议将结构体、字段(属性)的首字母小写(其它包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格)

2)给结构体所在包提供一个工厂模式的函数,首字母大写 (类似一个构造函数)

3)提供一个首字母大写的set方法(类似其它语言的public),用于对属性判断并赋值

这里我给出如下封装代码:

package testUtils
import "fmt"
type person struct {
	Name string
	age  int // 首字母小写,其他包不能直接访问
}

// NewPerson 定义工厂模式的函数,相当于构造器
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

// 定义set和get方法,对age字段进行封装,因为在方法中可以加一系列的限制操作,确保被封装字段的安全合理性
func (p *person) SetAge(age int) {
	if age < 0 || age > 150 {
		fmt.Println(age, "年龄不合法")
	}
	p.age = age
}
func (p *person) GetAge() int {
	return p.age
}

接下来在main函数中开始调用结构体中的实例,如下:

package main
import (
	"fmt"
	"testUtils"
)

func main() {
	// 创建person结构体的实例
	p := testUtils.NewPerson("张三")
	p.SetAge(20)
	fmt.Println(p.Name)     // 张三
	fmt.Println(p.GetAge()) // 20
	fmt.Println(*p)         // {张三 20}
}

继承的引入

        当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其它的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性,如下:

如下代码给出具体的示例:

package main
import "fmt"
// 定义动物结构体
type Animal struct {
	Name string
	Age  int
}

// 给Animal绑定方法
func (a *Animal) Speak() {
	fmt.Println("动物说话")
}
func (a *Animal) showInfo() {
	fmt.Println("动物名称:", a.Name, "年龄:", a.Age)
}
// 定义猫结构体
type Cat struct {
	Animal // 匿名嵌入
}

// 对cat绑定特有方法
func (c *Cat) CatSpeak() {
	fmt.Println("喵喵~")
}
func main() {
	// 创建Cat结构体示例
	cat := &Cat{}
	cat.Animal.Name = "小猫"
	cat.Animal.Age = 2
	cat.Animal.Speak()    // 动物说话
	cat.Animal.showInfo() // 动物名称:小猫 年龄:2
	cat.CatSpeak()        // 喵喵~
}

注意

1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用:

2)匿名结构体字段访问可以简化:

3)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访间原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

4)golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是go中保留了。

5)如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分

6)结构体的匿名字段是基本数据类型

7)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

8)嵌入匿名结构体的指针也是可以的

9)结构体的字段可以是结构体类型的(组合模式)

接口的引入

在go语言中,接口(Interface)是一种类型,它定义了一组方法的集合,但没有为这些方法提供实现,任何实现了这些方法的类型都隐式地实现了该接口,无需显式声明。这种特性使得接口在Go中成为实现多态性的主要方式:

package main
import "fmt"
// 接口的定义:定义规则、定义规范、定义某种能力
type SayHello interface {
	// 声明没有实现的方法
	sayHello()
}

// 接口的实现,定义一个结构体
type Chinese struct{}
type Amerian struct{}

// 实现接口的方法
func (person *Chinese) sayHello() {
	fmt.Println("你好,中国")
}
func (person *Amerian) sayHello() {
	fmt.Println("Hello, America")
}

func main() {
	// 创建中国人
	var chinese = Chinese{}
	// 创建美国人
	var amerian = Amerian{}
	// 调用接口的方法
	chinese.sayHello() // 你好,中国
	amerian.sayHello() // Hello, America
}

注意

1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。

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

3)一个自定义类型可以实现多个接口。

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

5)interface类型默认是一个指针(引l用类型),如果没有对interface初始化就使用,那么会输出nil

6)空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把如何一个变量赋给空接口。

最后总结

1)接口中可以定义一组方法,但不需要实现,不需要方法体。并且接口中不能包含任何变量。到某个自定义类型要使用的时候(实现接口的时候),再根据具体情况把这些方法具体实现出来。

2)实现接口要实现所有的方法才是实现。

3)golang中的接,不需要显式的实现接口,golang中没有implement关键字。

4)接口目的是为了定义规范,具体由别人来实现即可。

多态的引入

在go语言中,多态(Polymorphism)是一个重要的面向对象编程的概念,它指的是不同对象对同一消息做出不同的响应,在go中多态性主要通过接口(Interface)来实现,但go并没有传统意义上的类和继承机制:

在go中,多态性主要体现在以下几个方面:

1)接口作为类型:Go的接口定义了一组方法的集合,任何实现了这些方法的类型都可以被视为该接口类型的实例。这意味着,我们可以将实现了相同接口的不同类型的对象赋值给接口类型的变量,并通过这个接口变量调用接口中定义的方法。由于不同的类型可能会以不同的方式实现这些方法,因此通过接口调用这些方法时就会表现出多态性。

2)隐藏具体实现:通过接口,我们可以隐藏对象的具体类型和实现细节,只关注对象的行为(即接口中定义的方法)。这使得我们可以编写更加灵活和可重用的代码,因为我们可以将任何实现了特定接口的对象传递给函数或方法,而无需关心其具体的类型。

3)动态类型绑定:在运行时,Go会根据接口变量所引用的对象的实际类型来调用相应的方法实现。这种动态类型绑定的特性使得我们可以在不修改代码的情况下,通过替换实现了相同接口的不同对象来改变程序的行为。

package main

import "fmt"

// 定义一个接口
type Shape interface {
	Area() float64
	Perimeter() float64
}

// 矩形类型实现了Shape接口
type Rectangle struct {
	width, height float64
}

func (r Rectangle) Area() float64 {
	return r.width * r.height
}

func (r Rectangle) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

// 圆形类型实现了Shape接口
type Circle struct {
	radius float64
}

func (c Circle) Area() float64 {
	return 3.14 * c.radius * c.radius
}

func (c Circle) Perimeter() float64 {
	return 2 * 3.14 * c.radius
}

// 一个函数,接受Shape接口类型的参数
func printInfo(s Shape) {
	fmt.Println("Area:", s.Area())
	fmt.Println("Perimeter:", s.Perimeter())
}

func main() {
	rect := Rectangle{width: 4, height: 5}
	circle := Circle{radius: 3}

	printInfo(rect)   // 调用Rectangle的Area和Perimeter方法
	printInfo(circle) // 调用Circle的Area和Perimeter方法
}

断言的引入

        在Go语言中,断言(Assertion)通常与接口(Interface)和类型断言(Type Assertion)相关。类型断言用于在运行时检查接口变量中存储的具体类型,并尝试将其转换为该类型。如果接口变量确实包含该类型的值,则断言成功;否则,断言失败并可能导致运行时错误:

非安全断言(也称为显式类型断言):使用类型变量.(类型)的形式进行断言。如果接口变量不包含该类型的值,则会引发运行时错误(panic):

var x interface{} = "hello"  
s := x.(string) // 如果x包含字符串,则s将接收该字符串;否则,panic

安全断言(也称为类型选择):使用类型变量, ok := 类型变量.(类型)的形式进行断言。如果接口变量包含该类型的值,则ok为true,并且该值会被赋予相应的变量;如果接口变量不包含该类型的值,则ok为false,并且不会引发运行时错误:

var x interface{} = "hello"  
if s, ok := x.(string); ok {  
    // 如果x是字符串,则s将接收该字符串,并且ok为true  
    fmt.Println(s)  
} else {  
    // 如果x不是字符串,则不会执行这里的代码  
    fmt.Println("x is not a string")  
}

在Go中,断言通常用于处理空接口interface{}类型的变量,因为空接口可以存储任何类型的值。通过使用断言,我们可以将空接口变量转换为具体的类型,以便我们可以调用该类型的特定方法或访问其字段。

断言在Go的并发编程和接口交互中特别有用,特别是当你不知道一个接口变量具体包含什么类型的值时。通过断言,你可以编写更加灵活和健壮的代码,能够处理不同类型的值,并在运行时进行类型检查。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/671310.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm校园自行车租赁系统-计算机毕业设计源码82064

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;学校当然也不例外。基于ssm的校园自行车租赁系统是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#…

《已解决》F12显示已在程序中暂停

首先打开F12-->源代码 最后一步&#xff1a;

C#WPF数字大屏项目实战08--生产量/良品统计

1、区域划分 生产量/良品统计这部分位于第二列的第二行 2、livechart拆线图 定义折线图,如下: <lvc:CartesianChart> <lvc:CartesianChart.Series> <!--设置Series的类型为 Line 类型, 该类型提供了一些折线图的实现--> <lvc:LineSeries/>…

【安装】VMware虚拟机安装windows操作系统,VM的相关操作

目录 引出报错&#xff1a;press any key to boot form cd激活调整显示 在VMware上新建虚拟机&#xff0c;并安装Windows1、新建虚拟机2、装载 ISO 镜像3、安装Windows server 20164、开机初始化 虚拟机操作1、虚拟机基本操作2、虚拟机快照3、虚拟机克隆4、VMware Tools 总结 引…

vue-标签选择

效果 选中后 代码 <span :class"[item.bealtrue?p_yx_span span_active :span p_yx]" click"onTagSelect(index)" v-for"(item,index) in tagList" :key"index" >{{item.name}} </span> // 列表值 tagList:[ {id: 1, na…

设计模式(九)结构型模式---外观模式

文章目录 外观模式简介结构优缺点UML图具体实现UML图代码实现 外观模式简介 外观模式 &#xff08;Facade Pattern&#xff09;也叫门面模式&#xff0c;是一种通过为多个复杂的子系统提供一个一致的接口&#xff0c;而使这些子系统更加容易被访问的模式。MyBatis的Configurat…

Java利用POI绘制表格

前提需求 最近公司要求写一些记录的表格&#xff0c;并且带有导出功能。再深入学习后&#xff0c;表格的底层其实就是list遍历塞值&#xff0c;导出功能的话可以由前端&#xff0c;后端实现&#xff0c;但技多不压身嘛&#xff0c;这里我自己就写了后端的导出功能&#xff0c;…

【固定资产管理】软件系统建设方案体系文档(Word原件)

固定资产管理系统解决方案 1系统概述 1.1需求描述 1.2需求分析 1.3重难点分析 1.4重难点解决措施 2系统架构设计 2.1系统架构图 2.2关键技术 3系统功能设计 3.1功能清单列表 3.2资产采购 3.3资产验收 3.4资产入库 3.5资产领用 3.6资产出库 3.7资产维修 3.8资产…

【ARM-Linux篇】内核编译

一、Linux内核的主要功能 Linux内核的主要功能&#xff1a;进程管理、内存管理、驱动、系统调用 Linux操作系统框架 二、Linux内核编译流程 方法一&#xff1a; 1. 运行 build.sh 脚本&#xff0c; 记得加 sudo 权限 gyjgyj-virtual-machine:~/orangepi-build$ sudo ./buil…

如何跨渠道分析销售数据 - 7年制造业销售经验小结

如何跨渠道分析销售数据 - 7年制造业销售经验小结&#xff08;1&#xff09; 【前言】 在我过去7年销售工作生涯中&#xff0c;从第一年成为公司销冠后&#xff0c;我当时的确自满的一段时间&#xff0c;认为自己很了不起。但是第一年的销售业绩并没有拿到提成&#xff0c;最…

图解DSPy:Prompt的时代终结者?!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

OpenCASCADE开发指南<十四>:OCCT建模类之BRepPrimAPI_MakePipe创建管道

1、OpenCasCade拓扑几何 在Open CASCADE Technology (OCCT) 中,除了基本三维几何体建模类BRepBuilderAPI外,还提供了复杂模型的建模类,常用的有如下几种,他们可以单独使用或相互组合,通过OCCT提供的融合函数进行组装。例如:BRepOffsetAPI_ThruSections、BRepOffsetAPI_Ma…

Quick BI中lod_fixed函数数据计算过程解析

一、lod_fixed函数简介 lod_fixed{[声明维度1][,声明维度2]…&#xff1a;聚合表达式[:过滤条件]} [维度1][,维度2]...&#xff1a;声明维度&#xff0c;一方面如果外部筛选字段若不属于这里的声明维度则无效&#xff0c;另一方面这里声明的维度也内部聚合运算时的分组依据。使…

明日周刊-第12期

以前小时候最期待六一儿童节了&#xff0c;父母总会给你满足一个愿望&#xff0c;也许是一件礼物也许是一次陪伴。然而这个世界上其实还有很多儿童过不上儿童节&#xff0c;比如某些地区的小孩子&#xff0c;他们更担心的是能不能见到明天的太阳。 文章目录 一周热点航天探索火…

像艺术家一样工作:前言

名人名言 “艺术是盗窃” —— 巴勃罗毕加索 “不成熟的诗人模仿&#xff0c;成熟的诗人偷窃&#xff1b;对于偷窃得到的艺术&#xff0c;坏的诗人丑化它&#xff0c;好的诗人加入自己的理解&#xff0c;使它变得更好&#xff0c;至少会让它有点不同。最优秀的诗人&#xff0…

chat2-Client发送数据给Server

本文档描述了Client发送消息给Server&#xff0c; Server端打印接收的消息 一、Client 1.1.客户端的类Client.java中添加如下的start()方法 (表示启动客户端功能的方法)&#xff0c;并调用 /**start方法&#xff0c;作为客户端开始工作的方法*/ public void start(){ …

【SpringMVC】_SpringMVC实现用户登录

目录 1、需求分析 2、接口定义 2.1 校验接口 请求参数 响应数据 2.2 查询登录用户接口 请求参数 响应数据 4、服务器代码 5、前端代码 5.1 登录页面login.html 5.2 首页页面index.html 6、运行测试 1、需求分析 用户输入账号与密码&#xff0c;后端校验密码是否正确&a…

Python-3.12.0文档解读-内置函数sum()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 sum(iterable, /, start0) 参数&#xff1a; 返回值&#xff1a; 注意事…

【风力发电】山顶的白色“大风车”你了解吗?

文章目录 术语定义 基本要求 外部条件 电气系统 控制系统 参考资料 术语定义 风力发电机组wind turbine generator system;WTGS&#xff0c;将风的动能转换为电能的系统。示例如下&#xff1a; 支撑结构support structure&#xff0c;风力发电机组的塔架和基础部分。 机舱…

NVIDIA NeMo - 训练本地化多语种 LLM

本文转载自&#xff1a;使用 NVIDIA NeMo 训练本地化多语种 LLM &#xff08;2024年 5月 17日 By Nicole Luo and Amit Bleiweiss 第 1 部分 https://developer.nvidia.com/zh-cn/blog/training-localized-multilingual-llms-with-nvidia-nemo-part-1/ 第 2 部分 https://deve…