bells
安裝本網站至主畫面:

[Lua] 程式設計教學:多型 (Polymorphism)

PUBLISHED ON FEB 18, 2018 — PROGRAMMING
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    由於 Lua 是動態型別語言,不需要像 Java 等語言,利用子型別 (subtyping) 來達到多型的效果,使用內建的語法機制即可達到相同的效果。

    Duck Type

    像 Lua 等動態型別語言較為靈活,程式設計者不需要在意物件的型別 (type),只需符合公開的方法即可。如下例:

    local Duck = {}
    Duck.__index = Duck
    
    function Duck:new()
        self = {}
        setmetatable(self, Duck)
        return self
    end
    
    function Duck:speak()
        print("Pack pack")
    end
    
    local Dog = {}
    Dog.__index = Dog
    
    function Dog:new()
        self = {}
        setmetatable(self, Dog)
        return self
    end
    
    function Dog:speak()
        print("Wow wow")
    end
    
    local Tiger = {}
    Tiger.__index = Tiger
    
    function Tiger:new()
        self = {}
        setmetatable(self, Tiger)
        return self
    end
    
    function Tiger:speak()
        print("Halum halum")
    end
    
    do
        local animals = {
            Duck:new(),
            Dog:new(),
            Tiger:new()
        }
        
        for _, a in ipairs(animals) do
            a:speak()
        end
    end

    由於動態型別語言本身的性質,在本例中,只要物件滿足 speak 方法的實作,就可放入 animals 陣列中,不需要用繼承或介面去實作型別樹,也不需要檢查實際的型別。

    函式重載

    Lua 本身不支援函式重載 (function overloading),不過,由於 Lua 是動態型別語言,可以用一些 dirty hacks 去模擬。如以下實例用執行期的型別檢查來模擬函式重載:

    local function add(a, b)
        if type(a) == "string" or type(b) == "string" then
            return a .. b
        else
            return a + b
        end
    end
    
    -- The main program.
    do
        local p = add("1", 2)
        local q = add(1, 2)
        
        assert(p == "12")
        assert(q == 3)
    end

    除了上述方法外,由於 Lua 支援任意長度參數,只要在函式內根據不同的參數給予相對應的行為,就可以模擬函式重載。然而,過度使用此特性,會使得程式難以維護,實務上不鼓勵這種方法。

    Lua users wiki 上提供一個較複雜的方法 (見此處),因程式碼較長,這裡不重覆貼出。由於此方式較為複雜,筆者本身未採用此種方法來模擬函式重載,讀者可自行評估是否符合自身的需求。

    也可以直接將表 (table) 做為參數傳入函式,對於未賦值的參數直接給予預設值即可。如下例:

    local function foo(args)
        local args = args or {}
        local _args = {}
        
        _args.a = args.a or 1
        _args.b = args.b or 2
        _args.c = args.c or 3
        
        return _args.a + _args.b + _args.c
    end
    
    The main program.
    do
        assert(foo() == 6)
        assert(foo({a = 3}) == 8)
        assert(foo({a = 3, b = 4}) == 10)
        assert(foo({a = 3, b = 4, c = 5}) == 12)
    end

    使用表做為參數,不需要寫死參數的位置,可維護性會比較好一些。

    過度地使用函式重載,會使程式可維護性變差,即使我們可以用一些 hack 來模擬,還是要審慎使用。

    運算子重載

    Lua 透過 metamethod 來達到運算子重載的效果。筆者以數學上的向量 (vector) 來展示其實作法:

    local Vector = {}
    
    Vector.__index = Vector
    
    -- Implement __eq for equality check.
    Vector.__eq = function (a, b)
        if a:len() ~= b:len() then
            return false
        end
        
        for i = 1, a:len() do
            if a:at(i) ~= b:at(i) then
                return false
            end
        end
    
        return true
    end
    
    -- Implement __add for vector addition.
    Vector.__add = function (a, b)
        assert(a:len() == b:len())
        
        local out = Vector:new(a:len())
        
        for i = 1, a:len() do
            out:setAt(i, a:at(i) + b:at(i))
        end
        
        return out
    end
    
    -- Create a new vector with specific size.
    function Vector:new(size)
        assert(size > 0)
        
        self = {}
        self._vec = {}
        
        for i = 1, size do
            table.insert(self._vec, 0)
        end
        
        setmetatable(self, Vector)
        
        return self
    end
    
    -- Create a vector from a Lua array on-the-fly.
    function Vector:fromArray(t)
        local out = Vector:new(#t)
        
        for i, v in ipairs(t) do
            out:setAt(i, v)
        end
        
        return out
    end
    
    function Vector:len()
        return #(self._vec)
    end
    
    function Vector:at(i)
        return self._vec[i]
    end
    
    function Vector:setAt(i, value)
        self._vec[i] = value
    end
    
    -- The main program.
    do
        local p = Vector:fromArray({1, 2, 3})
        local q = Vector:fromArray({2, 3, 4})
        
        local v = p + q
        
        assert(v == Vector:fromArray({3, 5, 7}))
    end

    透過本例,我們可用類似內建數字的符號來操作向量。本例中僅實作 __eq__add 兩個方法,讀者可自行嘗試實作其他的 metamethod。

    泛型

    由於 Lua 是動態型別語言,不需泛型即可自動將同一套程式碼套用在不同型別的參數中。

    你或許對以下產品有興趣
    Xmas tree