# [.NET] OOP 三本柱(封裝、繼承、多型) ###### tags: `Bolger` `.NET` ## [.NET] OOP 三本柱(封裝、繼承、多型) 物件導向設計(Object-Oriented Programming, OOP),三本柱分別是 **封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism)**,除了此三大特性外還有個不得不提到的重要東西就是**抽象(Abstraction)**。就跟寶可夢有三本柱**小火龍、妙花種子、傑尼龜**,當然還有最重要的**皮卡丘**一樣,此特性之間都是密不可分的關係,所以此文章會針對三大特性跟抽象來進行介紹: ### 封裝 (Encapsulation) > 一種將抽象性函式介面的實作細節部份包裝、隱藏起來的方法。同時,它也是一種防止外界呼叫端,去存取物件內部實作細節的手段,這個手段是由程式語言本身來提供的。封裝被視為是物件導向的四項原則之一。 [wiki 封裝][wiki封裝] 封裝可以想像有個黑盒子,不需要理解盒子裡面實作什麼,也不可以控制盒子裡面的東西`(Priavate)`,除非盒子有對外開口`(Public)`,這樣可以確保黑盒子裡面的**一致性**。就如傑尼龜一樣不需要理解龜殼裡面在做什麼,反正可以讓你固定使用水槍攻擊。 ``` csharp var pokemon = new Squirtle(); Console.WriteLine(pokemon.GetAttack()); // 傑尼龜 使用 水槍 攻擊 public interface IPokemon { public string GetAttack(); } public class Squirtle : IPokemon { protected readonly string Name = "傑尼龜"; private readonly string SkillName = "水槍"; public string GetAttack() { return $"{Name} 使用 {SkillName} 攻擊"; } } ``` ### 繼承 (Inheritance) > 繼承可以使得**子類具有父類別別的各種屬性和方法**,而不需要再次編寫相同的代碼。在令子類別繼承父類別別的同時,可以重新定義某些屬性,並重寫某些方法,即覆蓋父類別別的原有屬性和方法,使其獲得與父類別別不同的功能。另外,為子類追加新的屬性和方法也是常見的做法。 >[wiki 繼承][wiki繼承] 繼承就是保留原本物件功能並可以額外增加功能,類似手機貼了保護貼除了獲得有保護力能力外,還保留手機原本的功能,可以提升程式碼的復用性。用寶可夢來舉例就如妙蛙種子進化成妙蛙草時想保留原本的"藤鞭"技能,妙蛙草就可以繼承妙蛙種子並新增新技能,這樣妙蛙草除了可以使用原本"藤鞭"外又可以使用"飛葉快刀"了,但使用**繼承會增加耦合度**,所以使用上還需要思考實際情境是否適合。 ``` csharp var pokemon1 = new Bulbasaur(); Console.WriteLine(pokemon1.GetAttack()); // 妙蛙種子 使用 藤鞭 攻擊 var pokemon2 = new Ivysaur(); Console.WriteLine(pokemon2.GetAttack()); Console.WriteLine(pokemon2.GetAttack1()); // 妙蛙草 使用 藤鞭 攻擊 // 妙蛙草 使用 飛葉快刀 攻擊 public interface IPokemon { public string GetAttack(); } public class Bulbasaur : IPokemon { protected string Name = "妙蛙種子"; private readonly string SkillName = "藤鞭"; public string GetAttack() { return $"{Name} 使用 {SkillName} 攻擊"; } } public class Ivysaur : Bulbasaur { private readonly string SkillName1 = "飛葉快刀"; public Ivysaur() { base.Name = "妙蛙草"; } public string GetAttack1() { return $"{Name} 使用 {SkillName1} 攻擊"; } } ``` ### 多型 (Polymorphism) > 指為不同資料類型的實體提供統一的介面,或使用一個單一的符號來表示多個不同的類型。 > [wiki 多型][wiki多型] 用實際案例就是工廠有同一模板,但可以輸出不同實體例如果凍就可以有草莓、葡萄、蘋果...等多種口味,這樣可以提升程式的可擴充性和可維護性。多型可以分為四種下面會一一介紹: #### 廣義多型 (universal polymorphism) ##### 繼承多型 (inclusion) 繼承多型可以直接使用父類別,可以不理會子類別是什麼類型可以直接使用方法,如果需要使用子類別時方法時需要額外轉型,範例如下: ``` csharp IPokemon pokemon; pokemon = new Charizard(); Console.WriteLine(pokemon.GetAttack()); // 噴火龍 使用 噴射火焰 攻擊 pokemon = new CharizardX(); Console.WriteLine(pokemon.GetAttack()); // 超級噴火龍X 使用 噴射火焰 攻擊 Console.WriteLine(((CharizardX)pokemon).GetAttack1()); // 超級噴火龍X 使用 龍爪 攻擊 public class Charizard : IPokemon { protected string Name = "噴火龍"; private readonly string SkillName = "噴射火焰"; public string GetAttack() { return $"{Name} 使用 {SkillName} 攻擊"; } } public class CharizardX : Charizard { public CharizardX() { base.Name = "超級噴火龍X"; } private readonly string SkillName1 = "龍爪"; public string GetAttack1() { return $"{Name} 使用 {SkillName1} 攻擊"; } } ``` ##### 參數多型 (parametric) `List<T>` 中的 T 就是參數型別,依據參數的型別決定實作的內容,範例如下: ``` csharp public interface IPokemon { public string GetAttack(); } var pokemons = new List<IPokemon>(); ``` #### 特設多型 (ad hoc polymorphism) ##### 多載 (overloading) 相同方法但參數不同,不論是參數數量或形態都屬於多載,範例如下: ``` csharp public class Pokemon { public string GetName() { return "喵喵"; } public string GetName(string name) { return name; } } ``` ##### 強制同型 (coercions) 自動將型別轉換,下面範例就將金額自動轉換成 `string`,範例如下: ``` csharp var pokemon1 = new Bulbasaur(); Console.WriteLine(pokemon1.GetAttack()); // 喵喵 使用 聚寶功 攻擊,獲得 100 元 public interface IPokemon { public string GetAttack(); } public class Bulbasaur : IPokemon { protected string Name = "喵喵"; private readonly string SkillName = "聚寶功"; private readonly decimal Money = 100m; public string GetAttack() { return $"{Name} 使用 {SkillName} 攻擊,獲得 {Money} 元"; } } ``` ### 抽象 (Abstraction) >是指以縮減一個概念或是一個現象的資訊含量來將其廣義化(Generalization)的過程,主要是為了只保存和一特定目的有關的資訊。例如,將一個皮製的足球抽象化成一個球,只保留一般球的屬性和行為等資訊。相似地,亦可以將快樂抽象化成一種情緒,以減少其在情緒中所含的資訊量。 >[wiki 抽象化][wiki抽象化] 簡單地說把真實情況轉換成類別,而這個類別可以包含 **狀態(屬性)** 或是 **行為(方法)**。例如寶可夢我們如果只關心寶可夢名字跟 `屬性` 與 `使用技能` 就可以將其抽象化為下面範例: ``` csharp var pokemon = new Pokemon(); pokemon.Name = "皮卡丘"; pokemon.Property = "電"; pokemon.SkillName = "十萬伏特"; Console.WriteLine(pokemon.GetProperty()); // 皮卡丘 屬性: 電 Console.WriteLine(pokemon.GetAttack()); // 皮卡丘 使用 十萬伏特 攻擊 public class Pokemon { public string Name { get; set; } public string Property { get; set; } public string SkillName { get; set; } public string GetProperty() { return $"{Name} 屬性: {Property}"; } public string GetAttack() { return $"{Name} 使用 {SkillName} 攻擊"; } } ``` ### 參考資料 [小小菜鳥的成長日記 - OOP 三大特性](https://dotblogs.com.tw/wuu1992/2017/10/16/213954) [伍夜黃昏之時 - 三大特性:封裝、繼承、多型](https://rileylin91.github.io/2020/06/19/OOP-2-OOP-Feature/) [OOP 物件導向的四個特性](https://coreychen71.github.io/posts/2019-10/oop/) [設計模式前置知識](https://hackmd.io/@CityChen/S18Fve5KY#%E7%B9%BC%E6%89%BF-amp-%E5%B0%81%E8%A3%9D-amp-%E5%A4%9A%E5%9E%8B) [存取範圍層級](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/accessibility-levels) [wiki封裝]: "https://zh.wikipedia.org/zh-tw/%E5%B0%81%E8%A3%9D_(%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)" [wiki繼承]: "https://zh.wikipedia.org/wiki/%E7%BB%A7%E6%89%BF_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)" [wiki多型]: "https://zh.wikipedia.org/wiki/%E5%A4%9A%E6%80%81_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)" [wiki抽象化]: "https://zh.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E5%8C%96" <!-- 基本圖 --> [C#基本圖]: https://www.mindomo.com/hu/mindmap/alm-for-c-17fd02c289d846dcbc76c02422606b0c