【Go语言从入门到实战】面向对象编程篇

news2024/12/27 3:37:07

面向对象编程

Go语言的面向对象编程和其他语言有非常大的差别。

image-20230428111344935

Go 是一种面向对象的语言吗?

是和不是。虽然 Go 有类型和方法,并允许面向对象的编程风格,但没有类型层次结构(继承)。Go 中的“接口”概念提供了一种不同的方法,我们认为这种方法易于使用,并且在某些方面更通用。还有一些方法可以将类型嵌入到其他类型中,以提供类似于(但不完全相同)子类化的东西。此外,Go 中的方法比 C++ 或 Java 中的方法更通用:它们可以为任何类型的数据定义,甚至是内置类型,例如普通的“未装箱”整数。它们不限于结构(类)。

此外,缺少类型层次结构使得 Go 中的“对象”感觉比 C++ 或 Java 等语言中的“对象”轻量级得多。

封装数据和行为

结构体定义

image-20230428121213526

实例创建及初始化

image-20230428121307421

type Employee struct {
	Id   string
	Name string
	Age  int
}

func TestCreateEmployeeObj(t *testing.T) {
	e := Employee{"0", "Bob", 20}
	e1 := Employee{Name: "Mike", Age: 30}
	e2 := new(Employee) // 返回指针
	e2.Id = "2"
	e2.Name = "Rose"
	e2.Age = 22
	t.Log(e)
	t.Log(e1)
	t.Log(e1.Id)
	t.Log(e2)
	t.Logf("e is %T", e)
	t.Logf("e2 is %T", e2)
}

image-20230428122127640

行为(方法)定义

image-20230428122402465

  • 第一种

    func (e Employee) String() string {
    	fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))
    	fmt.Println()
    	return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }
    
    func TestStructOperations(t *testing.T) {
    	e := Employee{"0", "Bob", 20}
    	fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))
    	fmt.Println()
    	t.Log(e.String())
    }
    

    image-20230428123715323

    所以这种写法会有复制的开销。

  • 第二种

    func (e *Employee) String() string {
    	fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))
    	fmt.Println()
    	return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }
    
    func TestStructOperations(t *testing.T) {
    	e := &Employee{"0", "Bob", 20} // 传递引用
    	fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))
    	fmt.Println()
    	t.Log(e.String())
    }
    

    image-20230428123601046

    更推荐这种。

接口(定义交互协议)

Go的接口和很多主流编程语言的接口有很大的区别。

Java代码的示例:

image-20230428124443597

Duck Type式接口

Go语言的interface:

image-20230428125301615

type Programmer interface {
	WriteHelloWorld() string
}

type GoProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() string { // duck type 鸭子类型
	return "Hello World"
}

func TestClient(t *testing.T) {
	var p Programmer
	p = new(GoProgrammer)
	t.Log(p.WriteHelloWorld())
}

image-20230428143644240

Go接口

image-20230428130027398

接口变量

image-20230428130325699

自定义类型

image-20230428130434903

type IntConv func(op int) int

// 计算函数操作的时长
func timeSpend(inner IntConv) IntConv { // 以前的方法特别的长 我们可以用自定义类型做替换
	// 类似装饰者模式,对原来的函数进行了一层包装
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spend:", time.Since(start).Seconds())
		return ret
	}
}

扩展和复用(类似继承)

Go语言无法天然支持继承,但是又想要实现面向对象的特性。

即父类对象 使用子类对象初始化,那么该父类对象调用的函数就是子类实现的函数 ,从而满足LSP(子类交换原则)。

案例一: Go语言 支持扩展父类的功能,如下代码:

package oriented_test

import (
  "fmt"
  "testing"
)

// Pet 类
type Pet struct {
}

func (p *Pet) Speak(){ // Pet类的函数成员
  	fmt.Print("Pet speak.\n")
}

func (p *Pet) SpeakTo(host string) { // Pet类的函数成员
  	p.Speak()
  	fmt.Println("Pet SpeakTo ", host)
}

// Dog 扩展Pet的功能
type Dog struct {
  	p *Pet
}

// 扩展父类的方法
func (d *Dog) Speak(){
  	d.p.Speak()
}

// 扩展父类的方法
func (d *Dog) SpeakTo(host string) {
  	d.Speak()
  	fmt.Println("Dog Speakto ", host)
}

func TestDog(t *testing.T) {
  	dog := new(Dog)
	dog.SpeakTo("Test dog")
}

以上测试代码的输出如下:

image-20230428141543094

dog 的 SpeakTo 中调用了 dog 的 Speak,其中调用了 Pet 的 Speak,所以输出正常。
Pet 和 Dog 调用不会相互影响,完全由用户决定。

但是这和我们所想需要的不同,Pet 类有自己的方法,Dog 类有自己的方法,两者作用域完全不同。

这里Go语言推出了匿名嵌套类型,即 Dog 类不用实现自己的和 Pet 类同名的方法即可,通过在 Dog 类的声明中变更 Pet 成 员。

匿名嵌套类型

案例二: Go语言支持匿名函数类型

// Dog 扩展Pet的功能
type Dog struct {
  	Pet
}

这样即不需要 Dog 声明自己的同名函数成员,默认的调用即为 Pet 成员函数的调用。

package oriented_test

import (
  	"fmt"
  	"testing"
)

type Pet struct {
}

func (p *Pet) Speak(){
  	fmt.Print("Pet speak.\n")
}

func (p *Pet) SpeakTo(host string) {
  	p.Speak()
  	fmt.Println("Pet SpeakTo ", host)
}

// Dog 扩展Pet的功能
type Dog struct {
  	Pet // 支持匿名嵌套类型
}

func TestDog(t *testing.T) {
  	var dog Dog

  	dog.Speak()
  	dog.SpeakTo("Test dog")
}

最终的输出如下:

image-20230428141503654

调用的都是 Pet 的成员函数,感觉像是继承了,因为继承默认就是子类能够使用父类的公有成员。

在匿名嵌套类型下,我们想要完整尝试一下Go语言是否真正支持继承,可以像之前的代码一样在 Dog 中实现 Pet 的同名函数,且能够通过父类对象调用子类的成员方法,像 C++/Java 这样进行向上类型转换(本身是不可能的,Go语言不支持显式类型转换)。

案例三: Go语言不支持继承,如下代码:

package oriented_test

import (
  	"fmt"
  	"testing"
)

type Pet struct {
}

func (p *Pet) Speak(){
  	fmt.Print("Pet speak.\n")
}

func (p *Pet) SpeakTo(host string) {
  	p.Speak()
  	fmt.Println("Pet SpeakTo ", host)
}

// Dog 扩展Pet的功能
type Dog struct {
  	//p *Pet
 	Pet // 支持匿名嵌套类型
}

// 重载父类的方法
func (d *Dog) Speak(){
  	fmt.Print("Dog speak.\n")
}

// 重载父类的方法
func (d *Dog) SpeakTo(host string) {
  d.Speak()
  fmt.Println("Dog Speakto ", host)
}

func TestDog(t *testing.T) {
	var dog Pet = new(Dog) // 这里就会编译错误
	dog.Speak()
	dog.SpeakTo("Test dog")
}
cannot use new(Dog) (value of type *Dog) as Pet value in variable declaration
不支持将Pet类型转换为Dog类型

总结一下,Go语言并不支持继承,能够支持接口的扩展 和 复用**(匿名嵌套类型)**,内嵌这种方式是完全不能当成继承来用的,因为它不支持访问子类的方法数据(重载),不支持LSP原则。

其中扩展 就是不同类实现相同的成员函数,能够实现类似于案例一中的扩展接口形态。

复用则是通过匿名嵌套类型实现 类似于重载的功能,可以看看案例二的代码。

多态

image-20230428143317910

type Programmer interface {
	WriteHelloWorld() string
}

type GoProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() string {
	return "fmt.Println(\"Hello World\")"
}

type JavaProgrammer struct {
}

func (j *JavaProgrammer) WriteHelloWorld() string {
	return "System.out.println(\"Hello World\")"
}

// 多态
func writeFirstProgram(p Programmer) {
	fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}

func TestClient(t *testing.T) {
	goProgrammer := new(GoProgrammer)
	javaProgrammer := new(JavaProgrammer)
	writeFirstProgram(goProgrammer)
	writeFirstProgram(javaProgrammer)
}

image-20230428144147613

空接口与断言

image-20230428144326958

func DoSomething(p interface{}) {
	// '.' 断言,p.(int),p断言为int类型
	if i, ok := p.(int); ok {
		fmt.Println("Integer", i)
		return
	}
	if s, ok := p.(string); ok {
		fmt.Println("string", s)
		return
	}
	fmt.Println("UnKnow type")
}

func TestEmptyInterface(t *testing.T) {
	DoSomething(10)
	DoSomething("10")
	DoSomething(10.00)
}

image-20230428145558309

Go接口最佳实践

image-20230428145718339

错误机制

error

image-20230428150518369

package error

import (
	"errors"
	"testing"
)

func GetFibonacci(n int) ([]int, error) {
	if n < 2 || n > 100 {
		return nil, errors.New("n should be in [2, 100]")
	}
	fibList := []int{1, 1}
	for i := 2; i < n; i++ {
		fibList = append(fibList, fibList[i-1]+fibList[i-2])
	}
	return fibList, nil
}

func TestGetFibonacci(t *testing.T) {
	if v, err := GetFibonacci(-10); err != nil {
		t.Error(err)
	} else {
		t.Log(v)
	}
}

image-20230428161919642

最佳实践

image-20230428162534768

尽早失败,避免嵌套!

例:

func GetFibonacci2(str string) {
	var (
		i    int
		err  error
		list []int
	)
	if i, err = strconv.Atoi(str); err != nil {
		fmt.Println("Error", err)
		return
	}
	if list, err = GetFibonacci(i); err != nil {
		fmt.Println("Error", err)
		return
	}
	fmt.Println(list)
}

panic

image-20230428163230055

panic vs os.Exit

image-20230428163307433

revocer

image-20230428164252392

recover 类似于 java 的 catch

func TestRecover(t *testing.T) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("recovered from", err)
		}
	}()
	fmt.Println("Start")
	panic(errors.New("something wrong"))
}

image-20230428183030584

image-20230428165703129

其实上面这种修复方式是非常危险的。

我们一定要当心自己 revocer 在做的事,因为我们 revocer 并不检测到底发生了什么错误,而只是记录了一下或者直接忽略掉了,这时可能系统某些核心资源消耗完了,但我们把他强制恢复之后系统依然是不能正常工作的,还会导致我们的健康检查程序 health check 检查不出当前系统的问题,因为很多 health check 只是检查当前系统的进程在还是不在, 因为我们的进程是在的,所以就会形成僵尸进程,它还活着,但它不能提供服务。

image-20230428165726627

如果出现了这种问题,我们可以用 “Let is Crash” 可恢复的设计模式,我们直接 crash 掉,这样守护进程就会重新把服务进程提起来(说的有点高大上,其实就是重启),重启是恢复不确定性错误的最好方法。

包 package

构建可复用的模块(包)

image-20230428183701611

image-20230428185412938

my_series.go

package series

func GetFibonacci(n int) ([]int, error) {
	ret := []int{1, 1}
	for i := 2; i < n; i++ {
		ret = append(ret, ret[i-1]+ret[i-2])
	}
	return ret, nil
}

package_test.go

引用另一个包中的方法:

package client

import (
	"mygoland/geekvideo/ch13/series" // 包路径要从自己的gopath开始写起
	"testing"
)

func TestPackage(t *testing.T) {
	t.Log(series.GetFibonacci(10))
}

init方法

image-20230428185555174

func init() {
	fmt.Println("init1")
}

func init() {
	fmt.Println("init2")
}

func GetFibonacci(n int) []int {
	ret := []int{1, 1}
	for i := 2; i < n; i++ {
		ret = append(ret, ret[i-1]+ret[i-2])
	}
	return ret
}

image-20230428185810992

如何使用远程的package

image-20230428192434197

ConcurrentMap for GO

https://github.com/easierway/concurrent_map

使用 go get 命令导入

go get -u github.com/easierway/concurrent_map

image-20230428192052651

package remote

import (
	cm "github.com/easierway/concurrent_map" // 导入远程包
	"testing"
)

func TestConcurrentMap(t *testing.T) {
	m := cm.CreateConcurrentMap(99)
	m.Set(cm.StrKey("key"), 10)
	t.Log(m.Get(cm.StrKey("key")))
}

image-20230428192409198

依赖管理

Go未解决的依赖问题

image-20230429184940847

vendor路径

image-20230429185019589

常用的依赖管理工具

  • godep:https://github.com/tools/godep
  • glide:https://github.com/Masterminds/glide
  • dep:https://github.com/golang/dep

安装glide

  • mac环境,使用 brew 安装 glide

    brew install glide
    

    安装成功
    image-20230430132905106

  • 初始化 glide

    glide init
    

    image-20230505090852387

    image-20230505092905006

    image-20230505093034649

    image-20230505093114018

    image-20230505093146424

    image-20230505093229266

    image-20230505093327934

  • glide init 执行完毕后,生成了一个 yaml 文件,并把依赖的包和版本号定义在了里面

    image-20230505093453057

  • 在之前的目录下执行glide install

    image-20230505094627270

    然后就会在我们的指定的文件下面生成一个 vender 目录和 glide.lock 文件。

    image-20230505094650525

  • 到此为止,Go 就能 搜索到 vender 目录下面的 package 了,我们就通过 vender 来指定了包的路径和版本号,即实现了在同一环境下使用同一个包的不同版本依赖了。

笔记整理自极客时间视频教程:Go语言从入门到实战

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot+vue学生综合测评系统(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的学生综合测评系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

electron更新机制

说明: 本文适用于win和mac这两个端mac端若要调试更新功能, 一定要把旧版本和新版本都配置mac的代码签名, 至于怎么配置, 主要就是先成为苹果开发者&#xff0c;然后去帐号那边下一个证书, 然后..... 一. 在package.json配置更新参数 参考: Publish - electron-builder &quo…

六级备考23天|CET-6|翻译技巧5-2|2019年12月真题|翻译荷花lotus|11:05-12:05

目录 一、中文 句子1 PRACTICE ANSWER 句子2 PRACTICE ANSWER 句子3 ​ PRACTICE ANSWER 句子4 PRACTICE ANSWER 句子5 PRACTICE ANSWER 句子6 PRACTICE ANSWER ​ 答案整合​ 一、中文 句子1 荷花是中国的名花之一&#xff0c;深受人们喜爱。 PRACTICE Lotus is one…

使用nvm管理node多版本(安装、卸载nvm,配置环境变量,更换npm淘宝镜像)

目录 前言一、卸载node二、nvm是什么&#xff1f;三、nvm安装1. 官网下载 nvm 包2. 安装 nvm-setup.exe小tips 3. 配置路径和下载镜像4. 检查nvm是否安装完成 四、使用nvm安装node版本五、修改npm默认镜像源为淘宝镜像六、 环境变量配置1. 设置系统变量和用户变量的作用是什么呢…

led显示屏怎么连接电脑主机

要将LED显示屏连接到计算机主机&#xff0c;通常需要使用适当的接口和连接线。以下是常见的连接方法&#xff1a; HDMI接口&#xff1a;大多数现代计算机和LED显示屏都配备了HDMI接口。您可以使用HDMI连接线将计算机主机的HDMI输出端口连接到LED显示屏的HDMI输入端口。这种连接…

leetcode--环形链表.找到入环节点(java)

环形链表II 环形链表.找到入环节点题目描述解题思路 环形链表.找到入环节点 LeetCode 142&#xff1a;环形链表II 可以在这里测试 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节…

深度解析线程的正确停止方法

一、解惑 1. 什么情况下&#xff0c;线程需要被停止&#xff1f; 线程和任务被创建和启动之后&#xff0c;大部分情况下都是自然运行到结束的&#xff0c;自然停止&#xff0c;但有些情况会需要用到停止线程&#xff0c;如&#xff1a; 用户主动取消 服务被快速关闭 运行出…

【C++实现插入排序、希尔排序、冒泡排序、快速排序、选择排序】

使用C实现来插入排序、希尔排序、冒泡排序、快速排序、选择排序算法。 一、插入排序 插入排序&#xff0c;一般也被称为直接插入排序。对于少量元素的排序&#xff0c;它是一个有效的算法 。插入排序是一种最简单的排序方法&#xff0c;它的基本思想是将一个记录插入到已经排好…

2023爱分析·中国面向开发者的低代码开发平台市场厂商评估报告

01 研究范围定义 “低代码”是一种可视化的应用开发方式&#xff0c;相对于传统编写代码的“纯代码”开发方式&#xff0c;低代码开发平台可以减少代码编写量或不使用代码编写进行应用的开发。随着技术革新&#xff0c;大模型也为低代码开发平台发展指明了新方向。从开发者与开…

单片机GD32F303RCT6 (Macos环境)开发 (二十八)—— 蓝牙透传模块HC-08 Android App开发

蓝牙透传模块HC-08 Android App开发 1、App整体开发思路 a、首先要申请权限&#xff0c;采用动态申请的方式&#xff0c;用户点击确认后方可操作蓝牙。 b、搜索蓝牙&#xff0c;之前的版本用startLeScan函数搜索蓝牙&#xff0c;虽然高版本中依然可用&#xff0c;但是google已…

什么时候该停止使用Scrum?

01、TL;DR:一个团队什么时候应该停止使用Scrum? 什么时候才能超越Scrum?毕竟许多类似思想、实践等事务迟早会过时;那为什么Scrum会是个例外?此外&#xff0c;我们不是通过实践Scrum来获得报酬&#xff0c;而是在既定的约束条件下解决客户的问题&#xff0c;同时又能为组织的…

( 链表) 707. 设计链表 ——【Leetcode每日一题】

❓707. 设计链表 难度&#xff1a;中等 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还…

ubuntu 安装ffmpeg

一、我的编译环境 ubuntu 22 ffmpeg 4.36 二、安装必要的依赖 sudo apt-get update sudo apt-get install -y \autoconf \automake \build-essential \cmake \git-core \libass-dev \libfreetype6-dev \libsdl2-dev \libtool \libva-dev \libvdpau-dev \libvorbis-dev \lib…

160743-62-4,DMG PEG2000,1,2-二肉豆蔻酰-rac-甘油-3-甲氧基聚乙二醇2000

DMG PEG2000&#xff0c;DMG-mPEG2000&#xff0c;1,2-二肉豆蔻酰-rac-甘油-3-甲氧基聚乙二醇2000 Product structure&#xff1a; Product specifications&#xff1a; 1.CAS No&#xff1a;160743-62-4 2.Molecular formula&#xff1a; C34H66O 3.Molecular weight&#xff…

Ubuntu18.04 dash to dock启动器安装教程

1.安装主题工具&#xff1a;GNOME Tweaks sudo apt-get update sudo apt-get install gnome-tweak-tool2.手动安装dash-to-dock插件 Dash-to-dock不支持3.32以上版本的gnome&#xff0c;git clone dash to dock的仓库 yeatsyeats-virtual-machine:~/Tools$ git clone https:/…

Axure教程—垂直方向多色图(中继器)

本文将教大家如何用AXURE制作动态垂直方向多色图 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://9fxtte.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87822547?spm1001.2014.3001.5503 二、功能介绍 简单填写中继…

mfc100.dll丢失如何解决?修复mfc100.dll的方法分享

mfc100.dll是Microsoft Visual C 2010中的一个动态链接库文件。如果该文件丢失&#xff0c;将会导致某些应用程序无法正常运行。在本文中&#xff0c;我们将探讨关于mfc100.dll丢失的问题&#xff0c;以及如何解决它。 一.什么是mfc100.dll mfc100.dll是Microsoft Visual C 20…

头羊部落亮相第26届北京餐食会

第26届AIFE2023亚洲&#xff08;北京&#xff09;国际食品饮料暨餐饮食材展览会&#xff08;简称&#xff1a;BCFE北京餐食会) 于2023年5月23-25日在北京中国国际展览中心火热召开。顺应时代发展下的餐饮新潮流&#xff0c;北京餐食会首次聚焦预制菜市场&#xff0c;为彰显预制…

Web安全:代码执行漏洞 测试.

Web安全&#xff1a;代码执行漏洞 测试 攻击者可以通过构造恶意输入来欺骗应用程序执行恶意代码。这种漏洞通常出现在应用程序中使用动态语言(如 PHP、Python、Ruby 等)编写的代码中&#xff0c;因为这些语言允许在运行时动态执行代码。攻击者可以通过构造特定的输入来欺骗应用…

分布式事务的21种武器 - 5

在分布式系统中&#xff0c;事务的处理分布在不同组件、服务中&#xff0c;因此分布式事务的ACID保障面临着一些特殊难点。本系列文章介绍了21种分布式事务设计模式&#xff0c;并分析其实现原理和优缺点&#xff0c;在面对具体分布式事务问题时&#xff0c;可以选择合适的模式…