1. 程式人生 > >laravel5.5原始碼筆記(五、Pipeline管道模式)

laravel5.5原始碼筆記(五、Pipeline管道模式)

Pipeline管道模式,也有人叫它裝飾模式。應該說管道是裝飾模式的一個變種,雖然思想都是一樣的,但這個是閉包的版本,實現方式與傳統裝飾模式也不太一樣。在laravel的原始碼中算是一個比較核心的設計模式了。
管道模式,或者說裝飾模式的思想,就是在不改變原有程式的基礎上,可以方便的在已有程式上新增新的功能。

在說管道模式之前讓我們看一下array_reduce這個函式
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )

array_reduce() 將回調函式 callback
迭代地作用到 array 陣列中的每一個單元中,從而將陣列簡化為單一的值。 array 輸入的 arraycallback mixed callback ( mixed $carry , mixed $item ) carry 攜帶上次迭代裡的值; 如果本次迭代是第一次,那麼這個值是 initial。 item 攜帶了本次迭代的值。 initial 如果指定了可選引數 initial,該引數將在處理開始前使用,或者當處理結束,陣列為空時的最後一個結果。

 

 

以上是php官網對這個函式的解釋,看的一頭霧水對不對?沒關係,我們先來一個demo

 

 

//首先我們有一個數組
$a = array(1, 2, 3, 4, 5);
$x = array();

//我們輸出了array_reduce的結果為15,這個函式傳遞了兩個引數,第一個就是上面的陣列a,我們重點來看第二個閉包函式
var_dump(array_reduce($a, "sum")); // int(15)

//array_reduce的第三個引數會在迴圈開始的時候當做閉包函式的第一個carry值傳入
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5

//當array_reduce處理結束後,若結果為空,也會將第三個引數返回
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce" //這個閉包函式接收了兩個引數 function sum($carry, $item) { echo 'before___carry:'.$carry.'<br><br>'; echo 'before___item:'.$item.'<br><br>'; //這個函式只是簡單的對兩個引數做了加法 $carry += $item; echo 'after___carry:'.$carry.'<br><br>'; echo '<br><br>'; return $carry; } function product($carry, $item) { echo 'before___carry:'.$carry.'<br><br>'; echo 'before___item:'.$item.'<br><br>'; $carry *= $item; echo 'after___carry:'.$carry.'<br><br>'; echo '<br><br>'; return $carry; }

大家在執行過這個demo之後,便會發現,array_reduce只是一個迴圈函式。但是,它和foreach不一樣的地方在於,它的閉包處理函式所接收的兩個引數。其中的$item引數會在每次迴圈的時候代入陣列的各項值,而$carry就比較專一了,它只接收上一次迴圈中自己的返回值。不過專一也只是相對的,$carray還接收array_reduce傳入的第三個引數作為閉包開始迴圈前$carray的預設值,或是迴圈結束後返回值為null時的預設值。我在程式碼中已經把變數各時期的值給打印出來了,相信大家一看就會明白。這個函式最典型的例子就是在做累計運算的時候,我們不需要向foreach迴圈那樣建立一個變量了。(大家都知道給變數起名字是一件很麻煩的事情)

ok,函式介紹完了,可是這和管道模式又有什麼關係呢?不知道大家注意到沒有,在array_reduce的閉包函式進行迴圈的時候,array_reduce的第三個引數從閉包的$array進入,從return返回,緊接著再次從$array中進入,而每次迴圈的時候,都會從$item中獲取到外部進行運算。這個過程是不是很像資料在一個螺旋管道中流通,同時不斷的向其中新增新的操作,並且沒有改變原先的程式程式碼,降低了程式間的耦合度。

 接下來進行管道模式

請求處理管道的思維導圖

思維導圖看的還不夠清晰?ok,接著舉例子,看demo

從圖中我們可以知道類Request都具有相同的介面,那麼我們可以約定一個介面規範

interface RequestInterface
{
    // 我們之前講過裝飾者模式,這裡是通過閉包函式實現
    // 通過之後實現類及呼叫就可以看出
    public static function handle(Closure $next);
}

 

介面有了那麼我們就遵循介面開始實現

 

class Request1 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request1 Begin." . "<br />";
        $next();
        echo "Request1 End." . "<br />";
    }
}

class Request2 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request2 Begin." . "<br />";
        $next();
        echo "Request2 End." . "<br />";
    }
}

class Request3 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request3 Begin." . "<br />";
        $next();
        echo "Request3 End." . "<br />";
    }
}

class Request4 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request4 Begin." . "<br />";
        $next();
        echo "Request4 End." . "<br />";
    }
}

 

 

四種請求處理過程我們均已實現,為了簡化都是列印一句話,可以在瀏覽器上直觀顯示流程;
我們還需要一個客戶端來發出請求

 

class Client
{
    // 這裡包含了所有的請求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

    // 這裡就是思維導圖中預設返回的匿名回撥函式
    private function defaultClosure()
    {
        return function () {
            echo '請求處理中...' . "<br />";
        };
    }

    // 這裡就是整個請求處理管道的關鍵
    private function getSlice()
    {
        return function ($stack, $pipe)
        {
            return function () use ($stack, $pipe)
            {
                return $pipe::handle($stack);
            };
        };
    }

    // 這裡是負責發起請求處理
    public function then()
    {
        call_user_func(array_reduce($this->pipes, $this->getSlice(), $this->defaultClosure()));
    }
}

當我們呼叫時:

$worker = new Client();
$worker->then();

瀏覽器就會顯示:

那麼我來解釋一下整個流程吧,在程式碼註釋說了getSlice是關鍵,那麼我們來解讀一下程式碼

解析程式碼

 

client的then方法,call_user_func是執行一個函式的api,它的引數是一個閉包函式。而這個引數則是我們文章一開始提到的array_reduce這個迴圈的最終返回值。

我們都還記得這個函式一共有三個引數:

第一個引數代表從外界加入到管道中的附加操作

第二個引數則是執行加工的工廠

第三個引數則是進入管道的程式資料來源

 

先看第三個引數資料來源

    // 這裡就是思維導圖中預設返回的匿名回撥函式
    private function defaultClosure()
    {
        //這個return出去的閉包就是我們的資料來源
        return function () {
            echo '請求處理中...' . "<br />";
        };
    }


然後對程式進行裝飾的裝飾品陣列
// 這裡包含了所有的請求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

 

這裡雖然只是個數組,但它在剛剛的迴圈閉包中被當做物件名來使用,通過名稱排程不同的類


然後是迴圈閉包

    // 這裡就是整個請求處理管道的關鍵
    private function getSlice()
    {
        //這個函式我們的閉包迴圈函式
        return function ($stack, $pipe)
        {
            //而這裡便是我們最開始例子中的return返回值,只不過剛開始的時候我們返回的是一個數字變數,這裡變成了另一個閉包
            return function () use ($stack, $pipe)
            {
                return $pipe::handle($stack);
            };
        };
    }
這裡的迴圈閉包返回值也是一個閉包,在不斷的迴圈中,閉包函式就像洋蔥一樣,一層一層不斷包裹。

如此類推,最終array_reduce返回一個匿名函式,這就像上面思維導圖最終所描述的一樣
function (){
    return Request4::handle(function (){
        return Request3::handle(function (){
            return Request2::handle(function (){
                return Reuqest1::handle(function (){
                    echo '請求處理中...' . "<br />";
                });
            });
        });
    });
}

所以當呼叫call_user_func時就是執行array_reduce返回的匿名函式,我們從各個請求處理類的handle方法也得知,會直接呼叫傳入的匿名函式,注意順序即可理解瀏覽器的輸出。

與裝飾者模式的關係

這裡沒有用到類來對例項物件進行包裝,而是通過閉包函式完成,每一次處理在上一次處理之上進行包裝,最後獲得響應,就像流水線一樣,一個請求進來通過一道道工序加工(包裝)最後生成響應。

 

理解了這幾個demo,那麼laravel裡中介軟體最重點的部分你就已經理解了。

 

最後,本文內容並非全部原創,部分圖片與程式碼來源:http://blog.chenjunwu.cn/2017/05/02/Request-pipeline