เมื่อใดที่จะใช้อินเทอร์เฟซแทนคลาสนามธรรมและในทางกลับกัน


427

นี่อาจเป็นคำถาม OOP ทั่วไป ฉันต้องการทำการเปรียบเทียบทั่วไประหว่างอินเทอร์เฟซและคลาสนามธรรมบนพื้นฐานของการใช้งานของพวกเขา

เมื่อใดที่จะต้องการใช้อินเทอร์เฟซและเมื่อใดที่จะต้องการใช้คลาสนามธรรม ?


1
มีการถามเรื่องนี้
บ่อยครั้ง

1
นอกเหนือจากคำตอบด้านล่างนี้เป็นรายการสั้น ๆ เกี่ยวกับที่ที่คุณอาจต้องการอินเทอร์เฟซและที่คุณอาจไม่: เมื่อต้องการใช้อินเทอร์เฟซ: msdn.microsoft.com/en-us/library/3b5b8ezk(v=vs.80) .aspx
Anthony

ใช้นามธรรมเมื่อคุณไม่แน่ใจว่าชั้นเรียนจะทำอะไร ใช้ส่วนต่อประสานถ้าคุณเป็น
UğurGümüşhan

ฉันสนใจที่จะดูว่านักพัฒนาซอฟต์แวร์จำนวนมากที่ไม่ได้ทำงานที่ Microsoft กำหนดและใช้ส่วนต่อประสานในการพัฒนาแบบวันต่อวัน
user1451111

คำตอบ:


431

ฉันเขียนบทความเกี่ยวกับเรื่องนั้น:

คลาสและอินเตอร์เฟสแบบนามธรรม

สรุป:

เมื่อเราพูดถึงคลาสนามธรรมเราจะนิยามลักษณะของประเภทวัตถุ การระบุว่าวัตถุคืออะไร

เมื่อเราพูดถึงอินเทอร์เฟซและกำหนดความสามารถที่เราสัญญาว่าจะให้บริการเรากำลังพูดถึงการสร้างสัญญาเกี่ยวกับสิ่งที่วัตถุสามารถทำได้


114
สิ่งนี้มีประโยชน์Interfaces do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk"มาก: ขอบคุณ
aexl

2
ดูเหมือนว่าลิงก์ของคุณจะตาย
Kim Ahlstrøm Meyn Mathiassen

คำอธิบายของอเล็กซ์ด้านล่าง: ความแตกต่างระหว่างฟังก์ชั่นการอธิบายที่นำไปใช้กับการอธิบายสถานะที่เก็บไว้ดูเหมือนจะเป็นคำตอบที่ดีกว่าสำหรับคำถามนี้เพราะความแตกต่างไม่ใช่แค่ปรัชญา
Duncan Malashock

1
Duncan Malashock ไม่ได้จริงๆ คำตอบของ Jorge นั้นดีกว่า คำตอบของอเล็กซ์มุ่งเน้นไปที่กลศาสตร์ในขณะที่อร์เฆมีความหมายมากขึ้น
Nazar Merza

8
ฉันชอบข้อความก่อนคำตอบที่คุณระบุ:Use abstract classes and inheritance if you can make the statement “A is a B”. Use interfaces if you can make the statement “A is capable of [doing] as”
S1r-Lanzelot

433

คลาสนามธรรมสามารถมีสถานะหรือการใช้งานร่วมกันได้ อินเทอร์เฟซเป็นเพียงสัญญาให้รัฐหรือฟังก์ชันการทำงาน คลาสนามธรรมที่ดีจะลดจำนวนของรหัสที่ต้องเขียนใหม่เนื่องจากการทำงานหรือสถานะของมันสามารถใช้ร่วมกันได้ อินเทอร์เฟซไม่มีข้อมูลที่กำหนดที่จะแบ่งปัน


65
สำหรับฉันนี่เป็นคำตอบที่ดีที่สุดที่นี่และเป็นความอัปยศที่ไม่ได้รับการโหวตสูงกว่า ใช่มีความแตกต่างทางปรัชญาระหว่างแนวคิดทั้งสอง แต่จุดรูตคือคลาสนามธรรมทำให้แน่ใจว่าลูกหลานทั้งหมดจะใช้ฟังก์ชัน / สถานะร่วมกัน
drharris

3
ตัวอย่างเช่นคลาสฐานนามธรรมถูกใช้สำหรับรูปแบบการออกแบบเมธอดเทมเพลตในขณะที่อินเตอร์เฟสใช้สำหรับรูปแบบการออกแบบกลยุทธ์
Raedwald

1
ฉันคิดว่าบทสรุปของ Jorge อธิบายหลักแม้ว่าเบื้องหลังการดำรงอยู่ของพวกเขาทั้งสองในขณะที่คำตอบของ Alex คือความแตกต่างในผลลัพธ์ ฉันหวังว่าฉันสามารถทำเครื่องหมายทั้งสองเป็นคำตอบที่ถูกต้อง แต่ฉันยังคงต้องการคำตอบของ Jorge
Chirantan

และนี่คือตัวอย่างที่มีรหัส
shaijut

สำหรับฉันแล้ว "คลาสนามธรรมที่ดีจะลดจำนวนรหัสที่ต้องเขียนใหม่เนื่องจากการทำงานหรือสถานะสามารถใช้ร่วมกันได้" คำสั่งเป็นหลักของคำตอบ
Div Tiwari

82

โดยส่วนตัวฉันแทบจะไม่จำเป็นต้องเขียนคลาสนามธรรมเลย

บ่อยครั้งที่ฉันเห็นว่ามีการใช้คลาสนามธรรม (mis) เป็นเพราะผู้เขียนคลาสนามธรรมใช้รูปแบบ "วิธีการแบบ"

ปัญหาเกี่ยวกับ "วิธีการแบบ" คือมันเกือบทุกครั้งที่ผู้เข้ามาใหม่ - คลาส "ที่ได้รับ" ไม่เพียง แต่รู้วิธีการ "นามธรรม" ของคลาสพื้นฐานที่ใช้งาน แต่ยังเกี่ยวกับวิธีการสาธารณะของคลาสพื้นฐาน แม้ว่าเวลาส่วนใหญ่จะไม่จำเป็นต้องเรียกพวกเขา

ตัวอย่าง (เรียบง่ายเกินไป):

abstract class QuickSorter
{
    public void Sort(object[] items)
    {
        // implementation code that somewhere along the way calls:
        bool less = compare(x,y);
        // ... more implementation code
    }
    abstract bool compare(object lhs, object rhs);
}

ดังนั้นที่นี่ผู้เขียนของคลาสนี้ได้เขียนอัลกอริทึมทั่วไปและตั้งใจสำหรับคนที่จะใช้มันโดย "เชี่ยวชาญ" โดยการให้ "hooks" ของตัวเอง - ในกรณีนี้วิธีการ "เปรียบเทียบ"

ดังนั้นการใช้งานที่ตั้งใจไว้เป็นดังนี้:

class NameSorter : QuickSorter
{
    public bool compare(object lhs, object rhs)
    {
        // etc.
    }
}

ปัญหาที่เกิดขึ้นคือคุณได้ผสมผสานแนวคิดทั้งสองอย่างเกินควร:

  1. วิธีเปรียบเทียบสองรายการ (สิ่งที่ควรทำก่อน)
  2. วิธีการเรียงลำดับรายการ (เช่น quicksort vs merge sort เป็นต้น)

ในโค้ดข้างต้นตามหลักทฤษฏีผู้เขียนวิธีการ "เปรียบเทียบ" สามารถโทรกลับไปยังวิธีการ "เรียงลำดับ" superclass อีกครั้ง ... แม้ว่าในทางปฏิบัติพวกเขาจะไม่ต้องการหรือต้องการทำสิ่งนี้

ราคาที่คุณจ่ายสำหรับข้อต่อที่ไม่จำเป็นนี้คือมันยากที่จะเปลี่ยนซูเปอร์คลาสและในภาษา OO ส่วนใหญ่ไม่สามารถเปลี่ยนได้ในรันไทม์

วิธีทางเลือกคือใช้รูปแบบการออกแบบ "กลยุทธ์" แทน:

interface IComparator
{
    bool compare(object lhs, object rhs);
}

class QuickSorter
{
    private readonly IComparator comparator;
    public QuickSorter(IComparator comparator)
    {
        this.comparator = comparator;
    }

    public void Sort(object[] items)
    {
        // usual code but call comparator.Compare();
    }
}

class NameComparator : IComparator
{
    bool compare(object lhs, object rhs)
    {
        // same code as before;
    }
}

ดังนั้นขอให้สังเกตตอนนี้: ทั้งหมดที่เรามีคืออินเทอร์เฟซและการใช้งานอินเทอร์เฟซเหล่านั้นอย่างเป็นรูปธรรม ในทางปฏิบัติคุณไม่จำเป็นต้องมีอย่างอื่นในการออกแบบ OO ระดับสูง

ในการ "ซ่อน" ข้อเท็จจริงที่เราได้ใช้ "การเรียงลำดับชื่อ" โดยใช้คลาส "QuickSort" และ "NameComparator" เราอาจยังคงเขียนวิธีการจากโรงงานไว้:

ISorter CreateNameSorter()
{
    return new QuickSorter(new NameComparator());
}

ใดคุณมีคลาสนามธรรมคุณสามารถทำเช่นนี้ ... แม้ว่าจะมีความสัมพันธ์แบบผู้เข้ามาใหม่ระหว่างฐานและคลาสที่ได้รับมาโดยธรรมชาติก็มักจะจ่ายให้ชัดเจน

หนึ่งความคิดสุดท้าย: สิ่งที่เราทำไปแล้วคือ "เขียน" ฟังก์ชั่น "NameSorting" โดยใช้ฟังก์ชั่น "QuickSort" และฟังก์ชั่น "NameComparison" ... ในภาษาโปรแกรมที่ใช้งานได้รูปแบบการเขียนโปรแกรมนี้ยิ่งเป็นธรรมชาติมากขึ้น ด้วยรหัสน้อย


5
เพียงเพราะคุณสามารถใช้คลาสนามธรรมหรือรูปแบบวิธีการเทมเพลตไม่ได้หมายความว่าคุณต้องหลีกเลี่ยง รูปแบบกลยุทธ์เป็นรูปแบบที่แตกต่างกันสำหรับสถานการณ์ที่แตกต่างเช่นในตัวอย่างนี้ แต่มีตัวอย่างมากมายที่รูปแบบแม่แบบเหมาะสมกว่ากลยุทธ์
Jorge Córdoba

5
ในประสบการณ์ของฉันฉันไม่เคยพบเจอพวกเขา (ในกรณีที่วิธีการใช้แม่แบบเป็นที่นิยม) ... หรือไม่ค่อย และนั่นคือทั้งหมด "นามธรรม" คือ - รองรับภาษาสำหรับรูปแบบการออกแบบ "เทมเพลต"
Paul Hollingsworth

ตกลงฉันใช้มันครั้งเดียวสำหรับระบบผู้เชี่ยวชาญที่กระบวนการเป็นเหมือนได้รับ 1. FillTheParameters, 2. สร้างผลิตภัณฑ์เวกเตอร์ระหว่างพวกเขา, 3. สำหรับผลลัพธ์การคำนวณแต่ละคู่, 4. เข้าร่วมผลลัพธ์โดยขั้นตอนที่ 1 และ 3 ที่มอบหมายและ 2 และ 4 ดำเนินการในชั้นฐาน
Jorge Córdoba

10
ฉันพบว่าการใช้คลาสนามธรรมยากต่อการเข้าใจ การคิดในแง่ของกล่องที่สื่อสารกันแทนที่จะเป็นความสัมพันธ์ทางมรดกนั้นง่ายกว่า (สำหรับฉัน) ... แต่ฉันก็เห็นด้วยว่าภาษา OO ในปัจจุบันบังคับให้สำเร็จรูปมากเกินไป ... การทำงานจะเป็นหนทางไป OO
Paul Hollingsworth

4
ตัวอย่างของการใช้ในทางที่ผิดนั้นค่อนข้างเล็กน้อย มันไม่ค่อยเดือดลงไปกับฟังก์ชั่นปล้นที่ดีเช่นการเปรียบเทียบ ที่พบบ่อยมากขึ้นคือสถานการณ์ที่มีฟังก์ชันการทำงานเริ่มต้นบางอย่างที่คลาสที่ได้รับมาแทนที่หรือขยาย (และในกรณีหลังมันใช้ได้อย่างสมบูรณ์ในการเรียกใช้ฟังก์ชันคลาสพื้นฐาน) ในตัวอย่างของคุณไม่มีฟังก์ชั่นเริ่มต้นดังนั้นการใช้คลาสนามธรรมจึงไม่มีเหตุผล
SomeWittyUsername

41

หากคุณกำลังมองหาจาวาเป็นภาษา OOP

" อินเตอร์เฟสไม่ได้จัดเตรียมการใช้วิธีการ " ไม่สามารถใช้ได้กับการเปิดตัว Java 8 อีกต่อไป ตอนนี้จาวาให้การใช้งานในส่วนติดต่อสำหรับวิธีการเริ่มต้น

ในแง่ง่ายฉันต้องการที่จะใช้

อินเทอร์เฟซ:การใช้สัญญาโดยวัตถุที่ไม่เกี่ยวข้องหลายรายการ มันให้ความสามารถ " HAS A "

ระดับนามธรรม:เพื่อใช้พฤติกรรมที่เหมือนกันหรือแตกต่างระหว่างวัตถุที่เกี่ยวข้องหลายรายการ มันสร้างความสัมพันธ์" IS A "

เว็บไซต์ Oracle ให้ความแตกต่างที่สำคัญระหว่างinterfaceและabstractคลาส

ลองใช้คลาสนามธรรมหาก:

  1. คุณต้องการแบ่งปันรหัสระหว่างคลาสที่เกี่ยวข้องอย่างใกล้ชิดหลายชั้น
  2. คุณคาดหวังว่าคลาสที่ขยายคลาส abstract ของคุณมีวิธีหรือฟิลด์ทั่วไปจำนวนมากหรือต้องการตัวดัดแปลงการเข้าถึงอื่นที่ไม่ใช่สาธารณะ (เช่นได้รับการป้องกันและส่วนตัว)
  3. คุณต้องการประกาศเขตข้อมูลไม่คงที่หรือไม่ใช่เขตข้อมูลสุดท้าย

พิจารณาใช้อินเตอร์เฟสหาก:

  1. คุณคาดหวังว่าคลาสที่ไม่เกี่ยวข้องจะใช้อินเทอร์เฟซของคุณ ตัวอย่างเช่นวัตถุที่ไม่เกี่ยวข้องจำนวนมากสามารถใช้Serializableอินเตอร์เฟซ
  2. คุณต้องการระบุลักษณะการทำงานของชนิดข้อมูลเฉพาะ แต่ไม่ต้องกังวลว่าใครจะใช้พฤติกรรมของมัน
  3. คุณต้องการใช้ประโยชน์จากการสืบทอดหลาย ๆ แบบ

ตัวอย่าง:

ระดับนามธรรม ( เป็นความสัมพันธ์)

Readerเป็นคลาสนามธรรม

BufferedReaderเป็นReader

FileReaderเป็นReader

FileReaderและBufferedReaderใช้เพื่อจุดประสงค์ทั่วไป: การอ่านข้อมูลและเกี่ยวข้องกันReaderชั้นเรียน

อินเตอร์เฟส ( ความสามารถHAS A )

Serializableเป็นอินเตอร์เฟส

สมมติว่าคุณมีสองคลาสในแอปพลิเคชันซึ่งกำลังใช้งานSerializableอินเทอร์เฟซ

Employee implements Serializable

Game implements Serializable

ที่นี่คุณไม่สามารถสร้างความสัมพันธ์ผ่านSerializableอินเทอร์เฟซระหว่างEmployeeและGameซึ่งมีวัตถุประสงค์เพื่อวัตถุประสงค์ที่แตกต่างกัน ทั้งสองมีความสามารถในการทำให้เป็นอันดับรัฐและการเปรียบเทียบสิ้นสุดลงที่นั่น

ดูที่โพสต์เหล่านี้:

ฉันจะอธิบายความแตกต่างระหว่างอินเทอร์เฟซกับคลาสนามธรรมได้อย่างไร


40

ตกลงมีเพียง "grokked" ตัวเอง - นี่มันอยู่ในเงื่อนไขของคนธรรมดา (รู้สึกอิสระที่จะแก้ไขฉันถ้าฉันผิด) - ฉันรู้ว่าหัวข้อนี้เป็น oooooold แต่คนอื่นอาจสะดุดในวันหนึ่ง ...

คลาสนามธรรมช่วยให้คุณสร้างพิมพ์เขียวและอนุญาตให้คุณสร้างคุณสมบัติและวิธีการ CONSTRUCT (implement) เพิ่มเติมที่คุณต้องการให้ลูกหลานทั้งหมดของมันครอบครอง

อินเทอร์เฟซในอีกทางหนึ่งช่วยให้คุณสามารถประกาศว่าคุณต้องการคุณสมบัติและ / หรือวิธีการที่มีชื่อที่กำหนดให้มีอยู่ในทุกชั้นที่ใช้มัน - แต่ไม่ได้ระบุวิธีที่คุณควรใช้มัน นอกจากนี้คลาสสามารถใช้อินเตอร์เฟส MANY ได้ แต่สามารถขยายคลาส Abstract ได้เพียงคลาสเดียวเท่านั้น อินเทอร์เฟซเป็นเครื่องมือสถาปัตยกรรมระดับสูงมากกว่า (ซึ่งจะชัดเจนขึ้นหากคุณเริ่มเข้าใจรูปแบบการออกแบบ) - บทคัดย่อมีเท้าในค่ายทั้งสองและสามารถทำงานสกปรกบางอย่างได้เช่นกัน

เหตุใดจึงต้องใช้อีกอันหนึ่ง อดีตช่วยให้มากขึ้นเป็นรูปธรรมนิยามของลูกหลาน - หลังช่วยให้มากขึ้นหลายรูปแบบ จุดสุดท้ายนี้มีความสำคัญต่อผู้ใช้ / ผู้เข้ารหัสซึ่งสามารถใช้ข้อมูลนี้เพื่อใช้ AP I (nterface)ในชุดค่าผสม / รูปร่างที่หลากหลายเพื่อให้เหมาะกับความต้องการของพวกเขา

ฉันคิดว่านี่เป็นช่วงเวลาที่ "หลอดไฟ" สำหรับฉัน - คิดถึงอินเทอร์เฟซน้อยกว่าจากมุมมองของผู้เขียนและอีกมากจาก coder ใด ๆ ที่จะมาในภายหลังในห่วงโซ่ที่เพิ่มการใช้งานโครงการหรือขยาย API


เพื่อสร้างสิ่งนี้: วัตถุที่ใช้อินเทอร์เฟซใช้กับมันเป็นประเภท นี่เป็นสิ่งสำคัญ ดังนั้นคุณสามารถส่งผ่านรูปแบบต่าง ๆ ของอินเทอร์เฟซไปยังคลาส แต่อ้างถึงพวกเขา (และวิธีการของพวกเขา) ด้วยชื่อประเภทของอินเตอร์เฟซ ดังนั้นคุณไม่จำเป็นต้องใช้สวิตช์หรือถ้า / อื่นวนซ้ำ ลองแบบฝึกหัดนี้ในหัวข้อ - มันสาธิตการใช้อินเตอร์เฟสผ่านรูปแบบกลยุทธ์ phpfreaks.com/tutorial/design-patterns---strategy-and-bridge/…
sunwukung

ฉันเห็นด้วยทั้งหมดเกี่ยวกับช่วงเวลาที่หลอดไฟของคุณ: "API (nterface) ในชุดค่าผสม / รูปร่างที่หลากหลายเพื่อให้เหมาะกับความต้องการของพวกเขา"! จุดที่ดีมากที่จะทำ
Trevor Boyd Smith

39

สองเซ็นต์ของฉัน:

อินเทอร์เฟซโดยทั่วไปกำหนดสัญญาว่าคลาสใด ๆ ที่ใช้งานต้องปฏิบัติตาม (ใช้สมาชิกอินเทอร์เฟซ) ไม่มีรหัสใด ๆ

ในทางตรงกันข้ามคลาสนามธรรมสามารถมีรหัสและอาจมีวิธีการทำเครื่องหมายเป็นนามธรรมซึ่งคลาสสืบทอดจะต้องดำเนินการ

สถานการณ์ที่ไม่ค่อยเกิดขึ้นที่ฉันใช้คลาสนามธรรมคือเมื่อฉันมีฟังก์ชั่นเริ่มต้นบางอย่างที่คลาสสืบทอดอาจไม่น่าสนใจในการเอาชนะกล่าวคลาสฐานนามธรรมที่คลาสเฉพาะบางคลาสสืบทอดมา

ตัวอย่าง (เป็นพื้นฐานมาก!): พิจารณาชั้นฐานที่เรียกว่าลูกค้าซึ่งมีวิธีการที่เป็นนามธรรมเช่นCalculatePayment(), CalculateRewardPoints()และบางส่วนวิธีการที่ไม่ใช่นามธรรมเช่น,GetName()SavePaymentDetails()

คลาสพิเศษที่ชอบRegularCustomer และGoldCustomerจะสืบทอดจากCustomerคลาสฐานและใช้ตรรกะCalculatePayment()และCalculateRewardPoints()เมธอดของตนเองแต่จะใช้GetName()และSavePaymentDetails()วิธีการใหม่

คุณสามารถเพิ่มฟังก์ชันการทำงานเพิ่มเติมให้กับคลาสนามธรรม (ไม่ใช่วิธีที่เป็นนามธรรม) โดยไม่มีผลกับคลาสย่อยที่ใช้เวอร์ชันเก่ากว่า ขณะที่การเพิ่มเมธอดให้กับอินเตอร์เฟสจะมีผลกับคลาสทั้งหมดที่นำไปใช้งานเนื่องจากพวกเขาต้องการใช้สมาชิกอินเทอร์เฟซที่เพิ่งเพิ่มเข้าไปใหม่

คลาสนามธรรมกับสมาชิกนามธรรมทั้งหมดจะคล้ายกับอินเทอร์เฟซ


1
+1 สำหรับ "คุณสามารถเพิ่มฟังก์ชันการทำงานเพิ่มเติมให้กับคลาสนามธรรม (ไม่ใช่วิธีที่เป็นนามธรรม) โดยไม่มีผลกับคลาสย่อยที่ใช้เวอร์ชันเก่าขณะที่วิธีการเพิ่มอินเทอร์เฟซจะมีผลกับคลาสทั้งหมดที่ใช้งาน สมาชิกอินเตอร์เฟสที่เพิ่งเพิ่มใหม่ "
Div Tiwari

อินเทอร์เฟซสามารถมีวิธี "เริ่มต้น" เพื่อให้ไม่มีวิธีการที่ฝังอยู่ในอินเทอร์เฟซเป็นความคิดที่ผิด "ความสัมพันธ์ระหว่างผู้ปกครองกับเด็ก" คือความสัมพันธ์ที่เป็นกุญแจสำคัญที่นี่ นอกจากนี้ "แอตทริบิวต์ที่ใช้ร่วมกัน" กับ "คุณสมบัติที่ใช้ร่วมกัน" เช่น Dog iS-A Animal แต่สุนัขยังสามารถ "เดิน"
ha9u63ar

30

เมื่อใดที่ต้องทำสิ่งที่ง่ายมากถ้าคุณมีแนวคิดที่ชัดเจนในใจของคุณ

คลาสนามธรรมสามารถรับมาได้ในขณะที่อินเทอร์เฟซสามารถใช้งานได้ มีความแตกต่างระหว่างคนทั้งสอง เมื่อคุณได้รับคลาส Abstract ความสัมพันธ์ระหว่างคลาสที่ได้รับและคลาสพื้นฐานคือความสัมพันธ์ 'คือ' เช่นสุนัขเป็นสัตว์แกะเป็นสัตว์ซึ่งหมายความว่าคลาสที่ได้รับจะสืบทอดคุณสมบัติบางอย่างจากคลาสฐาน

ในขณะที่การนำอินเตอร์เฟสไปใช้งานความสัมพันธ์คือ "สามารถ" เช่นสุนัขสามารถเป็นสุนัขสายลับ สุนัขสามารถเป็นสุนัขในคณะละครสัตว์ได้ สุนัขสามารถเป็นสุนัขแข่งได้ ซึ่งหมายความว่าคุณใช้วิธีการบางอย่างเพื่อรับบางสิ่งบางอย่าง

ฉันหวังว่าฉันชัดเจน


11

1. หากคุณกำลังสร้างบางสิ่งบางอย่างที่มีฟังก์ชั่นการใช้งานทั่วไปสำหรับคลาสที่ไม่เกี่ยวข้องให้ใช้อินเทอร์เฟซ

2. ถ้าคุณกำลังสร้างบางสิ่งบางอย่างสำหรับวัตถุที่เกี่ยวข้องอย่างใกล้ชิดในลำดับชั้นใช้คลาสนามธรรม


10

ฉันเขียนบทความว่าจะใช้คลาสนามธรรมเมื่อใดและใช้อินเทอร์เฟซเมื่อใด มีความแตกต่างระหว่างพวกเขามากกว่า "one IS-A ... หนึ่ง CAN-DO ... " สำหรับฉันแล้วคำตอบเหล่านั้นเป็นกระป๋อง ฉันพูดถึงเหตุผลสองสามข้อเมื่อใช้อย่างใดอย่างหนึ่ง หวังว่ามันจะช่วย

http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/


7

ฉันคิดว่าวิธีที่กระชับที่สุดของการวางไว้คือ:

คุณสมบัติที่ใช้ร่วมกัน => คลาสนามธรรม
ฟังก์ชั่นที่ใช้ร่วมกัน => ส่วนต่อประสาน

และทำให้มันกระชับน้อยลง ...

ตัวอย่างคลาสนามธรรม:

public abstract class BaseAnimal
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }
}

public class Dog : BaseAnimal
{
    public Dog() : base(4) { }
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }
}

เนื่องจากสัตว์มีคุณสมบัติที่ใช้ร่วมกัน - จำนวนขาในกรณีนี้ - มันเหมาะสมที่จะทำให้คลาสนามธรรมที่มีคุณสมบัติที่ใช้ร่วมกันนี้ นอกจากนี้ยังช่วยให้เราสามารถเขียนรหัสทั่วไปที่ทำงานในคุณสมบัตินั้น ตัวอย่างเช่น:

public static int CountAllLegs(List<BaseAnimal> animals)
{
    int legCount = 0;
    foreach (BaseAnimal animal in animals)
    {
        legCount += animal.NumberOfLegs;
    }
    return legCount;
}

ตัวอย่างการเชื่อมต่อ:

public interface IMakeSound
{
    void MakeSound();
}

public class Car : IMakeSound
{
    public void MakeSound() => Console.WriteLine("Vroom!");
}

public class Vuvuzela : IMakeSound
{
    public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");        
}

โปรดทราบว่า Vuvuzelas และ Cars เป็นสิ่งที่แตกต่างกันโดยสิ้นเชิง แต่มีฟังก์ชั่นการใช้งานร่วมกัน: สร้างเสียง ดังนั้นอินเทอร์เฟซที่เหมาะสม นอกจากนี้ยังอนุญาตให้โปรแกรมเมอร์จัดกลุ่มสิ่งต่าง ๆ ที่ทำให้เกิดเสียงร่วมกันภายใต้อินเทอร์เฟซทั่วไป - IMakeSoundในกรณีนี้ ด้วยการออกแบบนี้คุณสามารถเขียนรหัสต่อไปนี้:

List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());

foreach (IMakeSound soundMaker in soundMakers)
{
    soundMaker.MakeSound();
}

คุณสามารถบอกได้ว่าจะเอาท์พุทอะไร?

สุดท้ายคุณสามารถรวมสองสิ่งนี้เข้าด้วยกัน

ตัวอย่างรวม:

public interface IMakeSound
{
    void MakeSound();
}

public abstract class BaseAnimal : IMakeSound
{
    public int NumberOfLegs { get; set; }

    protected BaseAnimal(int numberOfLegs)
    {
        NumberOfLegs = numberOfLegs;
    }

    public abstract void MakeSound();
}

public class Cat : BaseAnimal
{
    public Cat() : base(4) { }

    public override void MakeSound() => Console.WriteLine("Meow!");
}

public class Human : BaseAnimal 
{
    public Human() : base(2) { }

    public override void MakeSound() => Console.WriteLine("Hello, world!");
}

ที่นี่เราต้องการให้ทุกBaseAnimalคนส่งเสียง แต่เรายังไม่รู้การใช้งาน ในกรณีเช่นนี้เราสามารถสรุปการใช้อินเตอร์เฟสและมอบการใช้งานให้กับคลาสย่อย

สุดท้ายจุดหนึ่งจำได้อย่างไรในตัวอย่างคลาสนามธรรมเราสามารถทำงานกับคุณสมบัติที่ใช้ร่วมกันของวัตถุที่แตกต่างกันและในตัวอย่างอินเตอร์เฟสเราสามารถเรียกใช้ฟังก์ชั่นที่ใช้ร่วมกันของวัตถุที่แตกต่างกันได้อย่างไร ในตัวอย่างสุดท้ายนี้เราทำได้ทั้งสองอย่าง


7

เมื่อใดที่ต้องการคลาสนามธรรมผ่านอินเตอร์เฟส?

  1. ถ้ามีแผนการหนึ่งที่จะอัปเดตคลาสฐานตลอดอายุการใช้งานของโปรแกรม / โครงการดีที่สุดคืออนุญาตให้คลาสฐานเป็นคลาสนามธรรม
  2. หากมีใครพยายามสร้างกระดูกสันหลังสำหรับวัตถุที่เกี่ยวข้องอย่างใกล้ชิดในลำดับชั้นมันจะเป็นประโยชน์อย่างมากในการใช้คลาสนามธรรม

เมื่อใดที่ต้องการอินเตอร์เฟซมากกว่าคลาสนามธรรม?

  1. หากไม่มีการจัดการกับเฟรมเวิร์กประเภทลำดับชั้นขนาดใหญ่อินเทอร์เฟซจะเป็นตัวเลือกที่ดี
  2. เนื่องจากการสืบทอดหลายรายการไม่รองรับคลาสนามธรรม (ปัญหาเพชร) อินเตอร์เฟสสามารถบันทึกวันได้

อะไรทำให้คุณคิดว่าคำถามเก่าแก่ที่มีอายุเกือบสิบปีต้องการคำตอบที่ 22
jonrsharpe

3
ความคิดแบบเดียวกันทำให้ฉันค้นหาคำตอบสำหรับคำถามได้ง่าย
Satya

1
FWIW ฉันรักคำตอบนี้จริงๆ
Brent Rittenhouse

6

คลาสอาจสืบทอดมาจากคลาสฐานเดียวเท่านั้นดังนั้นหากคุณต้องการใช้คลาสนามธรรมเพื่อจัดเตรียมพหุสัณฐานให้กับกลุ่มของคลาสคลาสเหล่านั้นทั้งหมดจะต้องสืบทอดจากคลาสนั้น คลาสที่เป็นนามธรรมอาจจัดเตรียมสมาชิกที่ถูกนำไปใช้แล้ว ดังนั้นคุณสามารถมั่นใจได้ว่ามีฟังก์ชั่นที่เหมือนกันในระดับนามธรรม แต่ไม่สามารถใช้อินเทอร์เฟซได้

นี่คือคำแนะนำบางส่วนเพื่อช่วยคุณตัดสินใจว่าจะใช้ส่วนต่อประสานหรือคลาสนามธรรมเพื่อจัดเตรียมความหลากหลายให้กับองค์ประกอบของคุณ

  • หากคุณคาดว่าจะสร้างส่วนประกอบหลายเวอร์ชันให้สร้างคลาสนามธรรม คลาสนามธรรมให้วิธีที่ง่ายและสะดวกในการปรับแต่งส่วนประกอบของคุณ โดยการอัพเดตคลาสพื้นฐานคลาสที่สืบทอดทั้งหมดจะถูกอัพเดตโดยอัตโนมัติพร้อมกับการเปลี่ยนแปลง ในทางกลับกันอินเทอร์เฟซไม่สามารถเปลี่ยนแปลงได้เมื่อสร้างในลักษณะนั้น หากต้องการอินเทอร์เฟซรุ่นใหม่คุณต้องสร้างอินเทอร์เฟซใหม่ทั้งหมด
  • หากฟังก์ชันที่คุณกำลังสร้างจะมีประโยชน์สำหรับวัตถุที่แตกต่างหลากหลายให้ใช้อินเทอร์เฟซ คลาสนามธรรมควรใช้เป็นหลักสำหรับวัตถุที่เกี่ยวข้องอย่างใกล้ชิดในขณะที่อินเทอร์เฟซเหมาะสมที่สุดสำหรับการให้ฟังก์ชันการทำงานทั่วไปกับคลาสที่ไม่เกี่ยวข้อง
  • หากคุณกำลังออกแบบฟังก์ชั่นบิตขนาดเล็กและรัดกุมให้ใช้ส่วนต่อประสาน หากคุณกำลังออกแบบหน่วยการทำงานขนาดใหญ่ให้ใช้คลาสนามธรรม
  • หากคุณต้องการให้ฟังก์ชั่นทั่วไปที่ได้รับการติดตั้งใช้งานในการนำไปใช้งานทั้งหมดของส่วนประกอบให้ใช้คลาสนามธรรม คลาสนามธรรมช่วยให้คุณสามารถนำคลาสของคุณไปใช้ได้บางส่วนในขณะที่ส่วนต่อประสานไม่มีการนำไปใช้กับสมาชิก

คัดลอกมาจาก:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx


ไม่มีอะไรใน UML ที่ตัดการสืบทอดคลาสหลายคลาส การสืบทอดหลายครั้งถูกกำหนดโดยภาษาการเขียนโปรแกรมไม่ใช่โดย UML ตัวอย่างเช่นไม่อนุญาตให้มีการสืบทอดหลายคลาสใน Java และ C # แต่อนุญาตใน C ++
BobRodes

@BobRodes: มีคุณสมบัติหลายอย่างที่เฟรมเวิร์กเชิงวัตถุสามารถจัดเตรียมไว้ในชุดค่าผสมต่าง ๆ ได้ แต่ไม่ใช่ในชุดค่าผสมทั้งหมด การสืบทอดหลายแบบทั่วไปทำให้ไม่สามารถรวมคุณสมบัติที่มีประโยชน์อื่น ๆ รวมถึงความสามารถในการส่งการอ้างอิงโดยตรงไปยังชนิดพาเรนต์ของอินสแตนซ์จริงหรือชนิดอินเตอร์เฟสใด ๆ ที่สนับสนุนดังนั้นและความสามารถในการรวบรวมประเภทฐานและประเภท
supercat

@supercat Yours เป็นคำอธิบายที่ดีของปัญหาที่เกิดจากการใช้การสืบทอดหลายแบบ อย่างไรก็ตามไม่มีอะไรใน UML ที่ตัดการสืบทอดคลาสหลายคลาสในไดอะแกรม ฉันตอบกลับไปที่ "ชั้นเรียนอาจสืบทอดมาจากชั้นฐานเดียวเท่านั้น ... " ซึ่งไม่ค่อยเป็นเช่นนั้น
BobRodes

@BobRodes: คำถามถูกติดแท็ก Java Java มีคุณสมบัติที่ระบุและ จำกัด อยู่ในรูปแบบของการสืบทอดหลายอย่างที่ไม่สามารถสร้าง "มรณะไดมอนด์" ได้ (แม้ว่าอันที่จริงแล้ววิธีที่พวกเขาใช้งานอินเทอร์เฟซเริ่มต้นจะทำให้เพชรมฤตยูเป็นไปได้)
supercat

@supercat โอ้ตกลง ฉันมักจะไม่ดูแท็ก java ดังนั้นในเวลาที่ฉันเขียนอย่างน้อยฉันก็คิดว่าฉันแสดงความคิดเห็นในคำตอบ UML ในกรณีใด ๆ ฉันเห็นด้วยกับความคิดเห็นของคุณ
BobRodes

3

ลองใช้คลาสนามธรรมถ้าข้อความใด ๆ เหล่านี้ตรงกับสถานการณ์ของคุณ:

  1. คุณต้องการแบ่งปันรหัสระหว่างคลาสที่เกี่ยวข้องอย่างใกล้ชิดหลายชั้น
  2. คุณคาดหวังว่าคลาสที่ขยายคลาส abstract ของคุณมีวิธีหรือฟิลด์ทั่วไปจำนวนมากหรือต้องการตัวดัดแปลงการเข้าถึงอื่นที่ไม่ใช่สาธารณะ (เช่นได้รับการป้องกันและเป็นส่วนตัว)
  3. คุณต้องการประกาศเขตข้อมูลไม่คงที่หรือไม่ใช่เขตข้อมูลสุดท้าย สิ่งนี้ช่วยให้คุณสามารถกำหนดวิธีการที่สามารถเข้าถึงและแก้ไขสถานะของวัตถุที่พวกเขาอยู่

พิจารณาการใช้อินเทอร์เฟซหากข้อความใด ๆ เหล่านี้นำไปใช้กับสถานการณ์ของคุณ:

  1. คุณคาดหวังว่าคลาสที่ไม่เกี่ยวข้องจะใช้อินเทอร์เฟซของคุณ ตัวอย่างเช่นอินเทอร์เฟซที่เปรียบเทียบได้และ Cloneable ถูกใช้งานโดยคลาสที่ไม่เกี่ยวข้องจำนวนมาก
  2. คุณต้องการระบุลักษณะการทำงานของชนิดข้อมูลเฉพาะ แต่ไม่ต้องกังวลว่าใครจะใช้พฤติกรรมของมัน
  3. คุณต้องการใช้ประโยชน์จากการสืบทอดหลายอย่าง

แหล่ง


2

คำตอบนั้นแตกต่างกันไปในแต่ละภาษา ตัวอย่างเช่นใน Java คลาสสามารถใช้ (สืบทอดจาก) หลายอินเตอร์เฟส แต่สืบทอดจากคลาสนามธรรมเพียงคลาสเดียวเท่านั้น ดังนั้นอินเตอร์เฟสจึงให้ความยืดหยุ่นมากกว่า แต่นี่ไม่เป็นความจริงใน C ++


2

สำหรับฉันฉันจะไปกับอินเตอร์เฟสในหลายกรณี แต่ฉันชอบคลาสนามธรรมในบางกรณี

คลาสใน OO generaly หมายถึงการใช้งาน ฉันใช้คลาสนามธรรมเมื่อฉันต้องการบังคับรายละเอียดการใช้งานบางอย่างให้กับเด็กที่ฉันไปด้วยอินเตอร์เฟส

แน่นอนคลาสนามธรรมมีประโยชน์ไม่เพียง แต่ในการบังคับใช้ แต่ยังใช้ในการแบ่งปันรายละเอียดเฉพาะบางอย่างในคลาสที่เกี่ยวข้องมากมาย


1

ใช้คลาสนามธรรมหากคุณต้องการให้มีการใช้งานพื้นฐานบางอย่าง


1
ขอบคุณเซบาสเตียน แต่ถ้าฉันไม่จำเป็นต้องมีการใช้งานขั้นพื้นฐานล่ะ คลาสนามธรรมและอินเทอร์เฟซจะเหมือนกันถ้าเป็นความแตกต่างระหว่างพวกมันเท่านั้น? ทำไมถึงมีความแตกต่าง
Chirantan

1
เนื่องจากบางภาษาไม่มีอินเตอร์เฟส - C ++
jmucchiello

1

ในจาวาคุณสามารถสืบทอดจากคลาสหนึ่ง (นามธรรม) เพื่อ "ให้" ฟังก์ชั่นและคุณสามารถใช้อินเทอร์เฟซมากมายเพื่อ "ฟังก์ชั่น" มั่นใจ


คำใบ้ Lil': ถ้าคุณต้องการที่จะได้รับมรดกจากระดับนามธรรมและอินเตอร์เฟซให้แน่ใจว่าการดำเนินการที่เป็นนามธรรมระดับอินเตอร์เฟซ
แอนเดรี Niedermair

1

หมดจดบนพื้นฐานของมรดกคุณจะใช้บทคัดย่อที่คุณกำหนดได้อย่างชัดเจนลูกหลาน, ความสัมพันธ์ที่เป็นนามธรรม (เช่นสัตว์> แมว) และ / หรือจำเป็นต้องมีการถ่ายทอดทางพันธุกรรมของคุณสมบัติเสมือนหรือไม่ใช่แบบสาธารณะรัฐที่ใช้ร่วมกันโดยเฉพาะอย่างยิ่ง (ซึ่งเชื่อมต่อไม่สามารถสนับสนุน )

คุณควรลองและสนับสนุนการจัดองค์ประกอบ (ผ่านการฉีดขึ้นต่อกัน) ผ่านการสืบทอดที่คุณสามารถทำได้และโปรดทราบว่าการเชื่อมต่อของสัญญาสนับสนุนการทดสอบหน่วยการแยกข้อกังวลและ (การแปรเปลี่ยนภาษา) หลายมรดกในลักษณะที่บทคัดย่อไม่สามารถ


1

สถานที่ที่น่าสนใจแห่งหนึ่งที่อินเตอร์เฟซดีกว่าคลาสนามธรรมคือเมื่อคุณต้องการเพิ่มฟังก์ชันการทำงานพิเศษให้กับกลุ่มของวัตถุ (ที่เกี่ยวข้องหรือไม่เกี่ยวข้อง) หากคุณไม่สามารถให้คลาสนามธรรมพื้นฐานแก่พวกเขา (เช่นพวกเขามีsealedหรือมีพาเรนต์แล้ว) คุณสามารถให้อินเทอร์เฟซดัมมี (ว่าง) แทนพวกเขาได้และจากนั้นก็เขียนวิธีการขยายสำหรับอินเทอร์เฟซนั้น


0

นี่อาจเป็นการยากที่จะโทร ...

ตัวชี้หนึ่งที่ฉันสามารถให้: วัตถุสามารถใช้อินเทอร์เฟซจำนวนมากในขณะที่วัตถุสามารถสืบทอดคลาสฐานเดียวเท่านั้น (ในภาษา OO ที่ทันสมัยเช่น c # ฉันรู้ว่า C ++ มีหลายมรดก - แต่ไม่ขมวดคิ้ว?)


การสืบทอดหลายอย่างทำให้ Mixin สามารถนำไปใช้งานได้ดูเหมือนว่า Mixin นั้นเขียนได้ง่าย แต่ก็ยากที่จะเขียนและเขียนโดยไม่สั้น Mixin นั้นยอดเยี่ยมโดยรวมแม้ว่า IMO
Martijn Laarman

อันที่จริงฉันไม่ได้รับมรดกหลายอย่างแน่นอนเป็นที่ถกเถียงกันมากขึ้นในหมู่พวกเราฉันไม่เห็นเหตุผลที่จะลงคะแนน ในความเป็นจริงฉัน upvote คำตอบของคุณ
Martijn Laarman

จุดเดียวที่ฉันพยายามทำคือวิธีการ Mixin ในภาษาที่มีการสืบทอดแบบเดี่ยวก็มีความเป็นไปได้เช่นกัน (C #, PHP, javascript) แต่มีพฤติกรรมแฮ็ครางหรือไวยากรณ์ที่ไม่มีรสนิยมที่ดี ฉันรัก Mixin เมื่อพวกเขาทำงาน แต่ฉันก็ยังไม่แน่ใจในสิ่งที่จะทำต่อจากการสืบทอดหลายครั้งหรือไม่
Martijn Laarman

คำตอบนี้มีความแตกต่างด้านโครงสร้างมากกว่าความแตกต่างด้านการออกแบบ ฉันคิดว่าเขากำลังขอความแตกต่างในการออกแบบ
ปราโมทย์ Setlur

0

คลาสนามธรรมสามารถมีการนำไปใช้งานได้

อินเทอร์เฟซไม่มีการใช้งานมันเพียงกำหนดชนิดของสัญญา

อาจมีความแตกต่างขึ้นอยู่กับภาษาด้วยเช่นกันตัวอย่างเช่น C # ไม่มีการสืบทอดหลายอย่าง แต่หลายอินเตอร์เฟสสามารถนำไปใช้ในคลาสได้


เมื่อคุณพูดว่า "สัญญาประเภท" คุณหมายถึงชอบในบริการเว็บหรือไม่?
Chirantan

ในทางเทคนิคแล้วเว็บเซอร์วิสไม่สามารถใช้งานได้กับอินเตอร์เฟส ด้วยสัญญาฉันหมายถึงผู้ใช้ของวัตถุรู้วิธีการที่มีอยู่บนวัตถุนั้น ตัวอย่างเช่นอินเทอร์เฟซ IMouse จะมีวิธีการย้ายและเหตุการณ์ปุ่มเมาส์ซ้ายและขวา
Gerrie Schenck

-1

กฎหัวแม่มือพื้นฐานคือ: สำหรับ"คำนาม" ให้ใช้คลาสนามธรรมและสำหรับอินเตอร์เฟซการใช้ "คำกริยา"

เช่น: carเป็นคลาสนามธรรมและdriveเราสามารถทำให้เป็นอินเทอร์เฟซ


5
มันไม่สมเหตุสมผลเลยเราสามารถใส่ฟังก์ชั่นการใช้งานdriveในรถยนต์ - นั่นคือคลาสนามธรรม
Arslan Ali
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.