Golang中如何管理数百个结构体?

Golang中如何管理数百个结构体? 所有示例最多只使用了3个结构体。我找不到任何展示如何管理数百个结构体的示例或教程。

type Person struct {
	FirstName, LastName string
	Age       int
}

我已经设法在数据库中存储和获取许多SQL查询,但这似乎不适用于结构体。是这样吗?

提前感谢。

12 回复

嗨,Silbert,你能详细说明一下你提到的管理结构体是什么意思吗?你是在问如何从数据库中存储/检索它们,还是如何为所有这些结构体组织源代码,等等?

更多关于Golang中如何管理数百个结构体?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那么,对于你原帖中的例子:

Sibert:

type Person struct {
    FirstName, LastName string
    Age int
}

你能展示一下你是如何使用它的吗?

你的问题域中有多少结构体类型?100个似乎有点多。

有100张表对应不同的结构体。考虑到使用连接(JOIN)的查询,我估计至少是这个数量的两倍。

我知道可以使用更多的包,但有哪些选择呢?

正如我们之前所说,对于查询操作,完全不需要使用结构体。如果你真的不想使用结构体,你仍然可以使用 map[string]interface{},但你确实不应该这样做,使用它远比创建所需的结构体来完成正确操作要糟糕得多。

NobbZ:

这并不是一个硬性要求,尽管它能让事情变得更简单……

NobbZ:

只需将你的数据组织到包中,或者当查询仅需返回一个简单的单一值时,完全可以省略结构体……

那么有哪些选项呢?除了创建更多的包?

Sibert:

如果有数百个SQL语句对应不同的结构体,将100多个结构体放在一个main.go文件中似乎有些令人难以承受。

你的问题域中有多少种结构体类型?100个似乎有点过多。无论如何,如果结构体的数量增加,你总是可以将其拆分到一个单独的文件中,并将与该结构体相关的每个函数都放在该文件中。首先,随着main.go文件的增长,将你的结构体组织到不同的文件中;然后,随着文件数量的增长,再将你的结构体组织到不同的包中。

[quote=“ditchx, post:11, topic:17473”] 您或许可以为他们自动生成结构体。可以使用类似 db2struct 这样的工具,或者自己动手编写一个。 [/quote]

感谢您的建议。您可以将我的问题归纳为四点:

  1. 创建结构体(类似 db2struct?)
  2. 存储结构体(用包?)
  3. make([]Struct)(100 个结构体 = 100 次查询?)
  4. 扫描结构体(100 次查询?)

我认为 sqlx 可以解决第 4 点,但我还没有评估过它……

skillian: 你能澄清一下你所说的管理结构体是什么意思吗?你是想问如何在数据库中存储/检索它们,还是如何为所有这些结构体保持源代码的组织性,

从 SQL 数据库获取数据时,你需要有一个结构体。对吧?如果有成百上千个使用不同结构体的 SQL 查询,把 100 多个结构体都放在一个 main.go 文件里似乎让人难以招架。我曾想过将结构体与查询语句存放在一起,但这似乎是一条单行道,因为结构体必须定义在全局层面。

因此,我正在寻找一种管理数百个结构体的方法,以便能够轻松地维护它们。

如果能够将结构体与查询语句存放在一起,维护起来应该会更容易。我想……

有什么想法吗?

Sibert:

从 SQL 数据库获取数据时,你需要有一个结构体

这并不是必须的,尽管它会让事情变得更容易……

Sibert:

如果有成百上千个使用不同结构体的 SQL 语句,把 100 多个结构体都放在一个 main.go 文件里似乎让人难以招架

一般来说,在 main.go 文件中拥有超过大约 100 行代码似乎就让人难以招架了……通常我只是准备环境,然后调用其他包,或者启动执行真正繁重任务的 goroutine。

Sibert:

有什么想法吗?

只需将你的数据组织到不同的包中,或者当查询只需要返回一个简单的单一值时,完全可以省略结构体……

skillian:

你能展示一下你是怎么使用的吗?

这是我的简单结构体:

type Rec struct {
	Id      int
	Subject string
}

以及使用该结构体的代码:

func Get(query string) interface{} {

	rows, err := db.Query(query)
	if err != nil {
		fmt.Println(err)
	}

	defer rows.Close()

	list := make([]Rec, 0)
	for rows.Next() {
		rec := Rec{}
		err := rows.Scan(&rec.Id, &rec.Subject)
		if err != nil {
			fmt.Println(err)
		}
		list = append(list, rec)
	}

	err = rows.Err()
	if err != nil {
		fmt.Println(err)
	}

	return list
}

您或许可以自动为它们生成结构体。可以使用像 db2struct 这样的工具,或者自己编写一个。

与其为连接查询创建一个单独的结构体,您可以选择在结构体中包含一个成员,该成员指向代表连接表的结构体。

假设您有一个用于 users 表的 User 结构体,一个用于 profiles 表的 Profile 结构体。

type User struct {
   Username string
   Profile *Profile
}

type Profile struct {
  About string
  Owner *User
}

因此,连接这两个表并将结果读入结构体看起来会像这样:

    q := "SELECT users.username, profiles.about FROM users INNER JOIN profiles ON users.user_id=profiles.user_id"


	rows, err := db.Query(q)
	if err != nil {
		fmt.Println(err)
	}

	defer rows.Close()

	list := make([]User, 0)
	for rows.Next() {
		u := User{Profile: &Profile{}}
		err := rows.Scan(&u.Username, &u.Profile.About)
		if err != nil {
			fmt.Println(err)
		}
		list = append(list, u)
	}

	err = rows.Err()
	if err != nil {
		fmt.Println(err)
	}

	return list

在Go中管理数百个结构体时,通常需要结合设计模式和代码组织策略。以下是几种实用的方法:

1. 使用包和模块化组织

// models/person.go
package models

type Person struct {
    FirstName, LastName string
    Age int
}

// models/employee.go  
package models

type Employee struct {
    Person
    EmployeeID string
    Department string
}

// models/customer.go
package models

type Customer struct {
    Person
    CustomerID string
    Tier       string
}

2. 工厂模式创建实例

// factory/struct_factory.go
package factory

import "your-project/models"

func CreatePerson(firstName, lastName string, age int) models.Person {
    return models.Person{
        FirstName: firstName,
        LastName: lastName,
        Age: age,
    }
}

func CreateEmployee(person models.Person, empID, dept string) models.Employee {
    return models.Employee{
        Person:     person,
        EmployeeID: empID,
        Department: dept,
    }
}

3. 使用接口统一管理

// interfaces/entity.go
package interfaces

type Entity interface {
    GetID() string
    Validate() error
    ToMap() map[string]interface{}
}

// models/person.go 实现接口
func (p Person) GetID() string {
    return fmt.Sprintf("%s-%s", p.FirstName, p.LastName)
}

func (p Person) Validate() error {
    if p.Age < 0 {
        return errors.New("age cannot be negative")
    }
    return nil
}

4. 注册表模式管理结构体

// registry/struct_registry.go
package registry

import "reflect"

var structRegistry = make(map[string]reflect.Type)

func RegisterStruct(name string, typ reflect.Type) {
    structRegistry[name] = typ
}

func CreateInstance(name string) interface{} {
    if typ, exists := structRegistry[name]; exists {
        return reflect.New(typ).Interface()
    }
    return nil
}

// 初始化时注册
func init() {
    RegisterStruct("Person", reflect.TypeOf(models.Person{}))
    RegisterStruct("Employee", reflect.TypeOf(models.Employee{}))
}

5. 使用代码生成工具

// 使用go:generate自动生成代码
//go:generate go run github.com/vektra/mockery/v2 --name=Entity --output=mocks

// 或使用结构体标签自动生成验证、序列化代码
type Person struct {
    FirstName string `json:"first_name" validate:"required"`
    LastName  string `json:"last_name" validate:"required"`
    Age       int    `json:"age" validate:"min=0,max=150"`
}

6. 数据库映射示例

// repository/person_repo.go
package repository

import (
    "database/sql"
    "your-project/models"
)

type PersonRepository struct {
    db *sql.DB
}

func (r *PersonRepository) GetAll() ([]models.Person, error) {
    rows, err := r.db.Query("SELECT first_name, last_name, age FROM persons")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var persons []models.Person
    for rows.Next() {
        var p models.Person
        if err := rows.Scan(&p.FirstName, &p.LastName, &p.Age); err != nil {
            return nil, err
        }
        persons = append(persons, p)
    }
    return persons, nil
}

// 批量操作
func (r *PersonRepository) BatchCreate(persons []models.Person) error {
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }

    stmt, err := tx.Prepare("INSERT INTO persons (first_name, last_name, age) VALUES (?, ?, ?)")
    if err != nil {
        return err
    }
    defer stmt.Close()

    for _, p := range persons {
        _, err := stmt.Exec(p.FirstName, p.LastName, p.Age)
        if err != nil {
            tx.Rollback()
            return err
        }
    }
    
    return tx.Commit()
}

7. 使用依赖注入容器

// container/container.go
package container

import "sync"

type Container struct {
    services map[string]interface{}
    mu       sync.RWMutex
}

func (c *Container) Register(name string, service interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.services[name] = service
}

func (c *Container) Get(name string) interface{} {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.services[name]
}

// 使用
var container = &Container{
    services: make(map[string]interface{}),
}

func init() {
    container.Register("PersonService", &services.PersonService{})
    container.Register("EmployeeService", &services.EmployeeService{})
}

管理数百个结构体的关键在于良好的代码组织和架构设计。通过合理的包划分、接口抽象和设计模式应用,可以有效管理大量结构体。数据库操作与结构体管理并不冲突,可以使用Repository模式将两者结合。

回到顶部