缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
该书以java语言演绎了常见设计模式
本系列笔记拟采用golang练习之
接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。设计接口时,应当注意以下几点:
(1)一个类对另一个类的依赖应该建立在最小接口上。
(2)建立单一接口,不要建立庞大臃肿的接口。
(3)尽量细化接口,接口中的方法尽量少。
_
场景
- 设计一个动物接口
- 不同动物可能有eat(), fly(), swim()等方法
- 设计实现动物接口的Bird类和Dog类
IBadAnimal.go
不好的接口设计, 接口方法很多, 比较臃肿, 需要实现接口时负担很重
package interface_segregationtype IBadAnimal interface {ID() intName() stringEat() errorFly() errorSwim() error}
BadBird.go
BadBird实现了IBadAnimal接口.
BadBird是不支持Swim()的, 但由于接口要求, 只能返回无意义的错误应付.
package interface_segregationimport ("errors""fmt")type BadBird struct {iID intsName string}func NewBadBird(id int, name string) IBadAnimal {return &BadBird{iID: id,sName: name,}}func (me *BadBird) ID() int {return me.iID}func (me *BadBird) Name() string {return me.sName}func (me *BadBird) Eat() error {fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())return nil}func (me *BadBird) Fly() error {fmt.Printf("%v/%v is flying\n", me.Name(), me.ID())return nil}func (me *BadBird) Swim() error {return errors.New(fmt.Sprintf("%v/%v cannot swimming", me.Name(), me.ID()))}
BadDog.go
BadDog实现IBadAnimal接口.
本来BadDog是不支持Fly()方法的, 但由于接口要求, 因此只能返回无意义错误.
package interface_segregationimport ("errors""fmt")type BadDog struct {iID intsName string}func NewBadDog(id int, name string) IBadAnimal {return &BadDog{iID: id,sName: name,}}func (me *BadDog) ID() int {return me.iID}func (me *BadDog) Name() string {return me.sName}func (me *BadDog) Eat() error {fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())return nil}func (me *BadDog) Fly() error {return errors.New(fmt.Sprintf("%v/%v cannot fly", me.Name(), me.ID()))}func (me *BadDog) Swim() error {fmt.Printf("%v/%v is swimming\n", me.Name(), me.ID())return nil}
IGoodAnimal.go
更好的接口设计. 将动物接口拆分为基本信息接口IGoodAnimal, 以及三个可选的能力接口:
ISupportEat, ISupportFly, ISupportSwim
package interface_segregationtype IGoodAnimal interface {ID() intName() string}type ISupportEat interface {Eat() error}type ISupportFly interface {Fly() error}type ISupportSwim interface {Swim() error}
GoodAnimalInfo.go
实现IGoodAnimal接口, 提供动物的id,name等基本属性
package interface_segregationtype GoodAnimalInfo struct {iID intsName string}func (me *GoodAnimalInfo) ID() int {return me.iID}func (me *GoodAnimalInfo) Name() string {return me.sName}
GoodBird.go
更好的Bird实现, 异味代码更少.
通过集成GoodAnimalInfo实现IGoodAnimal接口, 并选择性实现ISupportEat, ISupportFly.
package interface_segregationimport "fmt"type GoodBird struct {GoodAnimalInfo}func NewGoodBird(id int, name string) IGoodAnimal {return &GoodBird{GoodAnimalInfo{id,name,},}}func (me *GoodBird) Eat() error {fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())return nil}func (me *GoodBird) Fly() error {fmt.Printf("%v/%v is flying\n", me.Name(), me.ID())return nil}
GoodDog.go
更好的Dog实现, 异味代码更少.
通过集成GoodAnimalInfo实现IGoodAnimal接口, 并选择性实现ISupportEat, ISupportSwim.
package interface_segregationimport "fmt"type GoodDog struct {GoodAnimalInfo}func NewGoodDog(id int, name string) IGoodAnimal {return &GoodDog{GoodAnimalInfo{id,name,},}}func (me *GoodDog) Eat() error {fmt.Printf("%v/%v is eating\n", me.Name(), me.ID())return nil}func (me *GoodDog) Swim() error {fmt.Printf("%v/%v is swimming\n", me.Name(), me.ID())return nil}
interface_segregation_test.go
单元测试
package mainimport (isp "learning/gooop/principles/interface_segregation""testing")func Test_ISP(t *testing.T) {fnLogIfError := func(fn func() error) {e := fn()if e != nil {t.Logf("error = %s\n", e.Error())}}fnTestBadAnimal := func (a isp.IBadAnimal) {fnLogIfError(a.Eat)fnLogIfError(a.Fly)fnLogIfError(a.Swim)}fnTestBadAnimal(isp.NewBadBird(1, "BadBird"))fnTestBadAnimal(isp.NewBadDog(2, "BadDog"))fnTestGoodAnimal := func(a isp.IGoodAnimal) {if it,ok := a.(isp.ISupportEat);ok {fnLogIfError(it.Eat)} else {t.Logf("%v/%v cannot eat", a.Name(), a.ID())}if it,ok := a.(isp.ISupportFly);ok {fnLogIfError(it.Fly)} else {t.Logf("%v/%v cannot fly", a.Name(), a.ID())}if it,ok := a.(isp.ISupportSwim);ok {fnLogIfError(it.Swim)} else {t.Logf("%v/%v cannot swim", a.Name(), a.ID())}}fnTestGoodAnimal(isp.NewGoodBird(11, "GoodBird"))fnTestGoodAnimal(isp.NewGoodDog(12, "GoodDog"))}
测试输出
$ go test -v interface_segregation_test.go=== RUN Test_ISPBadBird/1 is eatingBadBird/1 is flyinginterface_segregation_test.go:12: error = BadBird/1 cannot swimmingBadDog/2 is eatinginterface_segregation_test.go:12: error = BadDog/2 cannot flyBadDog/2 is swimmingGoodBird/11 is eatingGoodBird/11 is flyinginterface_segregation_test.go:42: GoodBird/11 cannot swimGoodDog/12 is eatinginterface_segregation_test.go:36: GoodDog/12 cannot flyGoodDog/12 is swimming--- PASS: Test_ISP (0.00s)PASSok command-line-arguments 0.002s
