เมื่อใดจึงควรใช้คลาสนามธรรมแทนอินเทอร์เฟซที่มีวิธีการขยายใน C #


70

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

ตัวอย่างที่น่าสังเกตของโมเดลวิธีส่วนต่อประสาน + ส่วนต่อขยายคือ LINQ โดยที่ฟังก์ชันการสืบค้นมีให้สำหรับประเภทใดก็ตามที่ใช้IEnumerableวิธีการขยายจำนวนมาก


2
และเราสามารถใช้ 'คุณสมบัติ' ในอินเทอร์เฟซได้เช่นกัน
Gulshan

1
ดูเหมือน C # เฉพาะแทนที่จะเป็นคำถาม OOD ทั่วไป (ภาษา OO อื่น ๆ ส่วนใหญ่ไม่มีวิธีการขยายและคุณสมบัติ)
PéterTörök


4
วิธีการขยายไม่ได้แก้ปัญหาที่เรียนนามธรรมแก้ดังนั้นเหตุผลไม่แตกต่างจากในภาษาจาวาหรือภาษาที่สืบทอดเดียว
งาน

3
ความจำเป็นในการใช้งานวิธีการเรียนนามธรรมไม่ได้ลดลงโดยวิธีการขยาย วิธีการขยายไม่สามารถเข้าถึงเขตข้อมูลสมาชิกส่วนตัวและไม่สามารถประกาศเสมือนและแทนที่ในชั้นเรียนที่ได้รับ
zooone9243

คำตอบ:


63

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

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

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

วิธีการขยายไม่จำเป็นต้องเป็นไปตาม "ต้องเป็นส่วนหนึ่งของคลาส" ของสมการ นอกจากนี้วิธีการส่วนขยาย iirc ไม่สามารถ (ดูเหมือนจะ) เป็นอะไรก็ได้ยกเว้นอยู่ในขอบเขต

แก้ไข

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

โดยส่วนตัวแล้วฉันมีปัญหาในการมองเห็นประโยชน์ของการปรับเปลี่ยนแนวทางปฏิบัติทั่วไปในปัจจุบัน แต่ก็เห็นว่ามีข้อเสียเล็กน้อยในการทำเช่นนั้น

ควรสังเกตว่าเป็นไปได้ที่จะตั้งโปรแกรมด้วยวิธีนี้ในหลาย ๆ ภาษาผ่านทางคลาสยูทิลิตี้ ส่วนขยายเพียงแค่เตรียมน้ำตาลประโยคเพื่อให้วิธีการดูเหมือนว่าอยู่ในชั้นเรียน


เอาชนะฉันไปเลย ..
Giorgi

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

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

นอกจากนี้ยังอาจมีข้อเสียกับรูปแบบการขยายอินเตอร์เฟส + ยกตัวอย่างเช่นใช้ห้องสมุด Linq มันยอดเยี่ยม แต่ถ้าคุณต้องการใช้เพียงครั้งเดียวในไฟล์โค้ดนั้นไลบรารี Linq แบบเต็มจะพร้อมใช้งานใน IntelliSense บนทุก ๆ IEnumerable ในไฟล์โค้ดของคุณ สิ่งนี้สามารถทำให้ประสิทธิภาพของ IDE ช้าลงอย่างมาก
KeithS

-1 เนื่องจากคุณยังไม่ได้สาธิตวิธีที่คลาสนามธรรมเหมาะสมกว่าสำหรับวิธีแม่แบบมากกว่าอินเทอร์เฟซ
Benjamin Hodgson

25

ทุกอย่างเกี่ยวกับสิ่งที่คุณต้องการทำโมเดล:

คลาสนามธรรมทำงานโดยการรับมรดก เป็นเพียงคลาสพื้นฐานพิเศษพวกมันจำลองความสัมพันธ์แบบ

ตัวอย่างเช่นสุนัขเป็นสัตว์ดังนั้นเราจึงมี

class Dog : Animal { ... }

เนื่องจากมันไม่สมเหตุสมผลเลยที่จะสร้างสัตว์ป่าทั่วไป (มันจะมีหน้าตาเป็นอย่างไร) เราสร้างAnimalคลาสพื้นฐานนี้abstract- แต่มันก็ยังเป็นคลาสพื้นฐาน และชั้นก็ไม่ได้ทำให้ความรู้สึกโดยไม่ต้องเป็นDogAnimal

การเชื่อมต่อในทางกลับกันเป็นเรื่องราวที่แตกต่างกัน พวกเขาไม่ได้ใช้การสืบทอด แต่ให้ความหลากหลาย (ซึ่งสามารถนำไปใช้กับการสืบทอดได้เช่นกัน) พวกเขาไม่ได้จำลองความสัมพันธ์แบบis-aแต่มันสนับสนุนมากกว่า

ใช้เช่นIComparable- วัตถุที่สนับสนุนการถูกเมื่อเทียบกับอีกคนหนึ่ง

ฟังก์ชันการทำงานของคลาสไม่ขึ้นอยู่กับอินเทอร์เฟซที่ใช้อินเทอร์เฟซเพียงให้วิธีทั่วไปในการเข้าถึงฟังก์ชันการทำงาน เรายังคงสามารถDisposeของGraphicsหรือได้โดยไม่ต้องใช้พวกเขาFileStream อินเตอร์เฟซที่เกี่ยวข้องเพียงวิธีด้วยกันIDisposable

โดยหลักการแล้วเราสามารถเพิ่มและลบส่วนต่อประสานเหมือนกับส่วนขยายได้โดยไม่ต้องเปลี่ยนพฤติกรรมของคลาสเพียงเพิ่มการเข้าถึง คุณไม่สามารถที่จะแย่งคลาสพื้นฐานออกไปได้เพราะวัตถุจะไร้ความหมาย!


เมื่อใดที่คุณจะเลือกให้นามธรรมระดับพื้นฐาน
Gulshan

1
@Gulshan: ถ้ามัน - ด้วยเหตุผลบางอย่าง - ไม่สมเหตุสมผลที่จะสร้างอินสแตนซ์ของคลาสพื้นฐาน คุณสามารถ "สร้าง" ชิวาวาหรือฉลามขาว แต่คุณไม่สามารถสร้างสัตว์โดยไม่มีความเชี่ยวชาญเพิ่มเติม - ดังนั้นคุณจะทำให้Animalนามธรรม สิ่งนี้ช่วยให้คุณสามารถเขียนabstractฟังก์ชั่นที่มีความเชี่ยวชาญโดยคลาสที่ได้รับ หรือมากกว่าในแง่การเขียนโปรแกรม - มันอาจจะไม่สมเหตุสมผลที่จะสร้าง a UserControlแต่Button ก็เป็น a UserControlดังนั้นเราจึงมีความสัมพันธ์ class / baseclass แบบเดียวกัน
Dario

หากคุณมีวิธีนามธรรมมากกว่าที่คุณมีระดับนามธรรม และวิธีการเชิงนามธรรมที่คุณมีเมื่อไม่มีค่าที่จะนำมาใช้ (เช่น "go" สำหรับสัตว์) และแน่นอนว่าบางครั้งคุณไม่ต้องการอนุญาตให้มีวัตถุของคลาสทั่วไปมากกว่าที่คุณจะทำให้มันเป็นนามธรรม
Dainius

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

3

ฉันเพิ่งรู้ว่าฉันไม่ได้ใช้คลาสนามธรรม (อย่างน้อยไม่ได้สร้าง) ในปีที่ผ่านมา

คลาสนามธรรมเป็นสัตว์ที่ค่อนข้างแปลกระหว่างส่วนต่อประสานและส่วนนำไปใช้ มีบางสิ่งในคลาสนามธรรมที่เสแสร้งความรู้สึกแมงมุมของฉัน ฉันจะโต้แย้งไม่ควร "เปิดเผย" การใช้งานที่อยู่เบื้องหลังส่วนต่อประสานในทางใดทางหนึ่ง (หรือทำการผูกใด ๆ )

แม้ว่าจะมีความต้องการพฤติกรรมร่วมกันระหว่างคลาสที่ใช้อินเทอร์เฟซ แต่ฉันจะใช้คลาสผู้ช่วยอิสระมากกว่าคลาสนามธรรม

ฉันจะไปหาอินเทอร์เฟซ


2
-1: ฉันไม่แน่ใจว่าคุณอายุเท่าไร แต่คุณอาจต้องการเรียนรู้รูปแบบการออกแบบโดยเฉพาะรูปแบบวิธีการของแม่แบบ รูปแบบการออกแบบจะทำให้คุณเป็นโปรแกรมเมอร์ที่ดีขึ้นและพวกเขาจะยุติความเขลาของคลาสนามธรรม
Jim G.

เช่นเดียวกับความรู้สึกเสียวซ่า คลาสซึ่งเป็นคุณสมบัติแรกและสำคัญที่สุดของ C # และ Java เป็นคุณสมบัติในการสร้างประเภทและเชื่อมโยงมันไว้กับการใช้งานเพียงครั้งเดียวทันที
Jesse Millikan

2

IMHO ฉันคิดว่ามีการผสมผสานของแนวคิดที่นี่

คลาสใช้การสืบทอดบางชนิดในขณะที่อินเตอร์เฟสใช้การสืบทอดชนิดอื่น

การสืบทอดทั้งสองแบบมีความสำคัญและมีประโยชน์และแต่ละแบบก็มีข้อดีและข้อเสีย

เริ่มกันที่จุดเริ่มต้นกันดีกว่า

เรื่องราวพร้อมอินเทอร์เฟซเริ่มต้นได้นานในขณะที่กลับด้วย C ++ ในช่วงกลางทศวรรษ 1980 เมื่อ C ++ ได้รับการพัฒนาความคิดของอินเทอร์เฟซที่เป็นประเภทที่แตกต่างกันก็ยังไม่เกิดขึ้นจริง (มันจะมาในเวลา)

C ++ มีการสืบทอดประเภทเดียวเท่านั้น, ชนิดของการสืบทอดที่ใช้โดยคลาส, เรียกว่าการสืบทอดการใช้งาน

เพื่อให้สามารถใช้คำแนะนำ GoF Book: "เพื่อโปรแกรมสำหรับอินเทอร์เฟซและไม่ใช่สำหรับการใช้งาน", C ++ รองรับคลาสนามธรรมและการสืบทอดการใช้งานหลายครั้งเพื่อให้สามารถทำสิ่งที่อินเตอร์เฟสใน C # มีความสามารถ (และมีประโยชน์!) .

C # แนะนำชนิดใหม่, ส่วนต่อประสาน, และรูปแบบใหม่ของการสืบทอด, การสืบทอดส่วนต่อประสาน, ให้อย่างน้อยสองวิธีในการสนับสนุน "การตั้งโปรแกรมสำหรับส่วนต่อประสาน, และไม่ใช่สำหรับการนำไปใช้", ด้วยข้อแม้สำคัญอย่างหนึ่ง: C # เท่านั้น รองรับการสืบทอดหลายรายการพร้อมกับการสืบทอดอินเตอร์เฟส (ไม่ใช่การสืบทอดการใช้งาน)

ดังนั้นเหตุผลทางสถาปัตยกรรมที่อยู่เบื้องหลังอินเตอร์เฟสใน C # คือข้อเสนอแนะของ GoF

ฉันหวังว่านี่จะเป็นประโยชน์

ขอแสดงความนับถือGastón


1

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

สำหรับการออกแบบใหม่ฉันชอบที่จะใช้วิธีการขยายบนอินเตอร์เฟส สำหรับลำดับชั้นของประเภทวิธีการขยายให้วิธีการขยายลักษณะการทำงานโดยไม่ต้องเปิดลำดับชั้นทั้งหมดสำหรับการปรับเปลี่ยน

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


ไม่ไม่ใช่วิธีที่ปลอดภัยเท่านั้น มีอีกวิธีที่ปลอดภัยกว่า นั่นคือวิธีการขยาย ลูกค้าจะไม่ติดเชื้อเลย นั่นเป็นเหตุผลที่ฉันได้กล่าวถึงวิธีการเชื่อมต่อ + นามสกุล
Gulshan

@Ed James ฉันคิดว่า "มีประสิทธิภาพน้อยลง" = "มีความยืดหยุ่นมากขึ้น" เมื่อใดที่คุณจะเลือกคลาสนามธรรมที่มีประสิทธิภาพยิ่งขึ้นแทนที่จะใช้ส่วนต่อประสานที่ยืดหยุ่นมากขึ้น
Gulshan

@Ed James ในวิธีการขยาย asp.mvc ถูกนำมาใช้ตั้งแต่เริ่มต้น คุณคิดยังไงเกี่ยวกับที่?
Gulshan

0

ฉันคิดว่าใน C # หลายมรดกไม่ได้รับการสนับสนุนและนั่นเป็นสาเหตุที่นำเสนอแนวคิดของส่วนต่อประสานใน C #

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


จะแม่นยำก็บริสุทธิ์คลาสนามธรรมที่เรียกว่า "อินเตอร์เฟซ" ใน C #
Johan Boulé

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