最近複習設計模式
拜讀譚勇德的<<設計模式就該這樣學>>
該書以java語言演繹了常見設計模式
本系列筆記擬採用golang練習之java
里氏替換原則(Liskov Substitution Principle, LSP):
若是對每個類型爲T1的對象O1
都有類型爲T2的對象O2
使得以T1定義的全部程序P
在全部對象O1都替換成O2時
程序P的行爲沒有發生變化
那麼類型T2是類型T1的子類型
_
能夠理解爲:
全部引用父類的地方
必須能透明地使用其子類對象
子類對象可以替換父類對象
而保持程序功能不變
_
里氏替換原則的優勢:
(1)約束繼承氾濫,是開閉原則的一種體現
(2)增強程序的健壯性,同時變動時能夠作到很是好的兼容性
_golang
很差的設計:設計模式
更好的設計:架構
很差的設計, 該接口未考慮某些鳥類是不能Fly的oop
package liskov_substitution type IBadBird interface { ID() int Name() string Tweet() error Fly() error }
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經過繼承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僅定義了最基本的方法集, 經過子接口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提供對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經過聚合GoodNormalBird實現IGoodBird接口, 經過提供Fly方法實現IFlyableBird子接口code
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經過聚合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 }
單元測試
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