ที่มา : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C # เป็นภาษาที่ยอดเยี่ยมที่ได้พัฒนาและเติบโตในช่วง 14 ปีที่ผ่านมา นี่เป็นสิ่งที่ยอดเยี่ยมสำหรับนักพัฒนาซอฟต์แวร์ของเราเพราะภาษาที่พัฒนาขึ้นสำหรับผู้ใหญ่ทำให้เรามีฟีเจอร์ภาษามากมายที่เราพร้อมให้บริการ
อย่างไรก็ตามด้วยพลังมากจะกลายเป็นความรับผิดชอบมาก คุณลักษณะเหล่านี้บางอย่างอาจถูกนำไปใช้ในทางที่ผิดหรือบางครั้งก็ยากที่จะเข้าใจว่าทำไมคุณถึงเลือกที่จะใช้คุณสมบัติหนึ่งเหนืออีกคุณสมบัติหนึ่ง ในช่วงหลายปีที่ผ่านมาฟีเจอร์ที่ฉันได้เห็นนักพัฒนาหลายคนประสบคือเมื่อต้องเลือกใช้อินเทอร์เฟซหรือเลือกที่จะใช้คลาสนามธรรม ทั้งสองมีข้อดีและข้อเสียและเวลาที่ถูกต้องและสถานที่ที่จะใช้แต่ละ แต่เราจะตัดสินใจอย่างไร ???
ทั้งสองมีไว้สำหรับการใช้งานซ้ำของฟังก์ชั่นทั่วไประหว่างประเภท ความแตกต่างที่ชัดเจนที่สุดในทันทีคืออินเตอร์เฟสไม่สามารถใช้งานได้ในขณะที่คลาสนามธรรมช่วยให้คุณสามารถใช้พฤติกรรม "พื้นฐาน" หรือ "เริ่มต้น" แล้วมีความสามารถในการ "แทนที่" พฤติกรรมเริ่มต้นนี้ด้วยประเภทที่ได้รับในชั้นเรียน .
ทั้งหมดนี้เป็นสิ่งที่ดีและดีและมีการใช้ซ้ำรหัสและยึดมั่นในหลักการ DRY (อย่าทำซ้ำตัวเอง) ของการพัฒนาซอฟต์แวร์ คลาสที่เป็นนามธรรมนั้นยอดเยี่ยมที่จะใช้เมื่อคุณมีความสัมพันธ์แบบ "เป็น"
ตัวอย่างเช่น: สุนัขจำพวกหนึ่ง“ เป็น” สุนัขประเภทหนึ่ง ดังนั้นพุดเดิ้ลก็คือ พวกเขาทั้งสองสามารถเห่าเหมือนสุนัขทุกตัวสามารถ อย่างไรก็ตามคุณอาจต้องการระบุว่าพุดเดิ้ลพาร์คนั้นแตกต่างจากเปลือกสุนัข "ปริยาย" อย่างมาก ดังนั้นจึงเหมาะสมสำหรับคุณที่จะใช้บางสิ่งดังนี้:
public abstract class Dog
{
public virtual void Bark()
{
Console.WriteLine("Base Class implementation of Bark");
}
}
public class GoldenRetriever : Dog
{
// the Bark method is inherited from the Dog class
}
public class Poodle : Dog
{
// here we are overriding the base functionality of Bark with our new implementation
// specific to the Poodle class
public override void Bark()
{
Console.WriteLine("Poodle's implementation of Bark");
}
}
// Add a list of dogs to a collection and call the bark method.
void Main()
{
var poodle = new Poodle();
var goldenRetriever = new GoldenRetriever();
var dogs = new List<Dog>();
dogs.Add(poodle);
dogs.Add(goldenRetriever);
foreach (var dog in dogs)
{
dog.Bark();
}
}
// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark
//
ดังที่คุณเห็นนี่จะเป็นวิธีที่ดีในการรักษารหัส DRY ของคุณและอนุญาตให้มีการเรียกใช้คลาสพื้นฐานเมื่อประเภทใดก็ตามสามารถพึ่งพา Bark เริ่มต้นแทนการใช้งานกรณีพิเศษ คลาสอย่าง GoldenRetriever, Boxer, Lab ทุกคนสามารถสืบทอด "ค่าเริ่มต้น" (เบสคลาส) เปลือกได้โดยไม่เสียค่าใช้จ่ายเพียงเพราะพวกเขาใช้คลาสนามธรรมของ Dog
แต่ฉันแน่ใจว่าคุณรู้แล้ว
คุณมาที่นี่เพราะคุณต้องการที่จะเข้าใจว่าทำไมคุณถึงต้องการเลือกอินเทอร์เฟซมากกว่าคลาสนามธรรมหรือในทางกลับกัน เหตุผลหนึ่งที่คุณอาจต้องการเลือกอินเทอร์เฟซในคลาสนามธรรมคือเมื่อคุณไม่มีหรือต้องการป้องกันการใช้งานเริ่มต้น ซึ่งมักเป็นเพราะประเภทที่ใช้งานอินเทอร์เฟซที่ไม่เกี่ยวข้องในความสัมพันธ์แบบ“ เป็นแบบ” ที่จริงแล้วพวกเขาไม่จำเป็นต้องเกี่ยวข้องเลยยกเว้นความจริงที่ว่าแต่ละประเภท“ สามารถ” หรือมี“ ความสามารถ” ที่จะทำอะไรบางอย่างหรือมีบางอย่าง
ตอนนี้ห่าหมายความว่าอะไร? ตัวอย่างเช่น: มนุษย์ไม่ใช่เป็ด…และเป็ดไม่ใช่มนุษย์ ค่อนข้างชัดเจน อย่างไรก็ตามทั้งเป็ดและมนุษย์มี "ความสามารถ" ในการว่ายน้ำ (เนื่องจากมนุษย์ผ่านการเรียนว่ายน้ำในชั้นประถมศึกษาปีที่ :)) นอกจากนี้เนื่องจากเป็ดไม่ใช่มนุษย์หรือในทางกลับกันนี่ไม่ใช่ความเป็น "เป็น" ความเป็นจริง แต่แทนที่จะเป็นความสัมพันธ์ "สามารถ" และเราสามารถใช้อินเทอร์เฟซเพื่อแสดงให้เห็นว่า:
// Create ISwimable interface
public interface ISwimable
{
public void Swim();
}
// Have Human implement ISwimable Interface
public class Human : ISwimable
public void Swim()
{
//Human's implementation of Swim
Console.WriteLine("I'm a human swimming!");
}
// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
public void Swim()
{
// Duck's implementation of Swim
Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
}
}
//Now they can both be used in places where you just need an object that has the ability "to swim"
public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
somethingThatCanSwim.Swim();
}
public void Main()
{
var human = new Human();
var duck = new Duck();
var listOfThingsThatCanSwim = new List<ISwimable>();
listOfThingsThatCanSwim.Add(duck);
listOfThingsThatCanSwim.Add(human);
foreach (var something in listOfThingsThatCanSwim)
{
ShowHowYouSwim(something);
}
}
// So at runtime the correct implementation of something.Swim() will be called
// Output:
// Quack! Quack! I'm a Duck swimming!
// I'm a human swimming!
การใช้อินเทอร์เฟซเช่นรหัสด้านบนจะช่วยให้คุณสามารถส่งวัตถุไปยังวิธีการที่“ สามารถ” ทำอะไรบางอย่างได้ รหัสไม่สนใจว่าจะทำอย่างไร ... ทั้งหมดที่รู้คือมันสามารถเรียกใช้วิธีการว่ายน้ำบนวัตถุนั้นและวัตถุนั้นจะรู้ว่าพฤติกรรมใดที่ต้องใช้เวลาทำงานตามประเภทของมัน
อีกครั้งนี้จะช่วยให้รหัสของคุณอยู่แห้งเพื่อที่คุณจะได้ไม่ต้องเขียนวิธีการต่าง ๆ ที่เรียกวัตถุเพื่อ preform ฟังก์ชันหลักเดียวกัน (ShowHowHumanSwims (มนุษย์), ShowHowDuckSwims (เป็ด) ฯลฯ )
การใช้อินเทอร์เฟซที่นี่ช่วยให้วิธีการโทรไม่ต้องกังวลเกี่ยวกับประเภทการทำงานหรือพฤติกรรมของผู้ใช้ มันเพิ่งรู้ว่าเมื่อได้รับส่วนต่อประสานวัตถุแต่ละชิ้นจะต้องใช้วิธีการว่ายน้ำดังนั้นจึงปลอดภัยที่จะเรียกมันในรหัสของตัวเองและอนุญาตให้มีการจัดการพฤติกรรมของวิธีการว่ายน้ำภายในชั้นเรียนของตัวเอง
สรุป:
ดังนั้นกฎหลักง่ายๆของฉันคือใช้คลาสนามธรรมเมื่อคุณต้องการใช้ฟังก์ชั่น“ เริ่มต้น” สำหรับลำดับชั้นของคลาสหรือ / และคลาสหรือประเภทที่คุณทำงานด้วยการแบ่งปันความสัมพันธ์“ คือ” (เช่นพุดเดิ้ล” คือ ” ประเภทของสุนัข)
ในทางกลับกันให้ใช้อินเทอร์เฟซเมื่อคุณไม่มีความสัมพันธ์“ เป็น” แต่มีประเภทที่ใช้ร่วมกัน“ ความสามารถ” ในการทำบางสิ่งหรือมีบางสิ่ง (เช่นเป็ด "ไม่ใช่" มนุษย์) อย่างไรก็ตามเป็ดและมนุษย์แบ่งปัน “ ความสามารถ” ในการว่ายน้ำ)
ข้อแตกต่างที่ควรทราบระหว่างคลาสนามธรรมและอินเทอร์เฟซคือคลาสสามารถใช้หนึ่งถึงหลายอินเตอร์เฟส แต่คลาสสามารถสืบทอดจากคลาสนามธรรมเพียงคลาสเดียวเท่านั้น (หรือคลาสใด ๆ สำหรับเรื่องนั้น) ใช่คุณสามารถซ้อนคลาสและมีลำดับชั้นการสืบทอด (ซึ่งหลายโปรแกรมทำและควรมี) แต่คุณไม่สามารถสืบทอดสองคลาสในนิยามคลาสที่ได้รับมาเดียว (กฎนี้ใช้กับ C # ในภาษาอื่น ๆ ที่คุณสามารถทำได้ เนื่องจากขาดอินเทอร์เฟซในภาษาเหล่านี้เท่านั้น)
โปรดจำไว้ว่าเมื่อใช้อินเทอร์เฟซเพื่อปฏิบัติตามหลักการแยกส่วนต่อประสาน (ISP) ISP ระบุว่าไม่ควรบังคับให้ลูกค้าพึ่งพาวิธีการที่ไม่ได้ใช้ ด้วยเหตุผลนี้อินเทอร์เฟซควรเน้นงานที่เฉพาะเจาะจงและมักจะมีขนาดเล็กมาก (เช่น IDisposable, IComparable)
เคล็ดลับอีกอย่างคือถ้าคุณกำลังพัฒนาฟังก์ชั่นเล็ก ๆ น้อย ๆ ให้ใช้อินเตอร์เฟส หากคุณกำลังออกแบบหน่วยการทำงานขนาดใหญ่ให้ใช้คลาสนามธรรม
หวังว่าสิ่งนี้จะช่วยให้บางคนดีขึ้น!
นอกจากนี้หากคุณสามารถนึกถึงตัวอย่างที่ดีขึ้นหรือต้องการชี้บางสิ่งออกไปได้โปรดทำตามความคิดเห็นด้านล่าง!