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


11

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

คำถามนี้เกี่ยวกับความคิดเดียวกัน แต่ไม่มีคำตอบแนะนำว่าอะไรคือวิธีที่ดีในการเปลี่ยนรูปแบบไม่ได้และเมื่อใดจะใช้งานจริง มีรูปแบบการออกแบบที่เปลี่ยนแปลงไม่ได้บ้างไหม? แนวคิดทั่วไปดูเหมือนจะ "ทำให้วัตถุไม่เปลี่ยนรูปเว้นแต่คุณต้องการเปลี่ยนอย่างแน่นอน" ซึ่งไม่มีประโยชน์ในทางปฏิบัติ

ประสบการณ์ของฉันคือการไม่เปลี่ยนรูปไดรฟ์รหัสของฉันมากขึ้นในกระบวนทัศน์การทำงานและความก้าวหน้านี้เกิดขึ้นเสมอ:

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

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

เพื่อความสะดวกฉันจะยกตัวอย่าง มาChessBoardเป็นคอลเล็กชั่นหมากรุกที่ไม่เปลี่ยนรูปแบบกันเถอะ(ขยายคลาสนามธรรม)Piece) จากมุมมองของ OOP ชิ้นส่วนมีหน้าที่สร้างการเคลื่อนไหวที่ถูกต้องจากตำแหน่งบนกระดาน แต่เพื่อสร้างการเคลื่อนไหวชิ้นส่วนจำเป็นต้องมีการอ้างอิงถึงคณะกรรมการในขณะที่คณะกรรมการจะต้องมีการอ้างอิงถึงชิ้นส่วนของมัน มีเทคนิคบางอย่างในการสร้างการอ้างอิงโยงที่ไม่เปลี่ยนรูปแบบเหล่านี้ขึ้นอยู่กับภาษา OOP ของคุณ แต่พวกเขามีความเจ็บปวดในการจัดการดีกว่าไม่มีชิ้นส่วนที่จะอ้างอิงบอร์ดของมัน แต่ชิ้นส่วนนั้นไม่สามารถสร้างการเคลื่อนไหวได้เนื่องจากมันไม่ทราบสถานะของบอร์ด จากนั้นชิ้นส่วนจะกลายเป็นเพียงโครงสร้างข้อมูลที่เก็บประเภทชิ้นส่วนและตำแหน่งของชิ้นงาน จากนั้นคุณสามารถใช้ฟังก์ชัน polymorphic เพื่อสร้างการเคลื่อนไหวสำหรับชิ้นส่วนทุกประเภท สิ่งนี้สามารถทำได้อย่างสมบูรณ์แบบในการเขียนโปรแกรมใช้งานได้ แต่แทบจะเป็นไปไม่ได้ใน OOP โดยไม่ต้องตรวจสอบชนิดของรันไทม์และแนวทางปฏิบัติ OOP ที่ไม่ดีอื่น ๆ ... จากนั้น


3
ฉันชอบคำถามฐาน แต่ฉันมีเวลายากกับรายละเอียด ยกตัวอย่างเช่นเหตุใดการสืบทอดจึงหยุดลงเมื่อคุณไม่เปลี่ยนรูป?
Martin Ba

1
วัตถุของคุณไม่มีวิธี?
หยุดทำอันตรายโมนิก้า

6
มีความเป็นไปได้ที่ซ้ำกันของการเปลี่ยนแปลงไม่ได้สมบูรณ์และการเขียนโปรแกรมเชิงวัตถุ
gnat

4
"จากมุมมองของ OOP ชิ้นส่วนมีหน้าที่สร้างการเคลื่อนไหวที่ถูกต้องจากตำแหน่งบนกระดาน" - ไม่แน่นอนการออกแบบชิ้นส่วนแบบนั้นอาจทำให้ SRP เสียหายได้
Doc Brown

1
@lishaak คุณ "พยายามอธิบายปัญหาเกี่ยวกับการสืบทอดในแง่ง่าย" เพราะมันไม่ใช่ปัญหา การออกแบบที่ไม่ดีเป็นปัญหา การสืบทอดเป็นสิ่งสำคัญของ OOP, ไซน์ใฐานะที่ไม่ใช่ถ้าคุณจะ สิ่งที่เรียกว่า "ปัญหาเกี่ยวกับการสืบทอด" ส่วนใหญ่เป็นปัญหาเกี่ยวกับภาษาที่ไม่มีไวยากรณ์แทนที่ที่ชัดเจนและพวกเขามีปัญหาน้อยกว่าในภาษาที่ออกแบบมาดีกว่า หากไม่มีวิธีการเสมือนจริงและรูปแบบที่หลากหลายคุณไม่มี OOP คุณมีการเขียนโปรแกรมขั้นตอนที่มีไวยากรณ์วัตถุตลก ดังนั้นจึงไม่น่าแปลกใจที่คุณไม่เห็นประโยชน์ของ OO หากคุณหลีกเลี่ยงมัน!
Mason Wheeler

คำตอบ:


24

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

อย่าดูว่าทำไมไม่ เคยทำมาหลายปีแล้วก่อนที่ Java 8 จะสามารถใช้งานได้ทั้งหมด เคยได้ยิน Strings ไหม? ดีและไม่เปลี่ยนรูปตั้งแต่เริ่มต้น

  1. ฉันเริ่มต้องการโครงสร้างข้อมูลแบบถาวร (ในแง่การใช้งาน) เช่นรายการแผนที่เป็นต้น

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

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

การอ้างอิงแบบวงกลมเป็นนรกชนิดพิเศษ การเปลี่ยนไม่ได้จะช่วยให้คุณประหยัดจากมัน

  1. การรับมรดกหยุดที่จะทำให้ความรู้สึกใด ๆ และฉันเริ่มที่จะใช้องค์ประกอบแทน

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

  1. แนวคิดพื้นฐานทั้งหมดของ OOP เช่นการห่อหุ้มเริ่มที่จะกระจุยและวัตถุของฉันเริ่มดูเหมือนฟังก์ชั่น

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

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

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

  • การเขียนโปรแกรมฟังก์ชั่นกำลังเป็นทางการเกี่ยวกับการมอบหมาย

  • OOP กำลังเป็นทางการเกี่ยวกับพอยน์เตอร์ของฟังก์ชัน

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

ผมขอแสดงอะไรบางอย่าง:

f n (x)

นั่นคือฟังก์ชั่น อันที่จริงมันเป็นฟังก์ชั่นต่อเนื่อง:

f 1 (x)
f 2 (x)
...
f n (x)

คาดเดาว่าเราจะแสดงสิ่งนั้นในภาษา OOP อย่างไร

n.f(x)

ที่นั่นnมีน้อยเลือกสิ่งที่การใช้งานของการfใช้งานและจะตัดสินใจว่าค่าคงที่บางส่วนที่ใช้ในฟังก์ชั่นนั้นคืออะไร (ซึ่งหมายความว่าสิ่งเดียวกัน) ตัวอย่างเช่น:

f 1 (x) = x + 1
f 2 (x) = x + 2

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

g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2

ใช่คุณเดาว่า:

n.g(x)

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

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

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

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

หาเรื่อง! อีกครั้งกับการอ้างอิงแบบวงกลมที่ไม่จำเป็น

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

เดี๋ยวก่อนเราไม่ได้พูดเกี่ยวกับหมากรุกแล้วใช่ไหม


6
ทุกคนควรอ่านสิ่งนี้ แล้วอ่านเข้าใจข้อมูลนามธรรมมาเยือนโดยวิลเลียมอาร์คุก แล้วอ่านมันอีกครั้ง แล้วอ่านคุกข้อเสนอสำหรับย่อ, คำจำกัดความที่ทันสมัยของ "วัตถุ" และ "Object Oriented" โยนใน Alan Kay ของ " ความคิดใหญ่คือ 'ส่งข้อความ' " คำจำกัดความ
Jörg W Mittag

1
และสุดท้าย แต่ไม่น้อยสนธิสัญญาออร์แลนโด
Jörg W Mittag

1
@Eurhoric: ฉันคิดว่ามันเป็นสูตรมาตรฐานที่ค่อนข้างตรงกับคำนิยามมาตรฐานสำหรับ "functional programming", "compative programming", "logic programming" เป็นต้นมิฉะนั้น C เป็นภาษาที่ใช้งานได้เพราะคุณสามารถเข้ารหัส FP ในนั้นได้ ภาษาลอจิกเนื่องจากคุณสามารถเข้ารหัสการเขียนโปรแกรมลอจิกในนั้นเป็นภาษาไดนามิกเพราะคุณสามารถเข้ารหัสการพิมพ์แบบไดนามิกในภาษานักแสดงเพราะคุณสามารถเข้ารหัสระบบนักแสดงในนั้นและอื่น ๆ และ Haskell เป็นภาษาที่จำเป็นที่สุดในโลกเนื่องจากคุณสามารถตั้งชื่อผลข้างเคียงแล้วเก็บไว้เป็นตัวแปรส่งผ่านเป็น ...
Jörg W Mittag

1
ฉันคิดว่าเราสามารถใช้ความคิดแบบเดียวกันกับที่อยู่ในบทความอัจฉริยะของ Matthias Felleisen เกี่ยวกับการแสดงออกทางภาษา การย้ายโปรแกรม OO จาก Java ไปยังC♯สามารถทำได้ด้วยการแปลงเฉพาะที่ แต่การย้ายไปที่ C ต้องมีการปรับโครงสร้างทั่วโลก (โดยทั่วไปคุณต้องแนะนำกลไกการกระจายข้อความ Java และC♯สามารถแสดง OO และ C สามารถเข้ารหัส "เท่านั้น"
Jörg W Mittag

1
@lishaak เพราะคุณกำลังระบุสิ่งต่าง ๆ สิ่งนี้จะช่วยให้มันอยู่ในระดับหนึ่งของสิ่งที่เป็นนามธรรมและป้องกันการทำซ้ำการจัดเก็บข้อมูลตำแหน่งที่มิฉะนั้นอาจไม่สอดคล้องกัน หากคุณไม่พอใจการพิมพ์แบบพิเศษให้ติดไว้ในวิธีการเพื่อให้คุณพิมพ์ได้เพียงครั้งเดียว .. ชิ้นส่วนไม่จำเป็นต้องจำว่ามันอยู่ที่ไหนถ้าคุณบอกว่ามันอยู่ที่ไหน ตอนนี้ชิ้นส่วนเก็บข้อมูลเท่านั้นเช่นสีย้ายและภาพ / ตัวย่อที่เป็นจริงและไม่เปลี่ยนรูป
candied_orange

2

แนวคิดที่มีประโยชน์ที่สุดที่แนะนำโดย OOP ในใจของฉันคือ:

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

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

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

ลองดูว่า Scala พยายามที่จะรวมวิธีการที่เปลี่ยนแปลงไม่ได้และ FP เข้ากับ OOP เป็นที่ยอมรับว่าไม่ใช่ภาษาที่ง่ายและสง่างามที่สุด แม้ว่ามันจะประสบความสำเร็จในทางปฏิบัติ นอกจากนี้ให้ดูที่ Kotlin ที่มีเครื่องมือและวิธีการมากมายสำหรับการผสมผสานที่คล้ายกัน

ปัญหาที่เกิดขึ้นตามปกติกับการพยายามใช้วิธีการที่แตกต่างจากผู้สร้างภาษาที่มีอยู่ในใจNปีที่ผ่านมาคือ 'ความต้านทานไม่ตรงกันกับห้องสมุดมาตรฐาน OTOH ทั้ง Java และ. NET ในระบบนิเวศมีการสนับสนุนไลบรารีมาตรฐานที่เหมาะสมสำหรับโครงสร้างข้อมูลที่ไม่เปลี่ยนแปลง แน่นอนว่าห้องสมุดของบุคคลที่สามก็มีอยู่เช่นกัน

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