ใช้คลาสนามธรรมใน C # เป็นคำจำกัดความ


21

ในฐานะนักพัฒนา C ++ ฉันค่อนข้างคุ้นเคยกับไฟล์ส่วนหัว C ++ และพบว่ามีประโยชน์ในการมี "เอกสาร" ที่ถูกบังคับภายในโค้ด ฉันมักจะมีช่วงเวลาที่ไม่ดีเมื่อฉันต้องอ่านรหัส C # เพราะ: ฉันไม่มีแผนที่ทางจิตของชั้นเรียนที่ฉันทำงานด้วย

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

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


13
C # เป็นภาษาการเขียนโปรแกรมที่แตกต่างอย่างสิ้นเชิงกว่า C ++ ไวยากรณ์บางอย่างมีลักษณะคล้ายกัน แต่คุณต้องเข้าใจ C # เพื่อให้ทำงานได้ดี และคุณควรทำอะไรบางอย่างเกี่ยวกับนิสัยที่ไม่ดีที่คุณมีโดยใช้ไฟล์ส่วนหัว ฉันทำงานกับ C ++ มานานหลายทศวรรษฉันไม่เคยอ่านไฟล์ส่วนหัวเพียงเขียนมันเท่านั้น
Bent

17
หนึ่งในสิ่งที่ดีที่สุดของ C # และ Java คืออิสระจากไฟล์ส่วนหัว! เพียงใช้ IDE ที่ดี
Erik Eidt

9
การประกาศในไฟล์ส่วนหัว C ++ ไม่ได้จบลงด้วยการเป็นส่วนหนึ่งของไบนารีที่สร้างขึ้น พวกเขามีไว้สำหรับคอมไพเลอร์และลิงเกอร์ คลาส C # นามธรรมเหล่านี้จะเป็นส่วนหนึ่งของรหัสที่สร้างขึ้นเพื่อประโยชน์ใด ๆ
Mark Benningfield

16
โครงสร้างที่เทียบเท่าใน C # เป็นอินเทอร์เฟซไม่ใช่คลาสนามธรรม
Robert Harvey

6
@DaniBarcaCasafont "ฉันเห็นส่วนหัวเป็นวิธีที่รวดเร็วและเชื่อถือได้เพื่อดูว่าเมธอดและแอตทริบิวต์ของคลาสคืออะไรพารามิเตอร์ชนิดใดที่คาดหวังและสิ่งที่มันส่งคืน" เมื่อใช้ Visual Studio ทางเลือกของฉันคือ Ctrl-MO ทางลัด
wha7ever - Reinstate Monica

คำตอบ:


44

เหตุผลที่ทำใน C ++ นั้นเกี่ยวข้องกับการทำให้คอมไพเลอร์เร็วขึ้นและง่ายต่อการนำไปใช้ นี่ไม่ใช่การออกแบบเพื่อให้การเขียนโปรแกรมง่ายขึ้น

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

ไม่แนะนำให้พยายามทำซ้ำข้อ จำกัด ฮาร์ดแวร์เก่าในสภาพแวดล้อมการพัฒนาที่ทันสมัย!

การกำหนดอินเทอร์เฟซหรือคลาสนามธรรมสำหรับทุกชั้นจะลดประสิทธิภาพการทำงานของคุณ อะไรที่คุณจะได้ทำกับเวลานั้น ? นอกจากนี้ผู้พัฒนารายอื่นจะไม่ปฏิบัติตามอนุสัญญานี้

ในความเป็นจริงผู้พัฒนารายอื่นอาจลบคลาสนามธรรมของคุณ หากฉันพบอินเทอร์เฟซในรหัสที่ตรงกับเกณฑ์ทั้งสองนี้ฉันจะลบออกและปรับโครงสร้างใหม่จากโค้ด:1. Does not conform to the interface segregation principle 2. Only has one class that inherits from it

อีกอย่างคือมีเครื่องมือที่มาพร้อมกับ Visual Studio ที่ทำในสิ่งที่คุณตั้งใจจะทำให้สำเร็จโดยอัตโนมัติ:

  1. Class View
  2. Object Browser
  3. ในSolution Explorerคุณสามารถคลิกที่รูปสามเหลี่ยมเพื่อใช้จ่ายเรียนเพื่อดูฟังก์ชั่นพารามิเตอร์และประเภทผลตอบแทน
  4. มีเมนูแบบเลื่อนลงสามเมนูด้านล่างแท็บไฟล์หนึ่งรายการที่เหมาะสมที่สุดจะแสดงสมาชิกทั้งหมดของคลาสปัจจุบัน

ลองหนึ่งวิธีข้างต้นก่อนที่จะอุทิศเวลาในการเรพลิเคตไฟล์ส่วนหัว C ++ ใน C #


นอกจากนี้ยังมีเหตุผลทางเทคนิคที่จะไม่ทำเช่นนี้ ... มันจะทำให้ไบนารีสุดท้ายของคุณใหญ่กว่าที่ควรจะเป็น ฉันจะทำซ้ำความคิดเห็นนี้โดย Mark Benningfield:

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


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


2
นอกจากนี้คุณยังต้องจบด้วยการโทรเสมือนจริงที่ไม่จำเป็น (ไม่ดีถ้ารหัสของคุณไวต่อประสิทธิภาพ)
Lucas Trzesniewski


2
บางคนอาจจะยุบทั้งคลาส (คลิกขวา -> การสรุป -> ยุบไปที่คำจำกัดความ) เพื่อรับมุมมองจากมุมสูง
jpmc26

"คุณจะทำอะไรได้อีกในเวลานั้น" -> อืมคัดลอกและวางและทำสิ่งเล็ก ๆ น้อย ๆ ใช้เวลาประมาณ 3 วินาทีถ้าหากทั้งหมด แน่นอนว่าฉันอาจใช้เวลานั้นในการหยิบทิปกรองเพื่อเตรียมการรีดบุหรี่แทน ไม่ใช่จุดที่เกี่ยวข้องจริงๆ [ข้อจำกัดความรับผิดชอบ: ฉันชอบทั้งสำนวน C # และสำนวน C ++ และภาษาอื่น ๆ อีกมากมาย]
phresnel

1
@phresnel: ฉันต้องการเห็นคุณลองและทำซ้ำคำจำกัดความของคลาสและสืบทอดคลาสดั้งเดิมจากนามธรรมหนึ่งใน 3s ยืนยันโดยไม่มีข้อผิดพลาดสำหรับคลาสใดที่มีขนาดใหญ่พอที่จะมองไม่เห็นนกง่าย ๆ มัน (เนื่องจากเป็นกรณีการใช้งานที่เสนอของ OP: คลาสที่ไม่ซับซ้อนอย่างอื่นควรจะซับซ้อน) เกือบจะโดยเนื้อแท้แล้วความซับซ้อนของคลาสดั้งเดิมหมายความว่าคุณไม่สามารถเขียนนิยามคลาสนามธรรมด้วยใจ (เพราะนั่นหมายความว่าคุณรู้แล้วด้วยหัวใจ) จากนั้นพิจารณาเวลาที่ต้องใช้ในการทำโค้ดเบสทั้งหมด
Flater

14

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

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

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

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

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

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

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

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

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

ดังนั้นโปรดอย่าเขียน abstractions เพียงเพื่อช่วยให้เราอ่านรหัส เรามีเครื่องมือสำหรับสิ่งนั้น ใช้ abstractions เพื่อแยกสิ่งที่ต้องการแยกออก


5

ใช่มันจะแย่มากเพราะ (1) มันแนะนำโค้ดที่ไม่จำเป็น (2) มันจะทำให้ผู้อ่านสับสน

หากคุณต้องการโปรแกรมใน C # คุณควรจะคุ้นเคยกับการอ่าน C # รหัสที่เขียนโดยคนอื่นจะไม่เป็นไปตามรูปแบบนี้อยู่ดี


1

การทดสอบหน่วย

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

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

ตัวอย่าง: ที่ที่คุณอาจเขียนไฟล์ส่วนหัว myclass.h เช่นนี้:

class MyClass
{
public:
  void foo();
};

แต่ใน c # เขียนการทดสอบแบบนี้:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

การทดสอบที่ง่ายมากนี้สื่อถึงข้อมูลเดียวกันกับไฟล์ส่วนหัว (เราควรจะมีคลาสชื่อ "MyClass" ที่มีฟังก์ชั่นแบบไม่มีพารามิเตอร์ "Foo") ในขณะที่ไฟล์ส่วนหัวมีขนาดกะทัดรัดมากขึ้นการทดสอบจะมีข้อมูลมากขึ้น

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


คุณจะทำการทดสอบหน่วย (การทดสอบแบบแยก) ได้อย่างไร = จำเป็นต้องใช้ Mocks โดยไม่มีส่วนต่อประสาน? เฟรมเวิร์กใดที่สนับสนุนการเยาะเย้ยจากการใช้งานจริงส่วนใหญ่ที่ฉันเคยเห็นใช้อินเทอร์เฟซเพื่อสลับการปรับใช้กับการใช้งานจำลอง
Bulan

ฉันไม่คิดว่า mocks เป็นสิ่งจำเป็นสำหรับวัตถุประสงค์ของ OP: จำไว้ว่านี่ไม่ใช่การทดสอบหน่วยเป็นเครื่องมือสำหรับ TDD แต่เป็นการทดสอบหน่วยแทนไฟล์ส่วนหัว (แน่นอนว่าพวกเขาอาจพัฒนาไปสู่การทดสอบหน่วย "ปกติ" กับ mocks ฯลฯ )
Guran

ตกลงถ้าฉันเพียงพิจารณาด้านของ OPs ฉันเข้าใจดีขึ้น อ่านคำตอบของคุณเป็นคำตอบทั่วไป ขอบคุณสำหรับการชี้แจง!
Bulan

@Bulan ความจำเป็นในการเยาะเย้ยอย่างกว้างขวางมักจะบ่งบอกถึงการออกแบบที่ไม่ดี
TheCatWhisperer

@TheCatWhisperer ใช่ฉันรู้ดีว่าไม่มีข้อโต้แย้งที่นั่นไม่สามารถเห็นได้ว่าฉันทำคำสั่งใดก็ได้: DI กำลังพูดถึงการทดสอบโดยทั่วไปว่า Mock-framework ทั้งหมดที่ฉันใช้นั้นใช้การแลกเปลี่ยน การใช้งานจริงและถ้ามีเทคนิคอื่น ๆ เกี่ยวกับวิธีการที่คุณจะล้อเลียนถ้าคุณไม่มีอินเทอร์เฟซ
Bulan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.