jack 3 kuukautta sitten
sitoutus
4542fba0a8
12 muutettua tiedostoa jossa 672 lisäystä ja 0 poistoa
  1. 0 0
      animals/cat.go
  2. 0 0
      animals/dog.go
  3. 0 0
      animals/robodog.go
  4. 599 0
      answer.md
  5. 0 0
      data/animals.json
  6. 0 0
      data/remaining.json
  7. 45 0
      doc.md
  8. 3 0
      go.mod
  9. 1 0
      main.go
  10. 14 0
      shelter/animal.go
  11. 10 0
      shelter/registry.go
  12. 0 0
      shelter/shelter.go

+ 0 - 0
animals/cat.go


+ 0 - 0
animals/dog.go


+ 0 - 0
animals/robodog.go


+ 599 - 0
answer.md

@@ -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
+```

+ 0 - 0
data/animals.json


+ 0 - 0
data/remaining.json


+ 45 - 0
doc.md

@@ -0,0 +1,45 @@
+# 动物收容所管理系统设计方案
+
+本方案根据用户需求,设计并实现一个可扩展的动物收容所管理系统。系统遵循开闭原则,支持插件式扩展新动物类型,无需修改核心代码。详细设计和代码结构如下。
+
+---
+
+## 目录结构
+
+```
+.
+├── main.go
+├── shelter
+│   ├── shelter.go      // Shelter 结构体与核心业务逻辑
+│   ├── animal.go       // Animal 接口定义
+│   └── registry.go     // 动物类型注册机制
+├── animals
+│   ├── dog.go          // 狗实现示例
+│   ├── cat.go          // 猫实现示例
+│   └── robodog.go      // 机器人宠物示例插件
+└── data
+    ├── animals.json    // 批量导入示例数据
+    └── remaining.json  // 导出剩余动物数据
+```
+
+---
+
+覆盖率应不低于80%。
+
+## 依赖
+- Go 1.22+
+
+---
+
+该设计方案:
+
+- **严格遵守开闭原则**,新增动物无需改动主程序或 shelter 包。
+- **插件式注册机制**,通过反射和自注册实现类型动态创建。
+- **JSON动态序列化反序列化**,避免类型硬编码。
+- **线程安全**,适合并发环境。
+- **文档齐全**,代码注释清晰。
+- **性能优良**,批量导入 10000 条数据可优化为并发操作(可扩展)。
+
+---
+
+如需完整示例代码、测试、基准和 Makefile 等,请告知。以上即为按照用户需求的详细设计与关键代码示范。

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module animal_shelter
+
+go 1.22.2

+ 1 - 0
main.go

@@ -0,0 +1 @@
+package main

+ 14 - 0
shelter/animal.go

@@ -0,0 +1,14 @@
+package shelter
+
+type Animal interface {
+	ID() string
+	Name() string
+	Age() int
+	Weight() float64
+	Breed() string
+	DailyFoodGrams() int
+	DailyExerciseMinutes() int
+	IsAdopted() bool
+	SetAdopted(bool)
+	Type() string
+}

+ 10 - 0
shelter/registry.go

@@ -0,0 +1,10 @@
+package shelter
+
+import "sync"
+
+type AnimlConstructor func() Animal
+
+var (
+	registrMu sync.RWMutex
+	registry  = make(map[string]AnimlConstructor)
+)

+ 0 - 0
shelter/shelter.go