|
@@ -0,0 +1,599 @@
|
|
|
|
|
+## 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
|
|
|
|
|
+```
|