ทำไมการสืบทอดและความหลากหลายจึงใช้กันอย่างแพร่หลาย?


18

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

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

แต่วิธีนี้ง่ายกว่ามากในการพิมพ์เป็ดไม่ว่าจะเป็นภาษาแบบไดนามิกเช่น Python หรือภาษาแบบคงที่เช่น C ++

ลองพิจารณาฟังก์ชั่น Python ต่อไปนี้ตามด้วย C ++ แบบคงที่:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

การเทียบเท่า OOP จะเหมือนกับโค้ด Java ต่อไปนี้:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

แน่นอนว่าข้อแตกต่างที่สำคัญคือเวอร์ชัน Java ต้องการการสร้างอินเตอร์เฟสหรือลำดับชั้นการสืบทอดก่อนที่จะทำงาน เวอร์ชั่น Java จึงเกี่ยวข้องกับการทำงานมากขึ้นและมีความยืดหยุ่นน้อยลง นอกจากนี้ฉันพบว่าลำดับชั้นการสืบทอดในโลกความเป็นจริงส่วนใหญ่ค่อนข้างไม่เสถียร เราทุกคนได้เห็นตัวอย่างของรูปร่างและสัตว์ที่ประดิษฐ์ขึ้นแล้ว แต่ในโลกแห่งความจริงเมื่อความต้องการทางธุรกิจเปลี่ยนไปและมีการเพิ่มฟีเจอร์ใหม่ ๆ มันเป็นการยากที่จะทำงานให้เสร็จก่อนที่คุณจะต้องยืดความสัมพันธ์ "is-a" ระหว่าง คลาสย่อยหรืออื่น ๆ สร้างใหม่ / refactor ลำดับชั้นของคุณเพื่อรวมคลาสพื้นฐานหรืออินเทอร์เฟซเพิ่มเติมเพื่อรองรับความต้องการใหม่ ด้วยการพิมพ์เป็ดคุณไม่จำเป็นต้องกังวลเกี่ยวกับการสร้างแบบจำลองอะไรเลยคุณเพียงแค่กังวลเกี่ยวกับฟังก์ชั่นที่คุณต้องการ

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


ฉันไม่ใช่ผู้เชี่ยวชาญของ Python ดังนั้นฉันจึงต้องถามว่าจะเกิดอะไรขึ้นหากobjไม่มีdoSomethingวิธีการ มีข้อยกเว้นเกิดขึ้นหรือไม่ ไม่มีอะไรเกิดขึ้น?
FrustratedWithFormsDesigner

6
@Frustrated: มีข้อยกเว้นที่ชัดเจนและเฉพาะเจาะจงมากขึ้น
dsimcha

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

3
อ่านครั้งแรก "กันอย่างแพร่หลาย" เป็น "อย่างดุเดือด" ...

คำตอบ:


22

ฉันเห็นด้วยกับคุณเป็นส่วนใหญ่ แต่เพื่อความสนุกฉันจะเล่น Devil's Advocate อินเทอร์เฟซที่ชัดเจนให้ที่เดียวเพื่อค้นหาสัญญาที่ระบุไว้อย่างเป็นทางการและบอกคุณว่าควรจะทำอย่างไร สิ่งนี้อาจสำคัญเมื่อคุณไม่ได้เป็นเพียงผู้พัฒนาโครงการ

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

Bottom line: ฉันยอมรับว่าการสืบทอดสไตล์ Java และ polymorphism สามารถเป็น PITA และทางเลือกควรใช้บ่อยกว่า แต่ก็ยังมีข้อดีอยู่


29

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

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

คุณพูดว่า:

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

ฉันพูด:

ไม่มันไม่ใช่

สิ่งที่คุณอธิบายไม่ใช่ความแตกต่าง แต่เป็นมรดก ไม่น่าแปลกใจที่คุณกำลังมีปัญหา OOP! ;-)

สำรองขั้นตอน: polymorphism เป็นประโยชน์ของการส่งข้อความ; มันก็หมายความว่าแต่ละวัตถุมีอิสระที่จะตอบสนองต่อข้อความในทางของตัวเอง

ดังนั้น ... การพิมพ์เป็ดคือ polymorphism (หรือมากกว่านั้น)

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

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


2
+1 สำหรับ "การพิมพ์เป็ดคือ polymorphism" ความแตกต่างและการสืบทอดได้ถูกล่ามโซ่ไว้ด้วยกันเป็นเวลานานเกินไปและการพิมพ์แบบสแตติกที่เข้มงวดเป็นเหตุผลเดียวที่พวกเขาเคยมีมา
cHao

+1 ฉันคิดว่าความแตกต่างแบบคงที่เทียบกับแบบไดนามิกควรมากกว่าหมายเหตุด้านข้าง - มันเป็นหัวใจหลักของความแตกต่างระหว่างการพิมพ์เป็ดและความหลากหลายแบบจาวา
Sava B.

8

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

ทำไม?

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

นั่นคือทั้งหมดที่ ไม่มี "ข้อ จำกัด ที่ไม่จำเป็น" มันเป็นความเรียบง่าย

ความแตกต่างในทำนองเดียวกันคือ "การพิมพ์เป็ด" หมายถึง วิธีการเดียวกัน มีคลาสจำนวนมากที่มีอินเตอร์เฟสเหมือนกัน แต่มีการใช้งานที่แตกต่างกัน

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


7

มรดกถูกทารุณกรรม แต่เป็นเช่นเดียวกับการพิมพ์เป็ด ทั้งสามารถและนำไปสู่ปัญหา

ด้วยการพิมพ์ที่รัดกุมคุณจะได้รับ "การทดสอบหน่วย" ที่รวบรวมได้ ด้วยการพิมพ์เป็ดคุณมักจะต้องเขียนพวกเขา


3
ความคิดเห็นของคุณเกี่ยวกับการทดสอบจะใช้กับการพิมพ์เป็ดแบบไดนามิกเท่านั้น C ++ - การพิมพ์เป็ดแบบคงที่ (พร้อมแม่แบบ) ให้ข้อผิดพลาดขณะรวบรวม (แม้ว่าข้อผิดพลาดแม่แบบ C ++ ที่ได้รับนั้นค่อนข้างยากในการถอดรหัส แต่นั่นเป็นปัญหาที่แตกต่างออกไปโดยสิ้นเชิง)
Channel72

2

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

จากนั้นหากคุณได้เรียนรู้มันดันทุรังคุณอาจ "กบฏ" ในภายหลังเช่นเดียวกับในอีกทางหนึ่ง ไม่ดีเหมือนกัน

เช่นเดียวกับความคิดใด ๆ ที่ดีที่สุดที่จะใช้แนวทางปฏิบัติ พัฒนาความเข้าใจในสิ่งที่พวกเขาเหมาะสมและที่ที่พวกเขาไม่ และเพิกเฉยต่อวิธีการที่พวกเขาขายได้หมด


2

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

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

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


1

การสืบทอดเป็นความสัมพันธ์ที่ดีระหว่างสองคลาส ฉันไม่คิดว่า Java มีความแข็งแกร่งกว่านี้อีกแล้ว ดังนั้นคุณควรใช้เมื่อคุณหมายถึงเท่านั้น การสืบทอดสาธารณะคือความสัมพันธ์แบบ "is-a" ไม่ใช่แบบ "มักจะเป็น" มันเป็นเรื่องง่ายมากที่จะใช้มรดกมากเกินไปและยุ่งเหยิง ในหลายกรณีมรดกจะถูกใช้เพื่อแทน "has-a" หรือ "take-functional-from-a" และโดยทั่วไปแล้วการเรียงลำดับจะทำได้ดีกว่า

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

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

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

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


0

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

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

โดยทั่วไปเป็นสถานการณ์ที่เหมาะสมสำหรับเครื่องมือ: OOP แบบดั้งเดิมสามารถทำงานได้ดีเมื่อคุณมีแบบจำลองที่เหมาะกับมันและการปิดสามารถทำงานได้ดีเมื่อคุณไม่มี


-1

ความจริงตั้งอยู่ตรงกลาง ฉันชอบวิธีที่ C # 4.0 เป็นภาษาที่พิมพ์แบบคงที่รองรับ "เป็ดพิมพ์" โดยคำหลัก "ไดนามิก"


-1

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

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

ที่นี่ชั้นเรียนGoldenRetrieverมีเช่นเดียวSoundกับDogขอบคุณฟรีเพื่อสืบทอด

ฉันจะเขียนตัวอย่างเดียวกันกับระดับ Haskell เพื่อให้คุณเห็นความแตกต่าง

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

ที่นี่คุณไม่ได้หนีไม่ต้องระบุสำหรับsound GoldenRetrieverสิ่งที่ง่ายที่สุดโดยทั่วไปคือ

sound GoldenRetriever = sound Dog

แต่เพียงแค่ imagen ถ้าคุณมี 20 ฟังก์ชั่น! หากมีผู้เชี่ยวชาญของ Haskell โปรดแสดงให้เราเห็นวิธีที่ง่ายขึ้น

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


5
ฉันสาบานว่าAnimalเป็นการต่อต้านการกวดวิชาของ OOP
Telastyn

@Telastyn ฉันได้เรียนรู้ตัวอย่างจาก Derek Banas บน Youtube สุดยอดครู ในความคิดของฉันมันง่ายมากที่จะเห็นภาพและเหตุผลเกี่ยวกับ คุณมีอิสระที่จะแก้ไขโพสต์ด้วยตัวอย่างที่ดีกว่า
Cristian Garcia

ดูเหมือนจะไม่เพิ่มอะไรเลยใน 10 คำตอบก่อนหน้านี้
ริ้น

@gnat ในขณะที่ "สสาร" มีอยู่แล้วบุคคลที่ใหม่ในเรื่องอาจพบตัวอย่างง่ายกว่าที่จะเข้าใจ
Cristian Garcia

2
สัตว์และรูปร่างเป็นแอพพลิเคชั่นที่ไม่ดีอย่างแท้จริงของความหลากหลายด้วยเหตุผลหลายประการซึ่งได้มีการกล่าวถึงอาการคลื่นไส้ในเว็บไซต์นี้ โดยพื้นฐานแล้วการพูดว่าแมว "เป็น" สัตว์ไม่มีความหมายเนื่องจากไม่มี "สัตว์" ที่เป็นรูปธรรม สิ่งที่คุณหมายถึงแบบจำลองจริงๆคือพฤติกรรมไม่ใช่ตัวตน การกำหนดอินเทอร์เฟซ "CanSpeak" ที่สามารถนำไปใช้เป็น meow หรือเห่าทำให้รู้สึก การกำหนดอินเทอร์เฟซ "HasArea" ที่สี่เหลี่ยมและวงกลมสามารถนำมาใช้ทำให้รู้สึก แต่ไม่ใช่รูปร่างทั่วไป
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.