Skip to main content

模式:包裝型別 (Pattern: Wrapper Type)

有時候,需要建立一個行為類似既有型別、但具有特定修改或限制的新型別。例如,你可能想建立一個行為如同向量、但不允許在插入元素後修改元素的集合型別。包裝型別模式是實現此目標的有效方式。

定義 (Definition)​

包裝型別模式是一種設計模式,其中你建立一個包裝既有型別的新型別。包裝型別與原始型別不同,但可在兩者之間互相轉換。

它通常會實作為只有一個欄位的位置結構。

module book::wrapper_type_pattern;

/// Very simple stack implementation using the wrapper type pattern. Does not allow
/// accessing the elements unless they are popped.
public struct Stack<T>(vector<T>) has copy, store, drop;

/// Create a new instance by wrapping the value.
public fun new<T>(value: vector<T>): Stack<T> {
Stack(value)
}

/// Push an element to the stack.
public fun push_back<T>(v: &mut Stack<T>, el: T) {
v.0.push_back(el);
}

/// Pop an element from the stack. Unlike `vector`, this function won't
/// fail if the stack is empty and will return `None` instead.
public fun pop_back<T>(v: &mut Stack<T>): Option<T> {
if (v.0.length() == 0) option::none()
else option::some(v.0.pop_back())
}

/// Get the size of the stack.
public fun size<T>(v: &Stack<T>): u64 {
v.0.length()
}

常見作法 (Common Practices)​

當目標是擴充既有型別的行為時,通常會為被包裝的型別提供存取子。這種作法可讓使用者在需要時直接存取底層型別。例如,在下列程式碼中,我們為 Stack 型別提供 inner()、inner_mut() 與 into_inner() 方法。

/// Allows reading the contents of the `Stack`.
public fun inner<T>(v: &Stack<T>): &vector<T> { &v.0 }

/// Allows mutable access to the contents of the `Stack`.
public fun inner_mut<T>(v: &mut Stack<T>): &mut vector<T> { &mut v.0 }

/// Unpacks the `Stack` into the underlying `vector`.
public fun into_inner<T>(v: Stack<T>): vector<T> {
let Stack(inner) = v;
inner
}

優點 (Advantages)​

包裝型別模式提供數項優點:

  • 自訂函式:可讓你為既有型別定義自訂函式。
  • 穩健的函式簽章:將函式簽章限制為新型別,因而讓程式碼更加穩健。
  • 可讀性提升:透過提供更具描述性的型別名稱,通常可提升程式碼的可讀性。

缺點 (Disadvantages)​

包裝型別模式在兩種情境中相當強大:當你想限制既有型別的行為,同時為相同資料結構提供自訂介面時;以及當你想擴充既有型別的行為時。然而,它仍有一些限制:

  • 冗長:實作時可能相當冗長,尤其是當你想公開被包裝型別的所有方法時。
  • 精簡實作:實作可能相當精簡,因為它通常只會將呼叫轉送至被包裝型別。

後續步驟 (Next Steps)​

包裝型別模式非常實用,尤其是搭配集合型別使用時,如前一節所示。下一節將介紹動態欄位——這是一項重要的基本元素,可啟用動態集合,讓你能以更具彈性但成本更高的方式儲存大量資料。