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
}
说明:该接口为系统基础,所有动物类型均需实现,保证统一管理。
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注册,主程序无需修改。
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负责管理所有动物,线程安全,支持批量导入导出、统计算法、领养与退回操作。
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{} })
}
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{} })
}
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{} })
}
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"}
]
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,超出本文范围。
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("导出数据缺失猫")
}
}
测试覆盖建议:应覆盖注册、创建、领养、退回、导入导出、统计等核心逻辑。
# 动物收容所管理系统
## 介绍
本系统用于管理动物收容所,支持多种动物类型插件式扩展。支持批量导入、导出、领养与退回功能。
## 运行方法
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 ```