缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
该书以java语言演绎了常见设计模式
本系列笔记拟采用golang练习之

迪米特法则

迪米特法则(Law of Demeter, LoD)又叫作最少知道原则(Least KnowledgePrinciple, LKP),指一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合。
_

场景

  • TeamLeader每天需要查看未完成的项目任务数
  • TeamLeader指派TeamMember进行任务统计
  • TeamMember提供对Task的汇总方法, 返回未完成的任务数
  • 坏的设计:
    • Leader: 我需要统计未完成任务数
    • Member: 好的, 我可以统计, 但是任务清单在哪里呢?
    • Leader: … 我稍后给你吧
  • 好的设计:
    • Leader: 我需要统计未完成任务数
    • Member: 好的. 任务清单我知道在那里, 我会搞定的
    • Leader: 好兵!

Task.go

定义任务信息, 以及加载任务清单的方法

  1. package law_of_demeter
  2. type TaskStatus int
  3. const OPENING TaskStatus = 0
  4. const DONE TaskStatus = 1
  5. const CANCLED TaskStatus = 2
  6. const DENIED TaskStatus = 3
  7. type Task struct {
  8. iID int
  9. iStatus TaskStatus
  10. }
  11. func NewTask(id int, status TaskStatus) *Task {
  12. return &Task{
  13. id,
  14. status,
  15. }
  16. }
  17. func (me *Task) ID() int {
  18. return me.iID
  19. }
  20. func (me *Task) Status() TaskStatus {
  21. return me.iStatus
  22. }
  23. func LoadTaskList() []*Task {
  24. tasks := make([]*Task, 0)
  25. tasks = append(tasks, NewTask(1, OPENING))
  26. tasks = append(tasks, NewTask(2, DONE))
  27. tasks = append(tasks, NewTask(3, CANCLED))
  28. tasks = append(tasks, NewTask(4, DENIED))
  29. return tasks
  30. }

ITeamLeader.go

定义TeamLeader的接口

  1. package law_of_demeter
  2. type ITeamLeader interface {
  3. CountOpeningTasks() int
  4. }

BadTeamLeader.go

不好的ITeamLeader实现, 同时耦合了Task和BadTeamMember两个类

  1. package law_of_demeter
  2. import "fmt"
  3. type BadTeamLeader struct {
  4. iID int
  5. sName string
  6. }
  7. func (me *BadTeamLeader) CountOpeningTasks() int {
  8. tasks := LoadTaskList()
  9. member := NewBadTeamMember(11, "王Member")
  10. sum := member.countOpeningTasks(tasks)
  11. fmt.Printf("%v CountOpeningTasks, got %v", me.sName, sum)
  12. return sum
  13. }

BadTeamMember.go

不好的示例. 统计任务数的实现, 要求过多的参数, 增加调用方的耦合度和使用难度

  1. package law_of_demeter
  2. type BadTeamMember struct {
  3. iID int
  4. sName string
  5. }
  6. func NewBadTeamMember(id int, name string) *BadTeamMember {
  7. return &BadTeamMember{
  8. id,
  9. name,
  10. }
  11. }
  12. func (me *BadTeamMember) countOpeningTasks(lstTasks []*Task) int {
  13. sum := 0
  14. for _,it := range lstTasks {
  15. if it.Status() == OPENING {
  16. sum++
  17. }
  18. }
  19. return sum
  20. }

GoodTeamLerader.go

更好的ITeamLeader实现, 只依赖了GoodTeamMember

  1. package law_of_demeter
  2. import "fmt"
  3. type GoodTeamLeader struct {
  4. iID int
  5. sName string
  6. }
  7. func (me *GoodTeamLeader) CountOpeningTasks() int {
  8. member := NewGoodTeamMember(11, "王Member")
  9. sum := member.countOpeningTasks()
  10. fmt.Printf("%v CountOpeningTasks, got %v", me.sName, sum)
  11. return sum
  12. }

GoodTeamMember.go

更好的TeamMember, 对外屏蔽了任务列表的获取细节

  1. package law_of_demeter
  2. type GoodTeamMember struct {
  3. iID int
  4. sName string
  5. }
  6. func NewGoodTeamMember(id int, name string) *GoodTeamMember {
  7. return &GoodTeamMember{
  8. id,
  9. name,
  10. }
  11. }
  12. func (me *GoodTeamMember) countOpeningTasks() int {
  13. sum := 0
  14. tasks := LoadTaskList()
  15. for _,it := range tasks {
  16. if it.Status() == OPENING {
  17. sum++
  18. }
  19. }
  20. return sum
  21. }

law_of_demeter_test.go

单元测试

  1. package main
  2. import "testing"
  3. import (lod "learning/gooop/principles/law_of_demeter")
  4. func Test_LOD(t *testing.T) {
  5. bl := lod.NewBadTeamLeader(1, "张Leader")
  6. bl.CountOpeningTasks()
  7. gl := lod.NewGoodTeamLeader(2, "李Leader")
  8. gl.CountOpeningTasks()
  9. }

测试输出

  1. $ go test -v law_of_demeter_test.go
  2. === RUN Test_LOD
  3. Leader CountOpeningTasks, got 1
  4. Leader CountOpeningTasks, got 1
  5. --- PASS: Test_LOD (0.00s)
  6. PASS
  7. ok command-line-arguments 0.002s