MVC - View
在茂密的森林中,鳥兒會築巢 🐦,蜜蜂會傳花粉 🐝,松鼠會儲存堅果 🐿️。
其實網站開發也一樣,每一個畫面(View)就像是一個生命體,它有自己的「家」(Layout)、需要不同的「資訊傳遞方式」(ViewData / ViewBag),甚至還要有準備完善的「儲藏糧食系統」(ViewModel)。
這篇文章,我們就從自然界的角度,帶你用全新的視角理解:
🐦 Layout 是如何像鳥巢一樣包住每個頁面
🐝 ViewData / ViewBag 如何像蜜蜂快速傳遞資訊
🐿️ ViewModel 為什麼像松鼠預先打包好資料,確保整潔與安全
MVC 的 View,是一個「純粹負責呈現資料的角色」,它不處理邏輯、不處理資料,只專注「怎麼顯示」。
MVC vs Razor Pages(.NET 內建)
MVC View
Razor Page
View 是獨立的、由 Controller 控制
View 和處理邏輯寫在一起(PageModel)
較適合大型應用程式分層設計
較適合小型頁面導向開發
分離更清楚:Controller/M ...
Except
🐛 比較問題在 .NET 裡,Distinct()、Except()、Intersect() 這些方法都依賴物件的 相等性判斷。問題是,自訂類別沒有覆寫 Equals/HashCode 時,預設只會用「參考相等」來判斷。
也就是說,就算兩個 User 物件的內容 (Id, Name) 一模一樣,只要它們是從不同 JSON 反序列化回來的,記憶體位址不同 → 就被視為「不同物件」。結果就是:Except() 無法正確比對出差異。
Dictionary、HashSet、Distinct、Except 這些操作的底層共通點:都要先決定「兩個物件是否相等」、「如何計算 HashCode」。預設情況:C# 類別若沒覆寫,會使用物件的參考相等 → 兩個內容相同的物件仍被視為不同。正確做法:告訴系統如何判斷相等:
IEqualityComparer → 外部策略,適合偶爾需要、不同情境可切換。
IEquatable + Equals/GetHashCode → 內建策略,適合固定規則,讓所有集合操作一律遵守。
本質就是「給集合一個可靠的相等性規則」,否則系統只能退回到「記憶 ...
LINQ - SelectMany
艾倫家的地下室有三個抽屜,每個抽屜都要世界的秘密,我們想將它們都攤開
最外層是地下室
次一層表示抽屜們
下一層表示機密文件
Select
1[[照片,日記],[戰爭情報,國家情報],[美女照]]
使用 Select,我們等於搬著三個抽屜離開
SelectMany
1[照片,日記,戰爭情報,國家情報,美女照]
SelectMany,讓我們每一樣任務物品都可以輕易地取出排列整齊,還可以偷偷把美女照藏起來
🐛 如果世界上沒有 SelectMany今天我們有這樣一個集合,有 7 個老師,每位老師有 3 名學生,共 21 個學生
123456789101112List<Teacher> teachers = new List<Teacher> { new Teacher("Allen",new List<Student>{ new Student(100),new Student(90),new Student(80) }), new ...
Extension Method
ExtensionIEnumerable 擴充問題把「由右而左」改寫成「由左到右」字串反轉字串移除特定字元DI 的語法糖:建立可讀的註冊 APIAntiPattern 1 - 綁死生命週期物件AntiPattern 2 - IEnumerable 上做副作用(寄信、寫檔、打 API)AntiPattern 3 - 擴充承載過多業務意義AntiPattern 4 - 在 object 上加「萬能」擴充(例如 ToJson())AntiPattern 5 - 擴充方法包「重策略」:Retry、Timeout、Circuit BreakerSummary語言層級的能力讓我們在不修改原本 source code 的前提下,就能為 class 增加新 method,寫起來像 instance method,C# 編譯器幫你 static method + syntactic sugar
開放封閉原則(OCP)對「不能改、也不該改」的型別開放擴充,例如 .NET Framework、第三方 package、legacy code
123456789101112131415161718void Ma ...
Anonymous type
MobWhat is anonymous type?快速捏出 JSON匯出 CSV結構化 LogLINQ 資料流 - 排序LINQ - GroupBy 的複合鍵(Composite Key)Join / Left Join 後整形(Shaping)結果需要索引的中間運算Distinct/Except/GroupBy 需要多欄位相等比較Dapper 參數包(Param Bag)summary
茂夫 被稱為 Mob,不是因為他弱,而是需要時總在場,不需要時悄悄退場。匿名型別(anonymous type)在 C# 裡就是這種感覺
在方法裡臨時出現、幫你捏 JSON、整理投影(Select)、帶著排序鍵巡邏、裝參數進 Dapper、丟幾個欄位進結構化日誌……做完事就退場,不佔版面、也不必為它立個牌位
匿名型別就像「沒有名字的人」。
1var person = new { Name = "Allen", Age = 30 };
這個 person 裡面確實有 Name 跟 Age 屬性,但它的型別是 編譯器臨時生出來的,當需要方法的回傳結果、宣告屬性& ...
Dependency-Inversion Principle
🪵 我受夠了依賴「依賴」就是一種受到某個東西牽制、影響 的狀態。
一個大叔不抽菸就全身不舒服,這就是對香菸的依賴。手機一沒電就開始焦慮(好像是我),這也是一種依賴。
在系統裡,如果 A 模組必須靠 B 模組才能運作,那我們就說 A 依賴了 B。最典型的情況是:A 模組需要直接建立或呼叫 B 模組的實例,才能完成某個功能。
問題來了 —— 假設「DB 的連線方法」被修改了,那所有用到這個方法的「會員查詢」功能就得跟著改,甚至其他關聯模組也會被迫一起修改。 改動會一路延燒,越燒越大。
事實上,我們設計功能的順序應該是反過來的。不是因為「手邊剛好有鍋子和刀子」才說:「那就來煮菜吧!」而是因為「我們想做一道番茄炒蛋」,所以才需要鍋子來炒,刀子來切。
這正是 DIP(依賴反轉原則) 所強調的:
👉 高階模組(業務邏輯)不應該依賴低階模組(細節實作),兩者都應該依賴抽象。
🪵 IoC:控制反轉就算我們把依賴反轉了,還是得有人「建立實例」吧? 光是改成依賴抽象還不夠,因為在使用的那一刻,我們依然需要一個具體的物件。這時候,「控制反轉(Inversion of Control, IoC) ...
Interface Segregation Principle
違反介面隔離原則的例子符合介面隔離原則的例子與 SRP 的關係與 OCP 的關係summary客戶(Client)不應被迫依賴對其而言無用的方法或功能。換句話說:介面設計不應包山包海,而應拆分成精簡、專注的責任
1234567891011121314151617181920212223242526272829303132333435363738394041interface IPrintFunction{ public void print(); public void scan(); public void fax(); public void copy(); public void copyThenSendMail();}public class EPSON: IPrintFunction{ public void print() { Console.WriteLine("我能列印!"); } public void scan() ...
Liskov Substitution Principle
Inheritance先驗條件(Preconditions)不可加強後驗條件(Postconditions)不可削弱不變條件(Invariants)必須保持錯誤的繼承方式正確的繼承方式更好的抽象:設計為抽象類別DI 與 LSPSUM
在軟體設計中,繼承 (Inheritance) 的發生是再正常不過的事了,作為結構化的系統,我們可以視其為多個組件裝載而成的,當結構益龐大起來,就會想將行為類似的程式碼重用,而有了繼承的概念,但這也造成父類與子類使用上耦合造成的問題,繼承不應該只是為了「重複使用程式碼」而存在,而是應該基於行為上的一致性
因此里氏替換原則 (LSP, Liskov Substitution Principle) 就是在提醒我們
👉 子類別必須能夠替代父類別而不影響系統的正確性
換句話說,當一個地方期待父類別時,若換成子類別,程式應該還是能正常運作、不會出現違反預期的行為
子類別不能比父類別更挑剔輸入。想像你去餐廳,主廚說:「你隨便帶食材來,我都能幫你做菜。」結果徒弟廚師說:「不行!你只能帶牛肉來,不然我不煮。」這就是把條件變得更嚴格,使用者本來以為能帶什麼都行,結果換成 ...
Single Resposibility Principle
SRP 想解決甚麼問題職責的本質:來自業務,而非技術規模越大,越容易失控API節點設計問題為什麼要區分 DTO、Entity、EF Core Model?業務邏輯如何實踐:拆解職責在系統設計裡,最常見也最棘手的問題之一,就是「改 A 怕改到 B」。過度耦合會讓維護變得艱難:一個看似單純的修改,卻可能意外影響其他功能。這也是單一職責原則 (Single Responsibility Principle, SRP) 想要解決的核心:一個組件(class、模組、服務)應該只有一個「變動的原因」
那麼什麼是「職責」?不是單純的「資料庫」、「控制器」或「畫面元件」,而是 業務驅動的需求。一個組件的職責,應該來自同一群「對它提出業務需求的人」。
舉例來說:
帳戶狀態管理:由風控或會員管理部門提出需求。
用戶等級規則:由行銷或會員成長團隊提出需求。
這些需求雖然都跟「用戶」有關,但來自不同的業務關聯方。如果硬是把它們混在同一個組件裡,將來只要一邊業務有變化,另一邊就會被波及,導致「明明沒動它,卻壞了」。在小型專案裡,把功能寫在一起好像還能撐住。但當系統規模逐漸擴大、業務日益複雜,就會開始顯現問題 ...
Polymorphism
巨人之力Static (Compile-time) PolymorphismDynamic (Runtime) PolymorphismInterface PolymorphismAbstract Class Polymorphism結語
尤米爾·弗里茲是最初的巨人之力擁有者。在她死後,國王為了維持這股力量,命令她的三個女兒 ── 瑪莉亞、羅塞、希娜 ── 吃下分屍後的母體。於是,巨人之力被分散成 九大巨人:始祖、進擊、超大型、鎧甲、女巨人、獸、戰鎚、顎、車力
諫山創實作了多型(Polymorphism)
同樣都是「巨人」,卻能展現出完全不同的外觀、能力與戰鬥風格。
基底類別 Titan 定義了巨人的共通特性,而每個具體巨人(ColossalTitan、ArmoredTitan…)則是子類別,實作或覆寫不同的行為
後期的 Eren 同時擁有「進擊的巨人」、「始祖巨人」、「戰鎚巨人」的能力,就像一個物件同時實作多個介面,或覆寫了不同的行為
共通限制:因為尤米爾只活了 13 年,她的繼承者也都受「13 年壽命」的約束,這就像基底類別定義的共同規則,所有子類別都必須遵守
首先 ...






