Skip to main content

[探索 5 分鐘] method chaining 設計原理

有用 jQuery 操作過 DOM 物件的同學是不是有時候會好奇, 這種把方法不斷的用「點」運算式的背後是怎麼設計的 ? 它叫做 Method chaining, 方法鏈, 比如 jQuery 這麼寫
<div id="d1" onmouseover="foo()">jQuery is fun!!</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
    function foo() {
        $("#d1").css("color", "red").slideUp(2000).slideDown(2000);
    }
</script>
可以讓一個黑色文字, 滑鼠移上去時依序 > 變為紅色 > 向上滑動 2 秒 > 向下滑動 2 秒。現在大家傾向這麼寫
$("#d1").css("color", "red")
  .slideUp(2000)
  .slideDown(2000);

/* 囉嗦又不流暢的寫法
var d1 = $("#d1");
if (d1 != null) {
    d1.css("color", "red");
    d1.slideUp(2000);
    d1.slideDown(2000);
}*/
較為流暢, 英文叫 Fluent, 每一行有一個重點, 卻又與第一行的物件串在一起, 比傳統這麼寫好多了

wiki 有演示很多語言怎麼實作 Method chaining, 其實重點就是每次呼叫完函式, 把自己 (this or self) 回傳 (當然你回傳 null, 就 GG了)。由於大部分物件是 reference type, 僅回傳物件所在位址, 一般而言就是 4 bytes 的大小, 會影響一點效能但不大, 換來的是流暢的呼叫方法, 可讀性高。

JavaScript should.js

搜尋 Fluent JavaScript, 會找到一個開源的 JavaScript 專案, should.js, 挺有趣的。知名 BDD 單元測試框架 mocha.js 也依賴這個 library。
should is an expressive, readable, framework-agnostic assertion library. The main goals of this library are to be expressive and to be helpful.
>(5).should.be.exactly(5).and.be.a.Number();
should(10).be.exactly(5).and.be.a.Number();
user.should.be.an.instanceOf(Object).and.have.property('name', 'tj');
user.pets.should.be.instanceof(Array).and.have.lengthOf(4);
是不是寫代碼好像在說話一樣。

C# Linked List

method chaining 不一定要回傳 this 的, 若有特殊應用如 Linked List, 往下一個 Node 移動, 或是在那邊上上下下找東西 (?), 也挺適合使用 method chaining, 如
public sealed class LinkedListNode<T>
{
    internal LinkedList<T> list;
    internal LinkedListNode<T> next;
    internal LinkedListNode<T> prev;
    internal T item;

    public LinkedListNode(T value)
    {
        item = value;
    }

    internal LinkedListNode(LinkedList<T> list, T value)
    {
        this.list = list;
        item = value;
    }

    public LinkedList<T> List
    {
        get { return list; }
    }

    public LinkedListNode<T> Next
    {
        get { return next == null || next == list.head ? null : next; }
    }

    public LinkedListNode<T> Previous
    {
        get { return prev == null || this == list.head ? null : prev; }
    }
}
如此一來, 當創建一個 LinkedList 物件, 我們就可以從 first node 開始, Next, Next, Previous, Previous, until null
list.AddFirst(10);             // Contents: ->10
list.AddLast(15);              // Contents: ->10->15
list.AddLast(3);               // Contents: ->10->15->3
list.AddLast(99);              // Contents: ->10->15->3->99
list.AddBefore(list.Last, 25); // Contents: ->10->15->3->25->99

Console.WriteLine(list.First.Next.Next.Previous.Value); //15
Console.WriteLine(list.First.Previous.Value); //Exception
注意, 這個資料結構還是可能會回傳 null的哦 !

ASP.NET Core WebHostBuilder

更新一下, 2017 / 07 月看到一篇微軟文章介紹 WebHostBuilder, 他也是用 Fluent 的寫法, 看了真是非常舒服, 而且用 VS 2017 嘗試過是可以執行的代碼:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}
大家一起讓代碼更流暢吧 !

參考資料

  • https://en.wikipedia.org/wiki/Method_chaining
  • https://github.com/shouldjs/should.js
  • https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/Generic/LinkedList.cs
  • https://docs.microsoft.com/en-us/aspnet/core/fundamentals/hosting
  • http://nikas.praninskas.com/javascript/2015/04/26/fluent-javascript/

Comments