設計模式學習-使用go實現模板模式

模板模式

定義

模板模式(TemplateMethod):定義一個操作中的演算法骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重新定義該演算法的某些特定步驟。

模板方法模式就是提供一個程式碼復用平台,當不變和可變的行為在方法的子類實現中混合在一起的時候,不變的行為就會在子類中重複出現。通過模板方法模式把這些行為搬到單一的地方,這樣就幫助子類擺脫重複的不變行為的糾纏。

模板模式的作用

1、復用

所有的子類可以復用父類中提供的模板方法的程式碼

2、擴展

框架通過模板模式提供功能擴展點,讓框架用戶可以在不修改框架源碼的情況下,基於擴展點訂製化框架的功能

優點

1、封裝不變部分,擴展可變部分;

2、提取公共程式碼,便於維護;

3、行為由父類控制,子類實現。

缺點

每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。

適用範圍

1、有多個子類共有的方法,且邏輯相同;

2、重要的、複雜的方法,可以考慮作為模板方法。

程式碼實現

考試時候試卷,對於試題部分,同一場考試內容都是一樣的。試卷做完交卷只是我們每人個人填寫的答案不同,那麼試題就可以作為模板,我們只用去寫答案。

type TestPaperImpl interface {
	testQuestion1()
	testQuestion2()
	Answer1()
	Answer2()
}

type testPaper struct {
}

func (t *testPaper) testQuestion1() {
	fmt.Println("問題:中國有多少個民族")
}

func (t *testPaper) testQuestion2() {
	fmt.Println("問題:中國有多大")
}

func (t *testPaper) Answer1() {
}

func (t *testPaper) Answer2() {
}

// 封裝具體步驟
func doPaper(paper TestPaperImpl) {
	paper.testQuestion1()
	paper.Answer1()

	paper.testQuestion2()
	paper.Answer2()
}

type student1 struct {
	*testPaper
}

func (s *student1) Answer1() {
	fmt.Println("答案:56")
}

func (s *student1) Answer2() {
	fmt.Println("答案:很大")
}

type student2 struct {
	*testPaper
}

func (s *student2) Answer1() {
	fmt.Println("答案:51")
}

func (s *student2) Answer2() {
	fmt.Println("答案:不知道")
}

測試文件

func TestTestPaper(t *testing.T) {
	st1 := &student1{}
	doPaper(st1)

	fmt.Println("++++++++++++++")
	st2 := &student2{}
	doPaper(st2)
}

結果

問題:中國有多少個民族
答案:56
問題:中國有多大
答案:很大
++++++++++++++
問題:中國有多少個民族
答案:51
問題:中國有多大
答案:不知道

結構圖

template

回調

回調起到的作用和模板模式一樣

相對於普通的函數調用來說,回調是一種雙向調用關係。A類事先註冊某個函數F到B類,A類在調用B類的P函數的時候,B類反過來調用A類註冊給它的F函數。這裡的F函數就是「回調函數」。A調用B,B反過來又調用A,這種調用機制就叫作「回調」

回調分為兩種:

1、同步回調

在函數返回之前執行回調函數,同步回調看起來有點像模板模式

2、非同步回調

在函數返回之後執行回調函數

如果做過支付的同學肯定很熟悉這個,例如微信支付,我們調用微信支付進行付款,成功之後我們的伺服器會收到微信端支付的消息回調,然後進行支付成功之後的後續操作。

上面的考試例子使用回調實現

type testPaperCallback struct {
}

func (t *testPaperCallback) testQuestion1() {
	fmt.Println("問題1:中國有多少個民族")
}

func (t *testPaperCallback) testQuestion2() {
	fmt.Println("問題2:中國有多大")
}

func (t *testPaperCallback) Answer(callback CallbackImpl) {
	t.testQuestion1()
	t.testQuestion2()
	callback.AnswerCallback()
}

type CallbackImpl interface {
	AnswerCallback()
}

type student3 struct {
	*testPaperCallback
}

func (s *student3) AnswerCallback() {
	fmt.Println("答案1:56")
	fmt.Println("答案2:測試")
}

func doPaperCallback(student *student3) {
	student.Answer(&student3{})
}

結構圖

callback

模板模式 VS 回調

從應用場景上來看,同步回調跟模板模式幾乎一致。它們都是在一個大的演算法骨架中,自由替換其中的某個步驟,起到程式碼復用和擴展的目的。而非同步回調跟模板模式有較大差別,更像是觀察者模式。

從程式碼實現上來看,回調和模板模式完全不同。回調基於組合關係來實現,把一個對象傳遞給另一個對象,是一種對象之間的關係;模板模式基於繼承關係來實現,子類重寫父類的抽象方法,是一種類之間的關係。

  • 回調可以使用匿名類來創建回調對象,可以不用事先定義類;而模板模式針對不同的實現都要定義不同的子類。

  • 如果某個類中定義了多個模板方法,每個方法都有對應的抽象方法,那即便我們只用到其中的一個模板方法,子類也必須實現所有的抽象方法。而回調就更加靈活,我們只需要往用到的模板方法中注入回調對象即可。

參考

【文中程式碼】//github.com/boilingfrog/design-pattern-learning/tree/master/模板模式
【大話設計模式】//book.douban.com/subject/2334288/
【極客時間】//time.geekbang.org/column/intro/100039001
【模板模式】//boilingfrog.github.io/2021/11/20/使用go實現模板模式/