手撸 golang 架构设计准则 里氏替换准则
缘起
最近温习设计模式
拜读谭勇德的 << 设计模式就该这样学 >>
该书以 java 语言演绎了常见设计模式
本系列笔记拟采纳 golang 练习之
里氏替换准则
里氏替换准则(Liskov Substitution Principle, LSP):
如果对每一个类型为 T1 的对象 O1
都有类型为 T2 的对象 O2
使得以 T1 定义的所有程序 P
在所有对象 O1 都替换成 O2 时
程序 P 的行为没有发生变化
那么类型 T2 是类型 T1 的子类型
_
能够了解为:
所有援用父类的中央
必须能通明地应用其子类对象
子类对象可能替换父类对象
而放弃程序性能不变
_
里氏替换准则的长处:
(1)束缚继承泛滥,是开闭准则的一种体现
(2)增强程序的健壮性,同时变更时能够做到十分好的兼容性
_
场景
- 某线上动物园零碎, 定义了鸟类接口 IBird 和 NormalBird 类
- IBird 接口定义了鸣叫 – Tweet(), 和翱翔 – Fly()办法
- 现须要减少一种 ” 鸟类 ” – 鸵鸟: 鸵鸟只会跑 – Run(), 不会飞 – Fly()
-
不好的设计:
- 新增鸵鸟类 – OstrichBird, 从 NormalBird 继承
- 笼罩 Fly 办法, 并抛出谬误
- 增加 Run 办法
- 调用方须要批改: 判断是否 OstrichBird, 是则须要特地看待
- 存在问题: OstrichBird 跟 NormalBird 曾经有较大差别, 强行继承造成很多异味
-
更好的设计:
- IBird 接口保留鸣叫 – Tweet()办法
- NormalBird 实现 IBird 接口, 移除 Fly 办法
- 新增 IFlyableBird, 继承 IBird 接口, 并增加 Fly()办法
- 新增 FlyableBird, 继承 NormalBird, 并实现 IFlyableBird 接口
- 新增 IRunnableBird, 继承 IBird 接口, 并增加 Run()办法
- 新增 OstrichBird, 继承 NormalBird, 并实现 IRunnableBird
- 调用方判断是 IFlyableBird, 还是 IRunnableBird
IBadBird.go
不好的设计, 该接口未思考某些鸟类是不能 Fly 的
package liskov_substitution
type IBadBird interface {ID() int
Name() string
Tweet() error
Fly() error}
BadNormalBird.go
BadNormalBird 实现了 IBadBird 接口
package liskov_substitution
import "fmt"
type BadNormalBird struct {
iID int
sName string
}
func NewBadNormalBird(id int, name string) IBadBird {
return &BadNormalBird{
id,
name,
}
}
func (me *BadNormalBird) ID() int {return me.iID}
func (me *BadNormalBird) Name() string {return me.sName}
func (me *BadNormalBird) Tweet() error {fmt.Printf("BadNormalBird.Tweet, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
func (me *BadNormalBird) Fly() error {fmt.Printf("BadNormalBird.Fly, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
BadOstrichBird.go
不好的设计.
BadOstrichBird 通过继承 BadNormalBird 实现了 IBadBird 接口. 因为不反对 Fly, 因而 Fly 办法抛出了谬误. 额定增加了 IBadBird 未思考到的 Run 办法. 该办法的调用要求调用方必须判断具体类型, 导致重大耦合.
package liskov_substitution
import (
"errors"
"fmt"
)
type BadOstrichBird struct {BadNormalBird}
func NewBadOstrichBird(id int, name string) IBadBird {
return &BadOstrichBird{*(NewBadNormalBird(id, name).(*BadNormalBird)),
}
}
func (me *BadOstrichBird) Fly() error {return errors.New(fmt.Sprintf("BadOstrichBird.Fly, cannot fly, id=%v, name=%v\n", me.ID(), me.Name()))
}
func (me *BadOstrichBird) Run() error {fmt.Printf("BadOstrichBird.Run, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
IGoodBird.go
更好的设计.
IGoodBird 仅定义了最根本的办法集, 通过子接口 IFlyableBird 增加 Fly 办法, 通过子接口 IRunnableBird 增加 Run 办法
package liskov_substitution
type IGoodBird interface {ID() int
Name() string
Tweet() error}
type IFlyableBird interface {
IGoodBird
Fly() error}
type IRunnableBird interface {
IGoodBird
Run() error}
GoodNormalBird.go
GoodNormalBird 提供对 IGoodBird 的根底实现
package liskov_substitution
import "fmt"
type GoodNormalBird struct {
iID int
sName string
}
func NewGoodNormalBird(id int, name string) *GoodNormalBird {
return &GoodNormalBird{
id,
name,
}
}
func (me *GoodNormalBird) ID() int {return me.iID}
func (me *GoodNormalBird) Name() string {return me.sName}
func (me *GoodNormalBird) Tweet() error {fmt.Printf("GoodNormalBird.Tweet, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
GoodFlyableBird.go
GoodFlyableBird 通过聚合 GoodNormalBird 实现 IGoodBird 接口, 通过提供 Fly 办法实现 IFlyableBird 子接口
package liskov_substitution
import "fmt"
type GoodFlyableBird struct {GoodNormalBird}
func NewGoodFlyableBird(id int, name string) IGoodBird {
return &GoodFlyableBird{*NewGoodNormalBird(id, name),
}
}
func (me *GoodFlyableBird) Fly() error {fmt.Printf("GoodFlyableBird.Fly, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
GoodOstrichBird.go
GoodOstrichBird 通过聚合 GoodNormalBird 实现 IGoodBird 接口, 通过提供 Run 办法实现 IRunnableBird 子接口
package liskov_substitution
import ("fmt")
type GoodOstrichBird struct {GoodNormalBird}
func NewGoodOstrichBird(id int, name string) IGoodBird {
return &GoodOstrichBird{*NewGoodNormalBird(id, name),
}
}
func (me *GoodOstrichBird) Run() error {fmt.Printf("GoodOstrichBird.Run, id=%v, name=%v\n", me.ID(), me.Name())
return nil
}
liskov_substitution_test.go
单元测试
package main
import "testing"
import (lsp "learning/gooop/principles/liskov_substitution")
func Test_LSP(t *testing.T) {fnCallAndLog := func(fn func() error) {e := fn()
if e != nil {t.Logf("error = %s", e.Error())
}
}
// start testing bad /////////////////////////////////////////////////
bb := lsp.NewBadNormalBird(1, "普鸟")
fnCallAndLog(bb.Tweet)
fnCallAndLog(bb.Fly)
bo := lsp.NewBadOstrichBird(2, "鸵鸟")
fnCallAndLog(bo.Tweet)
fnCallAndLog(bo.Fly)
if it, ok := bo.(*lsp.BadOstrichBird);ok {fnCallAndLog(it.Run)
}
// end testing bad /////////////////////////////////////////////////
// start testing good /////////////////////////////////////////////////
fnTestGoodBird := func(gb lsp.IGoodBird) {fnCallAndLog(gb.Tweet)
if it, ok := gb.(lsp.IFlyableBird);ok {fnCallAndLog(it.Fly)
}
if it, ok := gb.(lsp.IRunnableBird);ok {fnCallAndLog(it.Run)
}
}
fnTestGoodBird(lsp.NewGoodFlyableBird(11, "飞鸟"))
fnTestGoodBird(lsp.NewGoodOstrichBird(12, "鸵鸟"))
// end testing good /////////////////////////////////////////////////
}
测试输入
$ go test -v liskov_substitution_test.go
=== RUN Test_LSP
BadNormalBird.Tweet, id=1, name= 普鸟
BadNormalBird.Fly, id=1, name= 普鸟
BadNormalBird.Tweet, id=2, name= 鸵鸟
liskov_substitution_test.go:10: error = BadOstrichBird.Fly, cannot fly, id=2, name= 鸵鸟
BadOstrichBird.Run, id=2, name= 鸵鸟
GoodNormalBird.Tweet, id=11, name= 飞鸟
GoodFlyableBird.Fly, id=11, name= 飞鸟
GoodNormalBird.Tweet, id=12, name= 鸵鸟
GoodOstrichBird.Run, id=12, name= 鸵鸟
--- PASS: Test_LSP (0.00s)
PASS
ok command-line-arguments 0.002s