answer.md 16 KB

1. Animal 接口定义(shelter/animal.go

package shelter

// Animal 接口规定所有动物必须实现的方法
type Animal interface {
    // 唯一 ID,同一收容所内不可重复
    ID() string
    // 人类可读的名字
    Name() string
    // 年龄(岁)
    Age() int
    // 体重(kg)
    Weight() float64
    // 品种
    Breed() string
    // 每日所需食物重量(g)
    DailyFoodGrams() int
    // 每日所需运动时长(分钟)
    DailyExerciseMinutes() int
    // 是否已被领养
    IsAdopted() bool
    // 设置领养状态
    SetAdopted(bool)
    // 返回 JSON 序列化所需的类型名字符串(用于反序列化识别)
    Type() string
}

说明:该接口为系统基础,所有动物类型均需实现,保证统一管理。


2. 注册机制(shelter/registry.go

package shelter

import (
    "errors"
    "sync"
)

// AnimalConstructor 定义动物实例构建函数类型
type AnimalConstructor func() Animal

var (
    registryMu sync.RWMutex
    registry   = make(map[string]AnimalConstructor)
)

// Register 注册新的动物类型构造器
// typeName: 动物类型名,构造器: 返回空实例用于反序列化
func Register(typeName string, constructor AnimalConstructor) {
    registryMu.Lock()
    defer registryMu.Unlock()
    registry[typeName] = constructor
}

// Create 实例化指定类型的动物,若类型未注册则返回错误
func Create(typeName string) (Animal, error) {
    registryMu.RLock()
    defer registryMu.RUnlock()
    constructor, ok := registry[typeName]
    if !ok {
        return nil, errors.New("unknown animal type: " + typeName)
    }
    return constructor(), nil
}

// GetRegisteredTypes 返回所有已注册的动物类型名列表
func GetRegisteredTypes() []string {
    registryMu.RLock()
    defer registryMu.RUnlock()
    keys := make([]string, 0, len(registry))
    for k := range registry {
        keys = append(keys, k)
    }
    return keys
}

说明:该机制实现插件式扩展,新动物只需调用 Register 注册,主程序无需修改。


3. Shelter 结构体和核心逻辑(shelter/shelter.go

package shelter

import (
    "encoding/json"
    "errors"
    "io"
    "os"
    "sync"
)

// Shelter 结构体,管理收容所所有动物及业务逻辑
type Shelter struct {
    mu      sync.RWMutex
    animals map[string]Animal // key: 动物唯一ID
}

// NewShelter 创建新的 Shelter 实例
func NewShelter() *Shelter {
    return &Shelter{
        animals: make(map[string]Animal),
    }
}

// AddAnimal 添加一只动物到收容所
// 若 ID 已存在返回错误
func (s *Shelter) AddAnimal(a Animal) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, exists := s.animals[a.ID()]; exists {
        return errors.New("animal with ID " + a.ID() + " already exists")
    }
    s.animals[a.ID()] = a
    return nil
}

// Adopt 领养动物,动物从日常统计中排除,但数据保留
func (s *Shelter) Adopt(id string) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    a, ok := s.animals[id]
    if !ok {
        return errors.New("animal not found: " + id)
    }
    a.SetAdopted(true)
    return nil
}

// Return 退回动物,重新参与日常统计
func (s *Shelter) Return(id string) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    a, ok := s.animals[id]
    if !ok {
        return errors.New("animal not found: " + id)
    }
    a.SetAdopted(false)
    return nil
}

// TotalDailyFood 计算所有未被领养动物每日所需食物总重量(克)
func (s *Shelter) TotalDailyFood() int {
    s.mu.RLock()
    defer s.mu.RUnlock()
    total := 0
    for _, a := range s.animals {
        if !a.IsAdopted() {
            total += a.DailyFoodGrams()
        }
    }
    return total
}

// TotalDailyExercise 计算所有未被领养动物每日所需运动总时长(分钟)
func (s *Shelter) TotalDailyExercise() int {
    s.mu.RLock()
    defer s.mu.RUnlock()
    total := 0
    for _, a := range s.animals {
        if !a.IsAdopted() {
            total += a.DailyExerciseMinutes()
        }
    }
    return total
}

// FilterByBreed 根据品种筛选动物,返回切片(包含已领养)
func (s *Shelter) FilterByBreed(breed string) []Animal {
    s.mu.RLock()
    defer s.mu.RUnlock()
    result := []Animal{}
    for _, a := range s.animals {
        if a.Breed() == breed {
            result = append(result, a)
        }
    }
    return result
}

// ImportFromJSON 从 JSON 文件批量导入动物
// 文件格式为:[{type, id, name, age, weight, breed}, ...]
// 根据 type 字段动态创建具体动物实例并填充数据
func (s *Shelter) ImportFromJSON(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    return s.ImportFromReader(file)
}

// ImportFromReader 通过 io.Reader 导入动物数据(方便测试)
func (s *Shelter) ImportFromReader(r io.Reader) error {
    // 先定义辅助结构只解析 type 字段,后续动态反序列化
    var rawList []map[string]interface{}
    dec := json.NewDecoder(r)
    if err := dec.Decode(&rawList); err != nil {
        return err
    }

    for _, raw := range rawList {
        typeVal, ok := raw["type"].(string)
        if !ok {
            return errors.New("missing or invalid 'type' field")
        }
        animal, err := Create(typeVal)
        if err != nil {
            return err
        }

        // 将 map 转为 JSON 再反序列化到具体实例
        b, err := json.Marshal(raw)
        if err != nil {
            return err
        }
        if err := json.Unmarshal(b, animal); err != nil {
            return err
        }

        if err := s.AddAnimal(animal); err != nil {
            return err
        }
    }
    return nil
}

// ExportToJSON 导出当前所有未被领养动物数据到 JSON 文件
func (s *Shelter) ExportToJSON(filename string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    return s.ExportToWriter(file)
}

// ExportToWriter 导出数据到 io.Writer
func (s *Shelter) ExportToWriter(w io.Writer) error {
    s.mu.RLock()
    defer s.mu.RUnlock()
    var exportList []Animal
    for _, a := range s.animals {
        if !a.IsAdopted() {
            exportList = append(exportList, a)
        }
    }
    enc := json.NewEncoder(w)
    enc.SetIndent("", "  ")
    return enc.Encode(exportList)
}

说明Shelter 负责管理所有动物,线程安全,支持批量导入导出、统计算法、领养与退回操作。


4. 具体动物示例实现

4.1 狗(animals/dog.go

package animals

import (
    "fmt"
    "shelter"
)

// Dog 结构体,实现 Animal 接口
type Dog struct {
    IDField      string  `json:"id"`
    NameField    string  `json:"name"`
    AgeField     int     `json:"age"`
    WeightField  float64 `json:"weight"`
    BreedField   string  `json:"breed"`
    adopted      bool
}

// ID 返回狗的唯一ID
func (d *Dog) ID() string { return d.IDField }

// Name 返回名字
func (d *Dog) Name() string { return d.NameField }

// Age 返回年龄(岁)
func (d *Dog) Age() int { return d.AgeField }

// Weight 返回体重(kg)
func (d *Dog) Weight() float64 { return d.WeightField }

// Breed 返回品种
func (d *Dog) Breed() string { return d.BreedField }

// DailyFoodGrams 计算每日食物需求(g)示例算法:体重 * 30
func (d *Dog) DailyFoodGrams() int {
    return int(d.WeightField * 30)
}

// DailyExerciseMinutes 计算每日运动需求(分钟)示例:年龄 * 20
func (d *Dog) DailyExerciseMinutes() int {
    return d.AgeField * 20
}

// IsAdopted 判断是否被领养
func (d *Dog) IsAdopted() bool { return d.adopted }

// SetAdopted 设置领养状态
func (d *Dog) SetAdopted(adopted bool) { d.adopted = adopted }

// Type 返回类型名
func (d *Dog) Type() string { return "dog" }

// String 格式化输出
func (d *Dog) String() string {
    return fmt.Sprintf("Dog[ID=%s, Name=%s, Age=%d, Weight=%.2f, Breed=%s, Adopted=%v]",
        d.IDField, d.NameField, d.AgeField, d.WeightField, d.BreedField, d.adopted)
}

func init() {
    shelter.Register("dog", func() shelter.Animal { return &Dog{} })
}

4.2 猫(animals/cat.go

package animals

import (
    "fmt"
    "shelter"
)

// Cat 结构体,实现 Animal 接口
type Cat struct {
    IDField      string  `json:"id"`
    NameField    string  `json:"name"`
    AgeField     int     `json:"age"`
    WeightField  float64 `json:"weight"`
    BreedField   string  `json:"breed"`
    adopted      bool
}

// 实现 Animal 接口(同 Dog 类似)
func (c *Cat) ID() string           { return c.IDField }
func (c *Cat) Name() string         { return c.NameField }
func (c *Cat) Age() int             { return c.AgeField }
func (c *Cat) Weight() float64      { return c.WeightField }
func (c *Cat) Breed() string        { return c.BreedField }
func (c *Cat) IsAdopted() bool     { return c.adopted }
func (c *Cat) SetAdopted(a bool)   { c.adopted = a }
func (c *Cat) Type() string         { return "cat" }
func (c *Cat) DailyFoodGrams() int { return int(c.WeightField*25) }
func (c *Cat) DailyExerciseMinutes() int {
    // 例:较活跃,按年龄*30分钟
    return c.AgeField * 30
}
func (c *Cat) String() string {
    return fmt.Sprintf("Cat[ID=%s, Name=%s, Age=%d, Weight=%.2f, Breed=%s, Adopted=%v]",
        c.IDField, c.NameField, c.AgeField, c.WeightField, c.BreedField, c.adopted)
}

func init() {
    shelter.Register("cat", func() shelter.Animal { return &Cat{} })
}

4.3 机器人宠物示例(animals/robodog.go

package animals

import (
    "fmt"
    "shelter"
)

// RoboDog 机器人宠物实现
type RoboDog struct {
    IDField      string  `json:"id"`
    NameField    string  `json:"name"`
    AgeField     int     `json:"age"`
    WeightField  float64 `json:"weight"`
    BreedField   string  `json:"breed"`
    adopted      bool
}

func (r *RoboDog) ID() string           { return r.IDField }
func (r *RoboDog) Name() string         { return r.NameField }
func (r *RoboDog) Age() int             { return r.AgeField }
func (r *RoboDog) Weight() float64      { return r.WeightField }
func (r *RoboDog) Breed() string        { return r.BreedField }
func (r *RoboDog) IsAdopted() bool     { return r.adopted }
func (r *RoboDog) SetAdopted(a bool)   { r.adopted = a }
func (r *RoboDog) Type() string         { return "robodog" }
func (r *RoboDog) DailyFoodGrams() int { return 0 } // 不吃东西
func (r *RoboDog) DailyExerciseMinutes() int {
    // 机器人需要定期维护,设定固定时间
    return 30
}
func (r *RoboDog) String() string {
    return fmt.Sprintf("RoboDog[ID=%s, Name=%s, Age=%d, Weight=%.2f, Breed=%s, Adopted=%v]",
        r.IDField, r.NameField, r.AgeField, r.WeightField, r.BreedField, r.adopted)
}

func init() {
    shelter.Register("robodog", func() shelter.Animal { return &RoboDog{} })
}

5. 示例 JSON 文件(data/animals.json

[
  {"type":"dog","id":"d001","name":"Rex","age":3,"weight":15.5,"breed":"Labrador"},
  {"type":"cat","id":"c001","name":"Mimi","age":2,"weight":4.2,"breed":"Persian"}
]

6. 主程序示例(main.go

package main

import (
    "fmt"
    "log"
    "os"
    "shelter"

    // 动物插件包初始化,自动注册,无需手动调用注册
    _ "animals"        // 注册 dog 和 cat
    _ "animals/robodog" // 机器人宠物插件示例
)

func main() {
    sh := shelter.NewShelter()

    // 1. 批量导入动物
    err := sh.ImportFromJSON("data/animals.json")
    if err != nil {
        log.Fatalf("导入失败: %v", err)
    }
    fmt.Println("成功导入动物。")

    // 2. 打印今日总需食物和运动时间
    totalFood := sh.TotalDailyFood()
    totalExercise := sh.TotalDailyExercise()
    fmt.Printf("今日总需食物:%dg\n", totalFood)
    fmt.Printf("今日总需运动时间:%d分钟\n", totalExercise)

    // 3. 领养一只狗(ID: d001)
    err = sh.Adopt("d001")
    if err != nil {
        log.Printf("领养失败: %v", err)
    } else {
        fmt.Println("成功领养动物 d001。")
    }

    // 4. 导出剩余动物到 remaining.json
    err = sh.ExportToJSON("data/remaining.json")
    if err != nil {
        log.Fatalf("导出失败: %v", err)
    }
    fmt.Println("成功导出剩余动物。")

    // 5. 动态注册并添加一只机器人宠物 RoboDog
    // 这里模拟动态注册,实际插件已init注册了,此处演示动态添加实例
    robodog, err := shelter.Create("robodog")
    if err != nil {
        log.Fatalf("创建 RoboDog 失败: %v", err)
    }
    // 填充字段(断言具体类型)
    if r, ok := robodog.(*animals.RoboDog); ok {
        r.IDField = "r001"
        r.NameField = "RoboRex"
        r.AgeField = 1
        r.WeightField = 10.0
        r.BreedField = "RoboBreed"
    }
    err = sh.AddAnimal(robodog)
    if err != nil {
        log.Fatalf("添加 RoboDog 失败: %v", err)
    }
    fmt.Println("成功添加机器人宠物 RoboDog。")

    // 再次统计
    totalFood = sh.TotalDailyFood()
    totalExercise = sh.TotalDailyExercise()
    fmt.Printf("更新后总需食物:%dg\n", totalFood)
    fmt.Printf("更新后总需运动时间:%d分钟\n", totalExercise)

    // 结束,示例演示完毕
}

注意

  • main.go 通过空导入 _ "animals"_ "animals/robodog" 触发插件注册。
  • 机器人宠物动态注册示例中展示如何通过 shelter.Create 新建实例并添加。
  • 若要实现完全无重启的动态加载,则需结合插件机制或 RPC,超出本文范围。

7. 单元测试示例(shelter/shelter_test.go

package shelter

import (
    "strings"
    "testing"
)

// 测试导入导出
func TestImportExport(t *testing.T) {
    sh := NewShelter()
    jsonData := `
[
  {"type":"dog","id":"d001","name":"Rex","age":3,"weight":15.5,"breed":"Labrador"},
  {"type":"cat","id":"c001","name":"Mimi","age":2,"weight":4.2,"breed":"Persian"}
]
`
    err := sh.ImportFromReader(strings.NewReader(jsonData))
    if err != nil {
        t.Fatalf("导入失败: %v", err)
    }

    if len(sh.animals) != 2 {
        t.Fatalf("导入数量不匹配,期望2,实际%d", len(sh.animals))
    }

    totalFood := sh.TotalDailyFood()
    if totalFood <= 0 {
        t.Error("总食物计算错误")
    }

    err = sh.Adopt("d001")
    if err != nil {
        t.Errorf("领养失败: %v", err)
    }
    adopted := sh.animals["d001"].IsAdopted()
    if !adopted {
        t.Error("领养状态设置失败")
    }

    // 导出测试
    var sb strings.Builder
    err = sh.ExportToWriter(&sb)
    if err != nil {
        t.Errorf("导出失败: %v", err)
    }
    if !strings.Contains(sb.String(), "c001") {
        t.Error("导出数据缺失猫")
    }
}

测试覆盖建议:应覆盖注册、创建、领养、退回、导入导出、统计等核心逻辑。


8. README.md 简要说明(示例)

# 动物收容所管理系统

## 介绍
本系统用于管理动物收容所,支持多种动物类型插件式扩展。支持批量导入、导出、领养与退回功能。

## 运行方法

bash go run main.go


示例会自动加载 `data/animals.json`,导入动物,计算统计,领养动物,导出剩余动物,并动态添加机器人宠物。

## 如何新增动物
1. 创建新动物类型文件(如 `animals/rabbit.go`)。
2. 实现 `shelter.Animal` 接口。
3. 在 `init()` 函数中调用 `shelter.Register("rabbit", func() shelter.Animal { return &Rabbit{} })`。
4. 新动物类型自动加入系统,无需改动核心代码。

## 测试

bash go test ./shelter -cover ```