第十一章 Golang面向对象编程(下)

news2025/5/10 13:21:28

面向对象编程三大特性

基本介绍
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他OOP语言不一样。

封装

面向对象编程思想-抽象

我们在前面去定义一个结构体的时候,实际上就是把一类事物共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
在这里插入图片描述

package main

import (
	"fmt"
)

// 定义一个结构体
type Account struct{
	AccountNo string
	Pwd string
	Banlance float64
}

// 方法
// 1.存款
func (account *Account) Deposite(money float64,pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.Pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	// 看看存款金额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return
	}

	account.Balance += money
	fmt.Println("存款成功~~")
}
// 取款
func (account *Account) WithDraw(money float64,pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.Pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	// 看看存款金额是否正确
	if money <= 0 || money > account.Balance {
		fmt.Println("你输入的金额不正确")
		return
	}

	account.Balance -= money
	fmt.Println("取款成功~~")
}
// 查询余额
func (account *Account) Query(pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.Pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	
	fmt.Printf("你的账号=%v 余额=%v \n",account.AccountNo,account.Balance)
}

func main(){
	//  测试
	account := Account{
		AccountNo:"gs111",
		Pwd:"666666",
		Balance:100.0
	}
	account.Query("666666")
	account.Deposite(200.0,"666666")
	account.Query("666666")
	account.Withdraw(20.0,"666666")
	account.Query("666666")
}

封装介绍

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

优点

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

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

实现步骤

  1. 将结构体,字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
    3 . 提供一个首字母大写的Set方法{类似其他语言的public},用于对属性判断并赋值
func (var 结构体类型名)SetXxx(参数列表)(返回值列表){
   // 加入数据验证的业务逻辑
   var 字段 = 参数
}

4 . 提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值

func (var 结构体类型名) GetXxx(){
   return var.age;
}

说明:在Golang开发中并没有特别强调封装,这点并不像Java,所以提醒学过Java的朋友不用总是用Java的特性来看待Golang,Golang本身对面向对象的特性做了简化的

案例

// person.go
package model
import "fmt"

type person struct{
	Name string 
	age int
	sal float64
}
// 写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
	return &person{
	     Name:name
	}
}
// 为了访问age和sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int){
	if age >0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("年龄范围不正确..")
	}
}

func (p *person) GetAge() int {
  return p.age
}

func (p *person) Setsal(sal float64){
  if sal >= 3000 && sal <= 30000{
		p.sal = sal
   }else{
		fmt.Println("薪水范围不正确..")
	}
}

func (p *person) GetSal() float64{
	return p.sal
}
// main.go
import (
	"fmt"
	"go_code/chapter11/encapsulate/model"
)
func main(){
   p := model.NewPerson("smith")
   p.SetAge(18)
   p.SetSal(5000)
   fmt.Println(p)
   fmt.Println(p.Name,"age =",p.GetAge(),"sal = ",p.GetSal())
}

练习
要求:

  1. 创建程序,在model包中定义Account结构体:在main函数中体会Golang的封装性
  2. Account结构体要求具有字段:账号(长队在6-10之间),余额(必须>20),密码(必须是六位)
  3. 通过SetXxx的方法给Account的字段赋值。
  4. 在main函数中测试
package model

import (
	"fmt"
)

// 定义一个结构体
type account struct{
	accountNo string
	pwd string
	banlance float64
}
// 工厂模式的函数-构造函数
func NewAccount(accountNo string,pwd string,balance float64) *account{
	if len(accountNo) <6 || len(accountNo) >10{
		fmt.Println("账号长度不对")
		return nil
	}
	if len(pwd) != 6{
		fmt.Println("密码长度不对")
		return nil
	}
	if balance <20{
		fmt.Println("余额数目不对")
		return nil
	}
	return &account{
		accountNo : accountNo,
		pwd : pwd,
		balance:balance
	}
}
// 方法
// 1.存款
func (account *account) Deposite(money float64,pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	// 看看存款金额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return
	}

	account.balance += money
	fmt.Println("存款成功~~")
}
// 取款
func (account *account) WithDraw(money float64,pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	// 看看存款金额是否正确
	if money <= 0 || money > account.balance {
		fmt.Println("你输入的金额不正确")
		return
	}

	account.balance -= money
	fmt.Println("取款成功~~")
}
// 查询余额
func (account *account) Query(pwd string){
   // 看一下输入的密码是否正确
   if pwd != account.pwd {
	   fmt.Println("你输入的密码不正确")
	   return
	}
	
	fmt.Printf("你的账号=%v 余额=%v \n",account.accountNo,account.balance)
}

package main

import (
	"fmt"
	"go_code/chapter11/encapexercise/model"
)

func main(){
  // 创建一个account变量
  account := model.NewAccount("jzh111111","999999",40)
  if account != nil{
		fmt.Println("创建成功=",account)
   }else{
		fmt.Println("创建失败")
	}
}

继承
继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法,只需嵌入一个student匿名结构体即可。

也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

在这里插入图片描述

嵌套匿名结构体的基本语法

type Goods struct {
   Name string
   Price int
}

type Book struct{
   Goods // 这里就是嵌套匿名结构体Goods
   Writer string
}

继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

继承的深入讨论

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

package main
import (
	"fmt"
)

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字段,如果有就调用,如果没有就继续寻找。如果都没有就报错

b.可以简化
c.当结构体和匿名函数结构体有相同的字段或者方法时,编译器采用就近访问原则访问,希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
d. 结构体嵌入两个(或多个)匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译出错

package main
import (
	"fmt"
)

type A struct{
  Name string
  age int
}
type B struct{
  Name string
  Score float64
}
type C struct{
  A
  B
  // Name string
}

func main(){
  var c C
  // 如果c 没有Name字段,而A 和 B有Name,这时就必须通过指定匿名结构体名字来区分
  // 所以  c.Name 就会包编译错误
  c.A.Name = "tom"
  fmt.Println("c")
}

e. 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

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

面向对象编程-多重继承

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

多重继承的细节

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

接口

基本介绍
按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在Golang中,多态特性主要是通过接口来实现的。

入门
这样的设计需求在Golang编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在其他程序中也会出现。我们用程序来模拟一下前面的应用场景。

package main
import (
 "fmt"
)
// 声明一个接口
type Usb interface{
  // 声明了两个没有实现的方法
  Start()
  Stop()
}

type Phone struct{

}
// 让Phone实现Usb接口的方法
func (p Phone) Start(){
  fmt.Println("手机开始工作...")
}
func (p Phone) Stop(){
  fmt.Println("手机停止工作...")
}

// 让相机实现Usb接口的方法
type Camera struct{

}
// 让Camera实现Usb接口的方法
func (c Camera) Start(){
  fmt.Println("相机开始工作...")
}
func (c Camera) Stop(){
  fmt.Println("相机停止工作...")
}
// 计算机
type Computer struct{
 
}
// 编写一个方法Working方法,接收一个Usb接口类型变量
// 只要实现了Usb接口,(所谓实现Usb接口,就是指实现了 Usb接口声明所有方法)
func (c Computer) Working(usb Usb){
  // 通过usb接口变量来调用Start和Stop方法
  usb.Start()
  usb.Stop()
}
func main(){
   // 测试
   // 先创建结构体变量
   computer := Computer{}
   phone := Phone{}
   //camera := Camera{}
 
   // 关键点
   computer.Working(phone)
   
}

215

感谢大家观看,我们下次见

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

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

相关文章

Presto 之 explain and explain analyze的实现

一. 前言 本文主要探索在Presto中Explain功能是如何实现的。在Presto中&#xff0c;Explain用法有两种&#xff0c;一种是单纯的explain&#xff0c;此场景只会显示经过RBO优化后的执行计划&#xff0c;但是查询并不会真正地执行。第二种是explain analyze&#xff0c;此场景会…

JVM常用参数

JVM内存相关的几个核心参数 -Xms&#xff1a;Java堆内存初始大小-Xmx&#xff1a;Java堆内存的最大大小-Xmn&#xff1a;Java堆内存中的新生代大小&#xff0c;扣除新生代剩下的就是老年代的内存大小了-XX:PermSize&#xff1a;永久代大小-XX:MaxPermSize&#xff1a;永久代最…

疾控物资管理系统-疾控中心物资管理系统

一、系统概述 东识科技&#xff08;DONWIT&#xff09;疾控中心物资管理系统&#xff08;智物资DW-S300&#xff09;是依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 随着疫情的突…

(STM32)从零开始的RT-Thread之旅--GPIO

上一篇&#xff1a; (STM32)从零开始的RT-Thread之旅--基础项目构建与时钟配置 无论什么开发板&#xff0c;最先调试的肯定是GPIO&#xff0c;一般用来用作指示灯或者按键输入。本篇只是很简单的GPIO应用&#xff0c;没有具体分析RTT框架实现。 首先先创建一个BSP文件夹&…

机器人操作系统ROS(21) jetson nano安装torch tensorflow

安装torch、tensorflow其实跟普通在Linux系统安装没有区别&#xff0c;但是Linux是arch64位的&#xff0c;而jetson是aarch64位的&#xff0c;所以还是不太一样。 另外一个坑是&#xff1a;购买的创乐博的机器人&#xff0c;已经安装ros&#xff0c;但是安装torh的时候需要apt …

使用Spring实现工厂+策略模式

使用Spring实现工厂策略模式 这里使用发短信业务&#xff1a; 不同短信有不同模板但是发送方法都相同只是发送内同不同 1. 定义短信发送策略接口&#xff1a; //策略接口 public interface SmsTemStrategy {public void sendSms(Map<String,String> params); }2.短信…

【Python】Numpy傅里叶变换总结

文章目录简介fft简介 Fourier变换极其逆变换在数学上的定义如下 F(ω)∫−∞∞f(t)e−iωtdtf(t)π2∫−∞∞F(ω)eiωtdωF(\omega)\int^\infty_{-\infty}f(t)e^{-i\omega t}\text dt\\ f(t)\frac{\pi}{2}\int^\infty_{-\infty}F(\omega)e^{i\omega t}\text d\omega F(ω)∫−…

飞行机器人专栏(八)-- AGX Xavier 通信、控制及视觉应用开发

目录 0. Introduction of Jetson Developer kits 1. 硬件对比 Jetson 模组系列 Jetson AGX Orin 系列 Jetson Orin NX 系列 Jetson AGX Xavier 系列 Jetson Xavier NX 系列 Jetson TX2 系列 Jetson Nano 2. 应用场景 1. Introduction of AGX Xavier Taking Perform…

HTML+PHP+MySQL实现新闻列表模块(1+X Web前端开发中级 例题)——初稿

&#x1f4c4;题目要求 阅读下列说明、效果图、MySQL数据库操作和代码&#xff0c;进行动态网页开发&#xff0c;填写&#xff08;1&#xff09;至&#xff08;15&#xff09;代码。&#x1f9e9;说明 该程序为一个html和php混合的新闻列表模块&#xff0c;使用PHP语言&#x…

【深入浅出Spring6】第九期——Spring对事务的支持

因为很多系统都包含事务&#xff0c;所以Spring提供了相关的Api&#xff0c;属于AOP切面编程的二次封装 那么什么是事务&#xff08;Transaction&#xff09;呢&#xff1f; 指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)在一个业务流程中可能需要多条DML联合执…

【Hack The Box】Linux练习-- OpenAdmin

HTB 学习笔记 【Hack The Box】Linux练习-- OpenAdmin &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月22日&#x1f334; &#x…

Flutter组件--OverflowBox、SizedOverflowBox(子组件超出父组件裁剪)

1.OverflowBox介绍 OverflowBox 允许子控件超出父控件的边界。这个特性主要可以用来实现文字或者按钮角标的. OverflowBox构造函数 const OverflowBox({Key? key,this.alignment Alignment.center,this.minWidth,this.maxWidth,this.minHeight,this.maxHeight,Widget? ch…

java面试强基(8)

String、StringBuffer、StringBuilder 的区别&#xff1f; 可变性 ​ String 是不可变的。StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类&#xff0c;在 AbstractStringBuilder 中也是使用字符数组保存字符串&#xff0c;不过没有使用 final 和 private …

Air780E涂鸦云远程开关-LuatOS

Air780E涂鸦云远程开关-LuatOS 涂鸦智能在远程开关和灯控领域可以算是龙头了&#xff0c;今天就来学习一下&#xff0c;如何接入涂鸦云平台 一、涂鸦云准备 注册账号不写了&#xff0c;自己注册账号即可 1、创建产品 点击产品->极速智能化->产品开发页面的创建产品 …

JDK动态代理

可以针对一些不特定的类或者一些不特定的方法进行代理 可以在程序运行时动态变化代理规则 代理类在程序运行时才创建代理模式成为动态代理 代理类并不是在Java代码中定义好的 而是在程序运行时根据在Java代码中指示动态生成的 Proxy JDK动态代理 面向接口 import java.lang.r…

前后端分离页面(从数据库到前端、后端手把手教你如何搭建 -- 功能实现:增加查询)

目录 一、准备条件 前台 后台 二、数据库设计 建议不要导入&#xff0c;导入有乱码的风险&#xff1b;新建查询运行下面代码更快捷。 三、导入前端项目 导入后使用命令行打开黑窗口&#xff0c;测试一下有没有npm -v环境 如果出现命令无效&#xff0c;就使用管理员身份打开…

SpringAOP(1)-spring源码详解(六)

上篇文章说了spring监听器&#xff0c;监听event&#xff0c;先把事件全部存储在多播器&#xff0c;通过publicEvent来触发多播器发送事件&#xff0c;他可以通过代码注解&#xff0c;或者实现接口的方式来实现事件监听&#xff0c;达到解耦和异步的效果。 Spring监听器-sprin…

Jupyter介绍和安装使用

一. 前言&#xff1a; 最近&#xff0c;小熙发现了一个有意思的工具Jupyter notebook&#xff0c;刚上手熟悉了下&#xff0c;特此记录分享。 二. 简介&#xff1a; 简单介绍并说下用途&#xff1a; Jupyter Notebook是一款开源的web应用&#xff0c;支持markdown编辑&#…

Windows为什么使用广播机制更新信息?

很多 Windows 信息通知机制是基于广播消息&#xff0c;例如&#xff0c;DDE&#xff0c;WM_FONTCHANGE 消息和系统设置的修改等。 当我们知道&#xff0c;消息的广播可能导致系统由于某个窗口停止处理消息而导致整个系统停止时&#xff0c;为什么我们还是使用广播呢&#xff1f…

【解决】VSCode编写C++自定义头文件undefined reference异常问题

最近在使用VSCode编写C程序过程中&#xff0c;自定义一个 *.h 文件&#xff0c;其中包含了函数声明&#xff0c;在主函数中引入头文件后&#xff0c;调用头文件对应C文件中的方法&#xff0c;出现了undefined reference问题&#xff0c;这个主要是配置没有正确造成的&#xff0…