缘起

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

接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。设计接口时,应当注意以下几点:
(1)一个类对另一个类的依赖应该建立在最小接口上。
(2)建立单一接口,不要建立庞大臃肿的接口。
(3)尽量细化接口,接口中的方法尽量少。
_

场景

  • 设计一个动物接口
  • 不同动物可能有eat(), fly(), swim()等方法
  • 设计实现动物接口的Bird类和Dog类

IBadAnimal.go

不好的接口设计, 接口方法很多, 比较臃肿, 需要实现接口时负担很重

  1. package interface_segregation
  2. type IBadAnimal interface {
  3. ID() int
  4. Name() string
  5. Eat() error
  6. Fly() error
  7. Swim() error
  8. }

BadBird.go

BadBird实现了IBadAnimal接口.
BadBird是不支持Swim()的, 但由于接口要求, 只能返回无意义的错误应付.

  1. package interface_segregation
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. type BadBird struct {
  7. iID int
  8. sName string
  9. }
  10. func NewBadBird(id int, name string) IBadAnimal {
  11. return &BadBird{
  12. iID: id,
  13. sName: name,
  14. }
  15. }
  16. func (me *BadBird) ID() int {
  17. return me.iID
  18. }
  19. func (me *BadBird) Name() string {
  20. return me.sName
  21. }
  22. func (me *BadBird) Eat() error {
  23. fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())
  24. return nil
  25. }
  26. func (me *BadBird) Fly() error {
  27. fmt.Printf("%v/%v is flying\n", me.Name(), me.ID())
  28. return nil
  29. }
  30. func (me *BadBird) Swim() error {
  31. return errors.New(fmt.Sprintf("%v/%v cannot swimming", me.Name(), me.ID()))
  32. }

BadDog.go

BadDog实现IBadAnimal接口.
本来BadDog是不支持Fly()方法的, 但由于接口要求, 因此只能返回无意义错误.

  1. package interface_segregation
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. type BadDog struct {
  7. iID int
  8. sName string
  9. }
  10. func NewBadDog(id int, name string) IBadAnimal {
  11. return &BadDog{
  12. iID: id,
  13. sName: name,
  14. }
  15. }
  16. func (me *BadDog) ID() int {
  17. return me.iID
  18. }
  19. func (me *BadDog) Name() string {
  20. return me.sName
  21. }
  22. func (me *BadDog) Eat() error {
  23. fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())
  24. return nil
  25. }
  26. func (me *BadDog) Fly() error {
  27. return errors.New(fmt.Sprintf("%v/%v cannot fly", me.Name(), me.ID()))
  28. }
  29. func (me *BadDog) Swim() error {
  30. fmt.Printf("%v/%v is swimming\n", me.Name(), me.ID())
  31. return nil
  32. }

IGoodAnimal.go

更好的接口设计. 将动物接口拆分为基本信息接口IGoodAnimal, 以及三个可选的能力接口:
ISupportEat, ISupportFly, ISupportSwim

  1. package interface_segregation
  2. type IGoodAnimal interface {
  3. ID() int
  4. Name() string
  5. }
  6. type ISupportEat interface {
  7. Eat() error
  8. }
  9. type ISupportFly interface {
  10. Fly() error
  11. }
  12. type ISupportSwim interface {
  13. Swim() error
  14. }

GoodAnimalInfo.go

实现IGoodAnimal接口, 提供动物的id,name等基本属性

  1. package interface_segregation
  2. type GoodAnimalInfo struct {
  3. iID int
  4. sName string
  5. }
  6. func (me *GoodAnimalInfo) ID() int {
  7. return me.iID
  8. }
  9. func (me *GoodAnimalInfo) Name() string {
  10. return me.sName
  11. }

GoodBird.go

更好的Bird实现, 异味代码更少.
通过集成GoodAnimalInfo实现IGoodAnimal接口, 并选择性实现ISupportEat, ISupportFly.

  1. package interface_segregation
  2. import "fmt"
  3. type GoodBird struct {
  4. GoodAnimalInfo
  5. }
  6. func NewGoodBird(id int, name string) IGoodAnimal {
  7. return &GoodBird{
  8. GoodAnimalInfo{
  9. id,
  10. name,
  11. },
  12. }
  13. }
  14. func (me *GoodBird) Eat() error {
  15. fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())
  16. return nil
  17. }
  18. func (me *GoodBird) Fly() error {
  19. fmt.Printf("%v/%v is flying\n", me.Name(), me.ID())
  20. return nil
  21. }

GoodDog.go

更好的Dog实现, 异味代码更少.
通过集成GoodAnimalInfo实现IGoodAnimal接口, 并选择性实现ISupportEat, ISupportSwim.

  1. package interface_segregation
  2. import "fmt"
  3. type GoodDog struct {
  4. GoodAnimalInfo
  5. }
  6. func NewGoodDog(id int, name string) IGoodAnimal {
  7. return &GoodDog{
  8. GoodAnimalInfo{
  9. id,
  10. name,
  11. },
  12. }
  13. }
  14. func (me *GoodDog) Eat() error {
  15. fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())
  16. return nil
  17. }
  18. func (me *GoodDog) Swim() error {
  19. fmt.Printf("%v/%v is swimming\n", me.Name(), me.ID())
  20. return nil
  21. }

interface_segregation_test.go

单元测试

  1. package main
  2. import (
  3. isp "learning/gooop/principles/interface_segregation"
  4. "testing"
  5. )
  6. func Test_ISP(t *testing.T) {
  7. fnLogIfError := func(fn func() error) {
  8. e := fn()
  9. if e != nil {
  10. t.Logf("error = %s\n", e.Error())
  11. }
  12. }
  13. fnTestBadAnimal := func (a isp.IBadAnimal) {
  14. fnLogIfError(a.Eat)
  15. fnLogIfError(a.Fly)
  16. fnLogIfError(a.Swim)
  17. }
  18. fnTestBadAnimal(isp.NewBadBird(1, "BadBird"))
  19. fnTestBadAnimal(isp.NewBadDog(2, "BadDog"))
  20. fnTestGoodAnimal := func(a isp.IGoodAnimal) {
  21. if it,ok := a.(isp.ISupportEat);ok {
  22. fnLogIfError(it.Eat)
  23. } else {
  24. t.Logf("%v/%v cannot eat", a.Name(), a.ID())
  25. }
  26. if it,ok := a.(isp.ISupportFly);ok {
  27. fnLogIfError(it.Fly)
  28. } else {
  29. t.Logf("%v/%v cannot fly", a.Name(), a.ID())
  30. }
  31. if it,ok := a.(isp.ISupportSwim);ok {
  32. fnLogIfError(it.Swim)
  33. } else {
  34. t.Logf("%v/%v cannot swim", a.Name(), a.ID())
  35. }
  36. }
  37. fnTestGoodAnimal(isp.NewGoodBird(11, "GoodBird"))
  38. fnTestGoodAnimal(isp.NewGoodDog(12, "GoodDog"))
  39. }

测试输出

  1. $ go test -v interface_segregation_test.go
  2. === RUN Test_ISP
  3. BadBird/1 is eating
  4. BadBird/1 is flying
  5. interface_segregation_test.go:12: error = BadBird/1 cannot swimming
  6. BadDog/2 is eating
  7. interface_segregation_test.go:12: error = BadDog/2 cannot fly
  8. BadDog/2 is swimming
  9. GoodBird/11 is eating
  10. GoodBird/11 is flying
  11. interface_segregation_test.go:42: GoodBird/11 cannot swim
  12. GoodDog/12 is eating
  13. interface_segregation_test.go:36: GoodDog/12 cannot fly
  14. GoodDog/12 is swimming
  15. --- PASS: Test_ISP (0.00s)
  16. PASS
  17. ok command-line-arguments 0.002s