ฉันควรใช้วิธีนามธรรมหรือเสมือน?


11

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

  • ข้อได้เปรียบของเวอร์ชั่น "นามธรรม" คือมันอาจดูสะอาดและบังคับให้คลาสที่ได้รับมาเพื่อให้การใช้งานมีความหมายอย่างมีความหวัง

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

เวอร์ชั่นบทคัดย่อ:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

รุ่นเสมือน:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

เหตุใดเราจึงสมมติว่าส่วนต่อประสานไม่เป็นที่ต้องการ
Anthony Pegram

หากไม่มีปัญหาบางส่วนก็ยากที่จะบอกว่าคนหนึ่งดีกว่าอีกคนหนึ่ง
Codism

@Anthony: อินเตอร์เฟสไม่เป็นที่ต้องการเนื่องจากมีฟังก์ชั่นการใช้งานที่มีประโยชน์ซึ่งจะเข้าสู่คลาสนี้ด้วย
Dunk

4
return ReturnType.NotImplemented? อย่างจริงจัง? หากคุณไม่สามารถปฏิเสธประเภทที่ยังไม่ได้ใช้งานได้ในเวลารวบรวม (คุณสามารถใช้วิธีนามธรรม) อย่างน้อยก็โยนข้อยกเว้น
Jan Hudec

3
@Dunk: ที่นี่พวกเขามีความจำเป็น ค่าส่งคืนจะไม่ถูกตรวจสอบ
Jan Hudec

คำตอบ:


15

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


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

3
+1 - นอกเหนือจากความล้มเหลวก่อนกำหนดผู้สืบทอดสามารถดูได้ทันทีว่าวิธีใดที่พวกเขาต้องใช้ผ่าน "Implement Abstract Class" แทนที่จะทำในสิ่งที่พวกเขาคิดว่าเพียงพอแล้วล้มเหลวขณะใช้งานจริง
Telastyn

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

25

เวอร์ชันเสมือนเป็นทั้งบั๊กที่มีแนวโน้มและไม่ถูกต้องทางความหมาย

บทคัดย่อกำลังพูดว่า "วิธีนี้ไม่ได้ถูกนำมาใช้ที่นี่คุณต้องใช้มันเพื่อให้การเรียนในชั้นนี้"

เสมือนกำลังพูดว่า "ฉันมีการใช้งานเริ่มต้น แต่คุณสามารถเปลี่ยนฉันหากคุณต้องการ"

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


3

ขึ้นอยู่กับการใช้ชั้นเรียนของคุณ

หากวิธีการมีการใช้งานที่“ ว่างเปล่า” อย่างสมเหตุสมผลคุณมีวิธีการมากมายและคุณมักจะแทนที่เพียงไม่กี่วิธีการเหล่านั้นก็ใช้virtualวิธีการที่เหมาะสม ตัวอย่างเช่นExpressionVisitorการใช้วิธีนี้

มิฉะนั้นฉันคิดว่าคุณควรใช้abstractวิธีการ

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


ฉันจะทราบว่า "NotImplementedException" มักจะระบุข้อผิดพลาดของการละเลยในขณะที่ "NotSupportedException" หมายถึงตัวเลือกที่ชัดเจน นอกจากนั้นฉันเห็นด้วย
Anthony Pegram

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

1

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

รหัสภาพเช่นนี้:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

การทำเช่นนี้จะช่วยแก้ปัญหาเหล่านี้ได้:

  1. โดยการมีรหัสทั้งหมดที่ใช้วัตถุที่ได้มาจาก AbstractVersion ตอนนี้สามารถนำไปใช้เพื่อรับส่วนต่อประสาน IVersion แทนซึ่งหมายความว่าพวกเขาสามารถทดสอบหน่วยได้ง่ายขึ้น

  2. รีลีส 2 ของผลิตภัณฑ์ของคุณสามารถนำอินเตอร์เฟส IVersion2 ไปใช้เพื่อมอบฟังก์ชันการทำงานเพิ่มเติมโดยไม่ทำลายรหัสลูกค้าที่มีอยู่ของคุณ

เช่น.

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

นอกจากนี้ยังควรอ่านเกี่ยวกับการพึ่งพาการพึ่งพาเช่นกันเพื่อป้องกันไม่ให้คลาสนี้มีการอ้างอิงแบบฮาร์ดโค้ดที่ป้องกันการทดสอบหน่วยที่มีประสิทธิภาพ


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

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

-2

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


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