下载工作台
Go 编程实战

结构体与方法

试读上半部分 · 解锁后可读全文

第 5 章 · 结构体与方法

本章目标:定义 struct、字段标签、方法 receiver(值 vs 指针);实现与全站对齐的 Productslugis_published、价格)与 OrderLine;理解 Go 组合优于继承;掌握 JSON 序列化基础;对照 java-dev ch05 OOP 与 python-dev ch05 dataclass。

学时建议:3~4 小时(含 2 小时跟练)

前置:完成 go-dev ch04;建议已学 java-dev ch05


5.1 场景说明:商品与订单行模型

toolkit-go~/learn-go/toolkit-go,模块 example.com/toolkit-go)在 ch11 将从 JSON/CSV 读商品与日志;字段与 django-web / gin-web api-go-demo 对齐:

字段类型说明
SlugstringURL 友好标识,唯一
Namestring展示名称
Priceint64,如 6800 = 68.00 元
IsPublishedbool是否上架(非 is_active
internal/model/
├── product.go
├── order_line.go
├── order.go
└── catalog.go          # 嵌入组合示例

5.2 定义结构体与 JSON 标签

// internal/model/product.go
package model

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	ID          uint   `json:"id"`
	Slug        string `json:"slug"`
	Name        string `json:"name"`
	Price       int64  `json:"price"` // 分
	IsPublished bool   `json:"is_published"`
}

func (p Product) PriceYuan() float64 {
	return float64(p.Price) / 100
}

func (p Product) DisplayPrice() string {
	return fmt.Sprintf("%.2f", p.PriceYuan())
}

func (p Product) IsOnSale() bool {
	return p.IsPublished && p.Price > 0
}
标签用途
` json:"slug" `encoding/json 序列化字段名
` json:"price,omitempty" `零值省略(选修)
导出字段首字母大写才可被外部包 / JSON 访问
JavaGoPython
class Producttype Product struct@dataclass class Product
getter/setter方法 + 导出字段属性
Jackson 注解struct tagpydantic Field

5.3 值 receiver vs 指针 receiver

// 指针 receiver:可修改字段
func (p *Product) Publish() {
	p.IsPublished = true
}

func (p *Product) Unpublish() {
	p.IsPublished = false
}

func (p *Product) SetPriceCents(cents int64) error {
	if cents < 0 {
		return fmt.Errorf("negative price")
	}
	p.Price = cents
	return nil
}

func (p *Product) DiscountPercent(pct int) error {
	if pct < 0 || pct > 100 {
		return fmt.Errorf("invalid discount: %d", pct)
	}
	discounted := p.Price * int64(100-pct) / 100
	p.Price = discounted
	return nil
}
receiver何时用
(p Product)只读、小对象
指针 (p *Product)修改字段、避免大 struct 拷贝
一致性建议:若任一方法用指针 receiver,同一类型的方法尽量统一用指针,避免混用导致困惑。Go 会自动对值调用指针方法(取地址),反之亦然。

5.4 OrderLine 与 Order 模型

// internal/model/order_line.go
package model

type OrderLine struct {
	Product  Product
	Quantity int
}

func (l OrderLine) SubtotalCents() int64 {
	return l.Product.Price * int64(l.Quantity)
}

// internal/model/order.go
type Order struct {
	ID    uint
	Lines []OrderLine
}

func (o Order) TotalCents() int64 {
	var total int64
	for _, ln := range o.Lines {
		total += ln.SubtotalCents()
	}
	return total
}

func (o Order) ItemCount() int {
	var n int
	for _, ln := range o.Lines {
		n += ln.Quantity
	}
	return n
}

5.5 构造辅助函数 NewProduct

Go 无构造器关键字,惯用 NewXxx 返回 (*Xxx, error)

func NewProduct(slug, name string, priceCents int64) (*Product, error) {
	if slug == "" {
		return nil, fmt.Errorf("slug required")
	}
	if priceCents < 0 {
		return nil, fmt.Errorf("invalid price")
	}
	return &Product{
		Slug:        slug,
		Name:        name,
		Price:       priceCents,
		IsPublished: false,
	}, nil
}
JavaGo
new Product(...)NewProduct(...)
构造器 overload不同函数名或 Functional Options(进阶)

5.6 嵌入 struct(组合)

Go 无类继承,用嵌入复用字段(promoted fields):

// internal/model/catalog.go
package model

type Timestamped struct {
	CreatedAt int64 // Unix 秒
	UpdatedAt int64
}

type CatalogProduct struct {
	Product
	Timestamped
	Tags []string
}

func NewCatalogProduct(p Product, tags []string) CatalogProduct {
	now := int64(1700000000) // 教学固定值
	return CatalogProduct{
		Product:     p,
		Timestamped: Timestamped{CreatedAt: now, UpdatedAt: now},
		Tags:        tags,
	}
}
cp := NewCatalogProduct(Product{Slug: "go-book", Name: "Go 实战", Price: 6800}, []string{"book", "go"})
_ = cp.Slug      // promoted:直接访问嵌入字段
_ = cp.CreatedAt

对照 java-dev:extends Product → Go embedding + interface(ch06)。


5.7 JSON 往返

func ProductFromJSON(data []byte) (Product, error) {
	var p Product
	err := json.Unmarshal(data, &p)
	return p, err
}

func (p Product) ToJSON() ([]byte, error) {
	return json.Marshal(p)
}

func FilterPublished(products []Product) []Product {
	out := make([]Product, 0, len(products))
	for _, p := range products {
		if p.IsPublished {
			out = append(out, p)

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。