goframe项目详解(基于开源项目 易理解)(二)

上一篇文章介绍了goframe开源项目下app/dao/interal文件夹下的category.go源代码,然后在app/dao目录下还有一个category.go是用来调用之前的代码的,今天我们来讲解一下这个

上一篇文章的代码:

package internal

import (
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/frame/gmvc"
)

// CategoryDao is the manager for logic model data accessing and custom defined data operations functions management.
type CategoryDao struct {
	gmvc.M                 // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
	C      categoryColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
	DB     gdb.DB          // DB is the raw underlying database management object.
	Table  string          // Table is the underlying table name of the DAO.
}

// CategoryColumns defines and stores column names for table gf_category.
type categoryColumns struct {
	Id          string // 分类ID,自增主键
	ContentType string // 内容类型:topic, ask, article, reply
	Key         string // 栏目唯一键名,用于程序部分场景硬编码,一般不会用得到
	ParentId    string // 父级分类ID,用于层级管理
	UserId      string // 创建的用户ID
	Name        string // 分类名称
	Sort        string // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶
	Thumb       string // 封面图
	Brief       string // 简述
	Content     string // 详细介绍
	CreatedAt   string // 创建时间
	UpdatedAt   string // 修改时间
}

// NewCategoryDao creates and returns a new DAO object for table data access.
func NewCategoryDao() *CategoryDao {
	columns := categoryColumns{
		Id:          "id",
		ContentType: "content_type",
		Key:         "key",
		ParentId:    "parent_id",
		UserId:      "user_id",
		Name:        "name",
		Sort:        "sort",
		Thumb:       "thumb",
		Brief:       "brief",
		Content:     "content",
		CreatedAt:   "created_at",
		UpdatedAt:   "updated_at",
	}
	return &CategoryDao{
		C:     columns,
		M:     g.DB("default").Model("gf_category").Safe(),
		DB:    g.DB("default"),
		Table: "gf_category",
	}
}

上一篇好像后面的返回值的具体解析没写

  • C 字段被赋值为 columns 变量。
  • M: g.DB("default").Model("gf_category").Safe(),

g.DB("default") 表示获取名为 "default" 的数据库连接对象。

.Model("gf_category") 用于指定要操作的数据表为 "gf_category"。通过这种方式,可以针对特定的数据表进行后续的数据库操作。增刪改查的方法叫做Model,传入的参数,就是这个数据表是gf_category

.Safe() 可能是用于增强数据库操作的安全性或添加一些额外的处理逻辑。具体的行为可能取决于 g.DB 或相关库的实现。

综合起来,g.DB("default").Model("gf_category").Safe() 这一行代码的目的是获取一个与 "default" 数据库连接相关联的、针对 "gf_category" 表的经过特定处理(可能是安全处理)的模型对象,并将其赋值给 M 字段。这样,在后续的代码中,可以使用 M 来执行针对 "gf_category" 表的各种数据库操作,例如查询、插入、更新和删除等,同时可能享有由 .Safe() 提供的额外安全性或功能特性。

例如,可以使用这个模型对象来执行查询操作:m.Find(&results) ,这里的 m 就是通过类似上述方式初始化的模型对象,&results 是用于存储查询结果的变量。这样可以方便地对特定表进行数据操作,而无需直接编写原始的 SQL 语句。这种方式通常被称为对象关系映射(ORM),它有助于提高数据库操作的便利性、可维护性和代码的可读性。具体的功能和用法还需要参考相关库或框架的文档和实现细节。

接下来一看,还有一个g.DB

具体来说,g.DB("default") 用于获取(或创建)一个名为 default 的数据库连接。通过这种方式,可以在代码的不同部分方便地使用同一个数据库连接来进行不同的操作。

第一个 g.DB("default") 可能用于创建或获取与数据库的连接,并将其赋值给 DB 属性,以便在其他地方使用该连接进行数据库操作。

第二个 g.DB("default").Model("gf_category").Safe() 则是基于 default 数据库连接创建了一个数据模型(Model),并使用 Safe() 方法可能是为了确保某种安全或特定的操作模式。

两个 g.DB("default") 的调用可能没有本质区别。

g.DB("default").Model("gf_category").Safe() 是用于获取针对特定表 gf_category 的经过某种安全处理的模型,并将其赋值给 M 。

g.DB("default") 只是获取数据库连接对象,并将其赋值给 DB 。

那么问题是:可不可以只写一个?

理论上是可以只写一个 g.DB("default") ,然后基于这个结果进行后续操作的,但这取决于 g.DB 方法的具体实现和代码的整体逻辑。

如果 g.DB("default") 每次调用返回的是一个新的独立对象,且后续的 Model 和 Safe 方法不会对这个对象的状态产生影响,那么就不能只写一个。

但如果 g.DB("default") 每次调用返回的是同一个共享对象,或者返回的对象的状态变化不会影响后续操作,那么就可以只写一个,例如:

db := g.DB("default")
return &CategoryDao{
    C:     columns,
    M:     db.Model("gf_category").Safe(),
    DB:    db,
    Table: "gf_category",
}

虽然它们都是从 default 数据库获取相关的资源,但用途和所得到的结果是不同的。一个是获取特定表的模型,一个是获取数据库连接本身。具体的差异还需要参考 g.DB 以及相关方法的具体实现逻辑和文档说明。

这样的设计可以提高代码的可读性和可维护性,使得在处理与特定数据库表(gf_category)相关的操作时,能够清晰地指定使用的数据库连接和数据模型。同时,将数据库连接存储在返回的对象中,方便在需要进行数据库操作的其他地方直接使用,而无需重复获取数据库连接

本篇文章要讲解的代码:

package dao

import (
	"focus/app/dao/internal"
)

// categoryDao is the manager for logic model data accessing and custom defined data operations functions management.
// You can define custom methods on it to extend its functionality as you wish.
type categoryDao struct {
	*internal.CategoryDao
}

var (
	// Category is globally public accessible object for table gf_category operations.
	Category categoryDao
)

func init() {
	Category = categoryDao{
		internal.NewCategoryDao(),
	}
}

第一段代码位于 internal 包中,定义了 CategoryDao 结构体及其相关的列信息结构体和创建函数

第二段代码位于 dao 包中,定义了一个新的 categoryDao 结构体,它嵌入了 internal 包中的 CategoryDao 。然后通过 Category 变量创建并初始化了这个 categoryDao 结构体,并使用了 internal 包中提供的 NewCategoryDao 函数来进行初始化。

总的来说,dao 包中的代码依赖于 internal 包中的代码来实现具体的数据访问对象,并在其基础上进行了进一步的封装或扩展(尽管目前看起来只是简单的嵌入),以便在整个项目中更方便地使用和管理与特定数据表相关的操作。

这里插入一个小插曲:

在 Go 语言中,嵌入结构体的概念与 Java 中的继承有一些相似之处,但也有重要的区别。

相似点:

  • 嵌入结构体可以让外部结构体访问嵌入结构体的字段和方法,就像继承可以让子类访问父类的成员一样。

不同点:

  • Go 语言的嵌入结构体没有像 Java 继承那样的显式的继承关系声明。
  • 在 Go 中,嵌入结构体更强调组合和代码复用,而 Java 的继承更强调类之间的层次关系和“是一种”的关系。

总的来说,虽然嵌入结构体和 Java 的继承有相似性,但它们在语言设计和使用方式上存在差异。

扯远了,我们来分析今天的代码

先说结论,今天的代码的作用:

定义了一个名为 categoryDao 的结构体类型,并在其中嵌入了 *internal.CategoryDao 。

然后,定义了一个全局可访问的 categoryDao 类型的变量 Category (首字母大写了),并在 init 函数中对其进行了初始化,初始化时调用了 internal.NewCategoryDao() 来创建。

// Fill with you ideas below. 这行注释表示您可以在下面添加自己的想法或自定义的逻辑。

import (
	"focus/app/dao/internal"
)

引入上一篇文章的代码

type categoryDao struct {
	*internal.CategoryDao
}

type categoryDao struct { *internal.CategoryDao } 这种形式是嵌套结构体。没有给嵌入的结构体字段前添加类似 M 这样的字段名,这是合法的,也可以加上 在前面加一个字段名,那么什么时候可以加字段名什么时候不加呢?那么有没有想过,嵌套结构体的字段名什么时候加上,什么时候不加上呢?

加上字段名的情况:

  1. 当存在多个嵌套结构体,且它们可能包含相同名称的字段时,为了避免混淆,应该加上字段名来明确区分。

  2. 代码的逻辑比较复杂,需要清晰地表明字段的归属和来源,以提高代码的可读性和可维护性。

  3. 团队或项目的编码规范要求加上字段名,以保持代码风格的一致性。

可以省略字段名的情况:

  1. 嵌套结构体比较简单,且字段名具有明确的语义,不会与外层结构体或其他嵌套结构体的字段产生混淆。

  2. 为了使代码更加简洁,并且在当前上下文下,字段的含义和归属是清晰易懂的。

咱们接着往底下看

var (
	// Category is globally public accessible object for table gf_category operations.
	Category categoryDao
)

这段代码声明了一个全局变量 Category ,其类型为 categoryDao 。

注释表明这个变量是全局可公开访问的对象,用于对名为 gf_category 的表进行操作。

 为什么这是一个全局变量呢?因为首字母大写,那么什么是局部变量呢

举个例子:

package main

// 这是一个全局变量,可导出(因为首字母大写)
var GlobalVar int

func main() {
    // 这是一个局部变量
    var localVar int
}

 

在 Go 语言中,通常通过首字母大写来表示一个标识符(包括变量、函数、结构体字段等)是可导出的(即可被其他包访问),而首字母小写表示是私有(仅在当前包内可见)。

但仅仅通过大写字母来判断一个变量是否为全局变量并不完全准确。一个变量是否为全局变量,更准确的判断依据是其声明的位置,即不在任何函数内部声明的变量通常被认为是全局变量。

首字母大写可以表明其可被其他包访问,但要判断是否为全局变量,还需要看其声明的位置。

func init() {
	Category = categoryDao{
		internal.NewCategoryDao(),
	}
}

这段代码中的 init 函数用于在包被初始化时执行一些初始化操作

在这个 init 函数中,将之前声明的全局变量 Category 进行了初始化,通过创建一个新的 categoryDao 实例,并将其赋值给 Category 变量。这个新的实例是通过调用 internal.NewCategoryDao() 来创建的。

总结下来,你看行云流水,定义了一个名为 categoryDao 的结构体,声明了一个该类型的全局变量 Category ,并在 init 函数中对其进行了初始化。

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值