วิธีการใช้มรดก RealNumber และ ComplexNumber


11

หวังว่าจะไม่เป็นนักวิชาการ ...

สมมติว่าฉันต้องการจำนวนจริงและซับซ้อนในห้องสมุด SW ของฉัน

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

ในทางตรงกันข้ามการดำเนินการของฉันจะเป็นเด็กที่ขยายผู้ปกครองดังนั้นในผู้ปกครอง RealNumber ฉันมีส่วนจริงและเด็ก ComplexNumber จะเพิ่มงานศิลปะจินตภาพ

นอกจากนี้ยังมีความเห็นว่าการรับมรดกเป็นความชั่วร้าย

ฉันจำได้เหมือนเมื่อวานเมื่อฉันเรียนรู้ OOP ที่มหาวิทยาลัยอาจารย์ของฉันบอกว่านี่ไม่ใช่ตัวอย่างที่ดีของการสืบทอดเนื่องจากค่าสัมบูรณ์ของทั้งสองนั้นคำนวณต่างกัน (แต่สำหรับวิธีที่เรามีวิธีการมากไป .

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


7
ดูเหมือนว่าจะครอบคลุมในคำถามก่อนหน้านี้: สี่เหลี่ยมควรสืบทอดจากสี่เหลี่ยมหรือไม่
ริ้น

1
@gnat โอ้มนุษย์นั่นเป็นอีกตัวอย่างที่ฉันต้องการใช้ ... ขอบคุณ!
Betlista

7
... โปรดทราบว่าประโยค "จำนวนจริงเป็นจำนวนเชิงซ้อน" ในความหมายทางคณิตศาสตร์มีผลเฉพาะกับจำนวนที่ไม่เปลี่ยนรูปดังนั้นหากคุณใช้วัตถุที่ไม่เปลี่ยนรูปคุณสามารถหลีกเลี่ยงการละเมิด LSP (เช่นเดียวกันสำหรับสี่เหลี่ยมและสี่เหลี่ยมดูที่นี้ดังนั้นคำตอบ )
Doc Brown

5
... สังเกตเพิ่มเติมการคำนวณค่าสัมบูรณ์สำหรับจำนวนเชิงซ้อนยังใช้กับจำนวนจริงดังนั้นฉันไม่แน่ใจว่าอาจารย์ของคุณหมายถึงอะไร หากคุณใช้วิธี "Abs ()" อย่างถูกต้องในจำนวนเชิงซ้อนที่ไม่เปลี่ยนรูปและได้รับ "ของจริง" จากนั้นวิธีการ Abs () จะยังคงให้ผลลัพธ์ที่ถูกต้อง
Doc Brown

คำตอบ:


17

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

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

นี่เป็นปัญหาเดียวกันกับตัวอย่างสี่เหลี่ยม / สี่เหลี่ยมที่กล่าวถึงในความคิดเห็น


2
วันนี้ฉันเห็น "หลักการทดแทน Liskow" หลายครั้งฉันจะต้องอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้เพราะฉันไม่รู้
Betlista

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

4
มันเหมาะสมที่จะมีจำนวนจริงมาจากจำนวนเชิงซ้อนตราบใดที่ทั้งคู่ไม่เปลี่ยนรูป และคุณไม่รังเกียจค่าใช้จ่าย
Deduplicator

@Dupuplicator: จุดที่น่าสนใจ การเปลี่ยนไม่ได้ช่วยแก้ไขปัญหาได้มากมาย แต่ฉันยังไม่มั่นใจอย่างเต็มที่ในกรณีนี้ ต้องคิดเกี่ยวกับมัน
Frank Puffer

3

ไม่ใช่ตัวอย่างที่ดีของการสืบทอดเนื่องจากค่าสัมบูรณ์ของทั้งสองนั้นถูกคำนวณต่างกัน

นี่ไม่ใช่เหตุผลที่น่าสนใจสำหรับการสืบทอดทั้งหมดที่นี่เพียงแค่โมเดลclass RealNumber<-> ที่class ComplexNumberเสนอ

คุณอาจกำหนดอินเตอร์เฟสNumberที่เหมาะสมซึ่งทั้งสองอย่าง RealNumberและComplexNumberจะนำไปใช้

ที่อาจมีลักษณะเช่น

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

แต่คุณต้องการ จำกัดNumberพารามิเตอร์อื่น ๆในการดำเนินการเหล่านี้ให้เป็นประเภทที่ได้รับเหมือนกับthisที่คุณสามารถเข้าใกล้ได้

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

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

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations

0

วิธีแก้ไข: ไม่มีRealNumberคลาสสาธารณะ

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

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

  • ขยายพฤติกรรม RealNumbersไม่สามารถทำการดำเนินการพิเศษใด ๆ ที่ซับซ้อนได้ไม่สามารถทำได้ดังนั้นจึงไม่มีประเด็นในการทำเช่นนี้

  • การใช้พฤติกรรมนามธรรมด้วยการใช้งานเฉพาะ เนื่องจากComplexNumberไม่ควรเป็นนามธรรมจึงไม่สามารถใช้ได้

  • รหัสนำมาใช้ใหม่ ถ้าคุณใช้ComplexNumberคลาสคุณจะใช้รหัส 100% อีกครั้ง

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

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


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

0

การบอกว่าจำนวนจริงเป็นจำนวนเชิงซ้อนมีความหมายมากกว่าในทางคณิตศาสตร์โดยเฉพาะทฤษฎีเซตมากกว่าวิทยาศาสตร์คอมพิวเตอร์
ในวิชาคณิตศาสตร์เราพูดว่า:

  • จำนวนจริงเป็นจำนวนเชิงซ้อนเนื่องจากชุดของจำนวนเชิงซ้อนประกอบด้วยชุดของจำนวนจริง
  • จำนวนตรรกยะเป็นจำนวนจริงเนื่องจากชุดของจำนวนจริงประกอบด้วยชุดของจำนวนตรรกยะ (และชุดจำนวนอตรรกยะ)
  • จำนวนเต็มเป็นจำนวนตรรกยะเนื่องจากชุดจำนวนตรรกยะรวมชุดของจำนวนเต็ม

อย่างไรก็ตามสิ่งนี้ไม่ได้หมายความว่าคุณต้องใช้หรือควรใช้การสืบทอดเมื่อออกแบบไลบรารีของคุณเพื่อรวมคลาส RealNumber และ ComplexNumber ในจาวาที่มีประสิทธิภาพ, Second Editionโดย Joshua Bloch; รายการที่ 16 คือ "โปรดปรานการประพันธ์มากกว่ามรดก" เพื่อหลีกเลี่ยงปัญหาที่กล่าวถึงในรายการนั้นเมื่อคุณได้กำหนดคลาส RealNumber ของคุณแล้วคุณสามารถใช้คลาส ComplexNumber ของคุณได้:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

สิ่งนี้ช่วยให้คุณสามารถใช้คลาส RealNumber ของคุณเพื่อรักษารหัสของคุณแห้งขณะที่หลีกเลี่ยงปัญหาที่ระบุโดย Joshua Bloch


0

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

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

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