<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>擴展型別 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E6%93%B4%E5%B1%95%E5%9E%8B%E5%88%A5/</link><description>Recent content in 擴展型別 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E6%93%B4%E5%B1%95%E5%9E%8B%E5%88%A5/index.xml" rel="self" type="application/rss+xml"/><item><title>Extension Type</title><link>https://tarrragon.github.io/blog/flutter/knowledge-cards/extension-type/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/flutter/knowledge-cards/extension-type/</guid><description>&lt;p>Extension type 是 Dart 3 引入的語言特性：在編譯期把一個型別包裝成另一個名字、限縮可用的 API、而 runtime 不存在額外物件（零成本抽象）。它跟 class 的差異在「沒有 runtime 開銷、但也沒有 runtime 的型別檢查——&lt;code>is&lt;/code> 跟 &lt;code>as&lt;/code> 看到的是底層型別」。同樣是「限縮型別可用介面」的目標，&lt;a href="https://tarrragon.github.io/blog/flutter/knowledge-cards/freezed/" data-link-title="freezed" data-link-desc="Dart 的 immutable data class 程式碼生成器。freezed 自動產生 copyWith、equals、toString、sealed union——它是 Dart 生態把 copyWith 推成預設路徑的主要推力。">freezed&lt;/a> 生成的 class 走 runtime 路徑、extension type 走編譯期路徑，兩者是互斥的實作選擇。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Extension type 是實作 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/value-object/" data-link-title="Value Object" data-link-desc="判斷一個概念該用內容比對還是身份追蹤時使用。value object 的同一性由內容定義——內容相等就是同一個、替換實例對系統沒有影響。">value object&lt;/a> 語意封閉的一種載體。金額用 extension type 包 Decimal：開放的運算限於領域有意義的集合（加減、乘數量、乘倍率），差集裡的誤用（金額乘金額）在編譯期就走不通。跟用 class 包裝的差異：class 有 runtime 身份（&lt;code>is Money&lt;/code> 為 true）但有 overhead；extension type 零 overhead 但 runtime 是透明的。這條分野也決定了誰適合搭 &lt;a href="https://tarrragon.github.io/blog/flutter/knowledge-cards/copywith/" data-link-title="copyWith" data-link-desc="物件的逐欄位覆寫方法在什麼時候是正確工具、什麼時候是逃生口時使用。copyWith 對資料袋語意清晰、對有領域方法的 entity 是繞過不變式的逃生口。">copyWith&lt;/a>——extension type 沒有 runtime 物件可覆寫欄位，逐欄位覆寫語意只在 class 路徑上成立。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Extension type 的 subtype 決策要在設計時做好——它是底層型別的 subtype（寬鬆：可以隱式 upcast 回底層型別）還是獨立型別（嚴格：只能顯式拆封）。前者方便、但封裝邊界弱；後者安全、但每個需要底層型別的銜接點都要顯式拆封。顯式拆封的出口要有語意明確的名字（如 &lt;code>toDecimal()&lt;/code>），見 &lt;a href="https://tarrragon.github.io/blog/ddd/construction-path-design/" data-link-title="建構路徑設計" data-link-desc="工廠表達力不足時缺陷如何被逃生口吸收——逃生口讓正確的修法變不必要、以語意錯誤在下游復發。含原始值官方出口的穩態邊界、封裝擺盪的判讀。">建構路徑設計&lt;/a>。Migration 案例見 &lt;a href="https://tarrragon.github.io/blog/work-log/dart_money_extension_type_migration/" data-link-title="金額型別的三段遷移：double、Decimal、再到 Money extension type" data-link-desc="金額欄位從 double 換 Decimal 只解決精度、沒解決「任何人都能對它做無意義運算」；用 Dart extension type 包成 Money 之後，型別系統只開放領域有意義的運算。含 implements Object 的 subtype 設計、以及大規模型別遷移前先寫 characterization test 鎖行為的做法。">Money 三段遷移&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Extension type 是 Dart 3 引入的語言特性：在編譯期把一個型別包裝成另一個名字、限縮可用的 API、而 runtime 不存在額外物件（零成本抽象）。它跟 class 的差異在「沒有 runtime 開銷、但也沒有 runtime 的型別檢查——<code>is</code> 跟 <code>as</code> 看到的是底層型別」。同樣是「限縮型別可用介面」的目標，<a href="/blog/flutter/knowledge-cards/freezed/" data-link-title="freezed" data-link-desc="Dart 的 immutable data class 程式碼生成器。freezed 自動產生 copyWith、equals、toString、sealed union——它是 Dart 生態把 copyWith 推成預設路徑的主要推力。">freezed</a> 生成的 class 走 runtime 路徑、extension type 走編譯期路徑，兩者是互斥的實作選擇。</p>
<h2 id="概念位置">概念位置</h2>
<p>Extension type 是實作 <a href="/blog/ddd/knowledge-cards/value-object/" data-link-title="Value Object" data-link-desc="判斷一個概念該用內容比對還是身份追蹤時使用。value object 的同一性由內容定義——內容相等就是同一個、替換實例對系統沒有影響。">value object</a> 語意封閉的一種載體。金額用 extension type 包 Decimal：開放的運算限於領域有意義的集合（加減、乘數量、乘倍率），差集裡的誤用（金額乘金額）在編譯期就走不通。跟用 class 包裝的差異：class 有 runtime 身份（<code>is Money</code> 為 true）但有 overhead；extension type 零 overhead 但 runtime 是透明的。這條分野也決定了誰適合搭 <a href="/blog/flutter/knowledge-cards/copywith/" data-link-title="copyWith" data-link-desc="物件的逐欄位覆寫方法在什麼時候是正確工具、什麼時候是逃生口時使用。copyWith 對資料袋語意清晰、對有領域方法的 entity 是繞過不變式的逃生口。">copyWith</a>——extension type 沒有 runtime 物件可覆寫欄位，逐欄位覆寫語意只在 class 路徑上成立。</p>
<h2 id="設計責任">設計責任</h2>
<p>Extension type 的 subtype 決策要在設計時做好——它是底層型別的 subtype（寬鬆：可以隱式 upcast 回底層型別）還是獨立型別（嚴格：只能顯式拆封）。前者方便、但封裝邊界弱；後者安全、但每個需要底層型別的銜接點都要顯式拆封。顯式拆封的出口要有語意明確的名字（如 <code>toDecimal()</code>），見 <a href="/blog/ddd/construction-path-design/" data-link-title="建構路徑設計" data-link-desc="工廠表達力不足時缺陷如何被逃生口吸收——逃生口讓正確的修法變不必要、以語意錯誤在下游復發。含原始值官方出口的穩態邊界、封裝擺盪的判讀。">建構路徑設計</a>。Migration 案例見 <a href="/blog/work-log/dart_money_extension_type_migration/" data-link-title="金額型別的三段遷移：double、Decimal、再到 Money extension type" data-link-desc="金額欄位從 double 換 Decimal 只解決精度、沒解決「任何人都能對它做無意義運算」；用 Dart extension type 包成 Money 之後，型別系統只開放領域有意義的運算。含 implements Object 的 subtype 設計、以及大規模型別遷移前先寫 characterization test 鎖行為的做法。">Money 三段遷移</a>。</p>
]]></content:encoded></item></channel></rss>