用三種語言實現模板方法模式

領會其心,不拘一法。

模板方法模式,顧名思義,就是一個方法裏有一些固定的模板部分,有一些可變的部分。模板方法模式的實質是,將流程中的固定和可變部分分離。

本文用三種語言來實現模板方法模式。

實現模板方法模式

Java

Java 實現模板方法模式的標準套路是:

  • 定義一個接口;
  • 定義一個抽象類,實現接口的方法,但是實現方法流程裏留有一個未實現的部分,方法簽名爲 abstract;
  • 定義一個子類,實現抽象類中的 abstract 方法。

代碼如下:

package zzz.study.pattern.tempatemethod;

import org.junit.Test;

/**
 * TemplateMethodTest:
 * created by qin.shu 2023/11/4
 */
public class TemplateMethodTest {

    @Test
    public void testCook() {
        Cook cookTomato = new CookTomato();
        cookTomato.doCook();

        Cook cookMeat = new CookMeat();
        cookMeat.doCook();
    }
}


interface Cook {
    void doCook();
}

abstract class AbstractCook implements Cook {

    public void doCook() {
        wash();
        pour_oil();
        cook();
        chuguozhuangpan();
    }

    abstract void cook();

    private void wash() {
        System.out.println("wash");
    }

    private void pour_oil() {
        System.out.println("pour_oil");
    }

    private void chuguozhuangpan() {
        System.out.println("chuguozhuangpan");
    }
}

class CookTomato extends AbstractCook {

    @Override
    void cook() {
        System.out.println("cook tomato");
    }
}

class CookMeat extends AbstractCook {

    @Override
    void cook() {
        System.out.println("cook meat");
    }
}

Go

接着,咱們用 Go 來模擬上面這段程序。

不過 Go 是沒有類繼承的語言特性的。Go 的接口“實現”即是基於行爲:實現了接口的所有方法,就是實現了接口,無需特意用 implements 關鍵字表明。

Go 代碼如下(這個例子來自網上示例):

package main

import (
    "fmt"
)

type Cooker interface {
    wash()
    pour_oil()
    cook()
    chuguozhuangpan()
}

// 類似於一個抽象類
type CookFlow struct {
}

func (CookFlow) wash() {
    fmt.Println("wash")
}

func (CookFlow) pour_oil() {
    fmt.Println("pour_oil")
}

// 做菜,交給具體的子類實現
func (CookFlow) cooke() {
}

func (CookFlow) chuguozhuangpan() {
    fmt.Println("chuguozhuangpan")
}


// 封裝具體步驟
func doCook(cook Cooker) {
    cook.wash()
    cook.pour_oil()
    cook.cook()
    cook.chuguozhuangpan()
}

type Tomato struct {
    CookFlow
}

func (t *Tomato) cook() {
    fmt.Println("cook tomato")
}

type Meat  struct {
    CookFlow
}

func (m *Meat) cook() {
    fmt.Println("cook meat")
}

func main() {

    tomato := &Tomato{}
    doCook(tomato)

    meat := &Meat{}
    doCook(meat)

}

可以發現,Go 沒有類繼承,所以模板方法實現是放在函數裏的。通過在函數參數中傳入子類來實現。

對於 Go 這樣的非對象語言來說,用對象語言思考,總會有點“拿着錘子把所有東西都看成釘子”的感覺。咱們現在來褪下“對象的外衣”,直視“模板方法模式的本體”,用函數式編程來實現。

可以看到,算法無非是一個編排,固定一部分,讓另一部分可變。

實際上,我只消寫一個模板方法實現,把可變部分作爲函數參數傳入即可。

package main

import "fmt"

func cook(doCook func()) {
    wash()
    pour_oil()
    doCook()
    chuguozhuangpan()
}

func wash() {
    fmt.Println("wash")
}

func pour_oil() {
    fmt.Println("pour_oil")
}

func chuguozhuangpan() {
    fmt.Println("chuguozhuangpan")
}

func main() {
    cook(func() {
        fmt.Println("cook tomato")
    })

    cook(func() {
        fmt.Println("cook meat")
    })

}


Python

流程,本質上就是函數的編排。咱們可以寫得更通用些。

python 對函數式編程支持更好,編寫起來更好。

如下代碼所示: 將一個函數列表傳入,遍歷並調用,就實現了一個流程編排能力。然後把具體函數傳入即可。是不是超簡單?

def do_cook(cook_funcs):
    for func in cook_funcs:
        func()


def wash():
    print('wash')

def pour_oil():
    print('pour oil')


def cook_tomato():
    print('cook tomato')

def cook_meat():
    print('cook meat')

def pot():
    print('pot')


if __name__ == '__main__':

    do_cook([wash, pour_oil, cook_tomato, pot])
    do_cook([wash, pour_oil, cook_meat, pot])

還可以用函數 curry 來實現。所謂 curry, 就像高中所學的多元函數,傳入一個參數,就可以得到另一個函數。比如 f(x,y) = x + y ,將 x = 1 傳入,就得到了函數 f(y) = 1 + y。咱們用 curry 來實現模板方法模式。

def do_cook_by_curry(wash, pour, pot):
    def after(cook):
        wash()
        pour()
        cook()
        pot()
    return after

if __name__ == '__main__':

    print('template method pattern using func curry')
    cook_template = do_cook_by_curry(wash, pour_oil, pot)
    cook_template(cook_tomato)
    cook_template(cook_meat)

注意到:這裏 do_cook_by_curry 函數的返回結果是一個函數,這個函數會傳入一個可變的函數作爲參數來調用。這樣就實現了模板方法模式。是不是感覺很“犀利” ?

函數式編程與對象編程

函數式編程與對象編程都屬於一種編程範式。那麼,這兩者各有什麼優劣呢 ?

  • 函數式編程:基於不可變原則,短小精悍,通過函數組合能夠快速建立強大的能力;
  • 對象編程:將狀態和行爲統一在對象裏,其思想貼合事物的整體性。

對象編程,適合管理具有大量狀態的工程(比如基於 DDD),適合於包裝,本身是一種工程手段;函數式編程,適合實現強大的功能,更加純粹。

對於很多 Java 工程來說,其實很多組件都是單例。數據都在數據對象裏。如果領域比較複雜,對象和行爲比較複雜,需要用 DDD 的思想來包裝狀態和行爲,則用對象編程比較適合,否則不如直接用函數式編程,反而更加簡潔。

函數式編程 + 泛型編程結合,是一種強大的編程技藝。若能嫺熟掌握,可以甩同齡人一條街。

小結

本文用三種編程語言來實現模板方法模式。其主旨在於,用不同的思想和視角去看待同一件事情。這種方式可以開闊技術視角,不侷限於某一種編程語言和平臺。

當我們領會模式的思想和本質後,其實現手段和具體招式就可以隨心變化。

領會其心,不拘一法。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章