[.NET] OOP 三本柱(封裝、繼承、多型)
物件導向設計(Object-Oriented Programming, OOP),三本柱分別是 封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism) ,除了三大特性外還有一個很重要的特性哪就是 抽象(Abstraction) 。就如同初代寶可夢一樣有 小火龍、妙花種子、傑尼龜 這三本柱,當然還有一位至今還非常重要的 皮卡丘 一樣,這幾個特性之間的關係都是密不可分,所以文章會針對三大特性跟抽象來進行介紹:
封裝 (Encapsulation)
一種將抽象性函式介面的實作細節部份包裝、隱藏起來的方法。同時,它也是一種防止外界呼叫端,去存取物件內部實作細節的手段,這個手段是由程式語言本身來提供的。封裝被視為是物件導向的四項原則之一。
wiki 封裝
封裝可以想像有個黑盒子,不需要理解盒子裡面實作什麼,也不可以控制盒子裡面的東西(Priavate)
,除非盒子有對外開口(Public)
,這樣可以確保黑盒子裡面的一致性。就如傑尼龜一樣不需要理解龜殼裡面在做什麼,反正可以讓你固定使用水槍攻擊。
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 繼承
繼承就是保留原本物件功能並可以額外增加功能,類似手機貼了保護貼除了獲得有保護力能力外,還保留手機原本的功能,可以提升程式碼的復用性。用寶可夢來舉例就如妙蛙種子進化成妙蛙草時想保留原本的”藤鞭”技能,妙蛙草就可以繼承妙蛙種子並新增新技能,這樣妙蛙草除了可以使用原本”藤鞭”外又可以使用”飛葉快刀”了,但使用繼承會增加耦合度,所以使用上還需要思考實際情境是否適合。
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 多型
用實際案例就是工廠有同一模板,但可以輸出不同實體例如果凍就可以有草莓、葡萄、蘋果…等多種口味,這樣可以提升程式的可擴充性和可維護性。多型可以分為四種下面會一一介紹:
廣義多型 (universal polymorphism)
繼承多型 (inclusion)
繼承多型可以直接使用父類別,可以不理會子類別是什麼類型可以直接使用方法,如果需要使用子類別時方法時需要額外轉型,範例如下:
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 就是參數型別,依據參數的型別決定實作的內容,範例如下:
public interface IPokemon
{
public string GetAttack();
}
var pokemons = new List<IPokemon>();
特設多型 (ad hoc polymorphism)
多載 (overloading)
相同方法但參數不同,不論是參數數量或形態都屬於多載,範例如下:
public class Pokemon
{
public string GetName()
{
return "喵喵";
}
public string GetName(string name)
{
return name;
}
}
強制同型 (coercions)
自動將型別轉換,下面範例就將金額自動轉換成 string
,範例如下:
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 抽象化
簡單地說把真實情況轉換成類別,而這個類別可以包含 狀態(屬性) 或是 行為(方法)。例如寶可夢我們如果只關心寶可夢名字跟 屬性
與 使用技能
就可以將其抽象化為下面範例:
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} 攻擊";
}
}