## 1. Animal 接口定义(`shelter/animal.go`) ```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`) ```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`) ```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`) ```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`) ```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`) ```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`) ```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`) ```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`) ```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 简要说明(示例) ```markdown # 动物收容所管理系统 ## 介绍 本系统用于管理动物收容所,支持多种动物类型插件式扩展。支持批量导入、导出、领养与退回功能。 ## 运行方法 ```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 ```