手撸 golang 架构设计准则 接口隔离准则
缘起
最近温习设计模式
拜读谭勇德的 << 设计模式就该这样学 >>
本系列笔记拟采纳 golang 练习之
接口隔离准则
接口隔离准则(Interface Segregation Principle, ISP)指用多个专门的接口,而不应用繁多的总接口,客户端不应该依赖它不须要的接口。设计接口时,该当留神以下几点:
(1)一个类对另一个类的依赖应该建设在最小接口上。
(2)建设繁多接口,不要建设宏大臃肿的接口。
(3)尽量细化接口,接口中的办法尽量少。
_
场景
- 设计一个动物接口
- 不同动物可能有 eat(), fly(), swim()等办法
- 设计实现动物接口的 Bird 类和 Dog 类
IBadAnimal.go
不好的接口设计, 接口办法很多, 比拟臃肿, 须要实现接口时累赘很重
package interface_segregation
type IBadAnimal interface {ID() int
Name() string
Eat() error
Fly() error
Swim() error}
BadBird.go
BadBird 实现了 IBadAnimal 接口.
BadBird 是不反对 Swim()的, 但因为接口要求, 只能返回无意义的谬误应酬.
package interface_segregation
import (
"errors"
"fmt"
)
type BadBird struct {
iID int
sName 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_segregation
import (
"errors"
"fmt"
)
type BadDog struct {
iID int
sName 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_segregation
type IGoodAnimal interface {ID() int
Name() string}
type ISupportEat interface {Eat() error
}
type ISupportFly interface {Fly() error
}
type ISupportSwim interface {Swim() error
}
GoodAnimalInfo.go
实现 IGoodAnimal 接口, 提供动物的 id,name 等根本属性
package interface_segregation
type GoodAnimalInfo struct {
iID int
sName 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_segregation
import "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_segregation
import "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 main
import (
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_ISP
BadBird/1 is eating
BadBird/1 is flying
interface_segregation_test.go:12: error = BadBird/1 cannot swimming
BadDog/2 is eating
interface_segregation_test.go:12: error = BadDog/2 cannot fly
BadDog/2 is swimming
GoodBird/11 is eating
GoodBird/11 is flying
interface_segregation_test.go:42: GoodBird/11 cannot swim
GoodDog/12 is eating
interface_segregation_test.go:36: GoodDog/12 cannot fly
GoodDog/12 is swimming
--- PASS: Test_ISP (0.00s)
PASS
ok command-line-arguments 0.002s