วิธีการสาธารณะทั้งหมดในคลาสนามธรรมควรถูกทำเครื่องหมายเสมือนหรือไม่?


12

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

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

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


สิ่งนี้อาจช่วยได้: stackoverflow.com/questions/391483/…
NoChance

1
บางทีคุณควรดูว่าทำไมใน C # ค่าเริ่มต้นไม่ใช่เสมือน แต่ใน Java เป็น เหตุผลที่อาจให้ข้อมูลเชิงลึกเกี่ยวกับคำตอบ
Daniel Little

คำตอบ:


9

อย่างไรก็ตามตราบใดที่คุณเชื่อมั่นว่าหลักการของการทดแทนของ Liskov จะเป็นไปตามนั้นทำไมคุณถึงไม่ยอมให้มันล้มล้าง?

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

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

ปรับปรุง

ตัวอย่างที่เป็นรูปธรรมของโครงการที่ฉันได้ทำ:

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

ในสองกรณีแรกการใช้งานแบบดั้งเดิมใช้กลยุทธ์ทำให้เกิดโค้ดซ้ำจำนวนมากซึ่งแน่นอนว่าในช่วงหลายปีที่ผ่านมามีความแตกต่างเล็กน้อยที่นี่และที่นั่นมีข้อผิดพลาดซ้ำหรือแตกต่างกันเล็กน้อยและยากมากที่จะรักษา การเปลี่ยนเป็นวิธีแม่แบบ (และการปรับปรุงอื่น ๆ เช่นการใช้คำอธิบายประกอบ Java) ลดขนาดรหัสลงประมาณ 40-70%

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


เพียงแค่อ้างข้อความ GoF ไม่ใช่คำตอบ คุณต้องให้เหตุผลที่แท้จริงในการทำสิ่งนั้น
DeadMG

@DeadMG ฉันใช้เทมเพลตเป็นประจำในระหว่างการทำงานดังนั้นฉันคิดว่ามันเห็นได้ชัดว่ามันเป็นรูปแบบที่ใช้งานได้จริงและมีประโยชน์มาก (เนื่องจากรูปแบบของ GoF ส่วนใหญ่คือ ... แบบฝึกหัดทางวิชาการเชิงทฤษฎีไม่ได้รวบรวมไว้ จากประสบการณ์จริง) แต่เห็นได้ชัดว่าทุกคนไม่เห็นด้วยกับมัน ... ดังนั้นฉันจึงเพิ่มตัวอย่างที่เป็นรูปธรรมลงในคำตอบของฉัน
PéterTörök

2

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

ตัวอย่างเช่นคุณอาจต้องการใช้สำนวน 'Non virtual polymorphism' โดยที่ฟังก์ชันนั้นเรียกว่า polymorphically จากฟังก์ชันที่ไม่ใช่สมาชิกเสมือนเพื่อให้แน่ใจว่ามีเงื่อนไขหรือ postconditions ที่แน่นอนก่อนที่จะเรียกใช้ฟังก์ชันเสมือนจริง

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};

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

2

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


1

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

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


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

3
ก่อนอื่นฉันจะไปที่ C # q ของคุณ วิธีนามธรรมใน C # เป็นเสมือนโดยปริยายและไม่สามารถใช้งานได้ สำหรับจุดแรกของคุณคลาสนามธรรมใน C # สามารถและควรมีการดำเนินการบางอย่าง (มิฉะนั้นคุณควรใช้อินเทอร์เฟซ) จุดของคลาสที่เป็นนามธรรมคือมันต้องเป็นคลาสย่อยอย่างไรก็ตามมันมีตรรกะที่ (ในทางทฤษฎี) คลาสย่อยทั้งหมดจะใช้ วิธีนี้จะช่วยลดความซ้ำซ้อนของรหัส สิ่งที่ฉันถามคือการใช้งานใด ๆ เหล่านั้นควรถูกปิดจากการถูก overriden (โดยพื้นฐานแล้วบอกว่าวิธีการพื้นฐานเป็นวิธีเดียว)
Justin Pihony

@JustinPihony: ขอบคุณ ใน Delphi เมื่อคุณทำเครื่องหมายนามธรรมวิธีคอมไพเลอร์จะบ่นถ้าคุณให้การใช้งานสำหรับมัน น่าสนใจว่าภาษาที่แตกต่างใช้แนวคิดต่างกันอย่างไรและสร้างความคาดหวังที่แตกต่างกันในผู้ใช้
Marjan Venema

@svick: ใช่มันทำให้รู้สึกที่สมบูรณ์แบบฉันจะไม่เรียกมันว่าเป็นระดับนามธรรม แต่ชั้นเรียนที่มีวิธีการที่เป็นนามธรรม ... แต่ฉันเดาว่ามันอาจเป็นการตีความของฉัน
Marjan Venema

@MarjanVenema แต่นั่นไม่ใช่คำศัพท์ที่ใช้ใน C # หากคุณทำเครื่องหมายวิธีใด ๆ ในชั้นเรียนabstractคุณต้องทำเครื่องหมายทั้งชั้นabstractด้วย
svick

0

อย่างไรก็ตามตราบใดที่คุณเชื่อมั่นว่าหลักการของการทดแทนของ Liskov จะต้องเป็นไปตามนั้น> ทำไมคุณถึงไม่ยอมให้มันล้มล้าง?

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

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

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