วิธีการยึดมั่นในหลักการเปิดในทางปฏิบัติ


14

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

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

  1. ก่อน: โปรแกรมไปยัง abstractions และ 'ทำนายอนาคต' มากที่สุดเท่าที่จะทำได้ ตัวอย่างเช่นวิธีการdrive(Car car)จะต้องเปลี่ยนหาก Motorcycleมีการเพิ่ม s ในระบบในอนาคตดังนั้นจึงอาจละเมิด OCP แต่วิธีdrive(MotorVehicle vehicle)นี้มีแนวโน้มที่จะไม่เปลี่ยนแปลงในอนาคตดังนั้นจึงเป็นไปตาม OCP

    อย่างไรก็ตามมันค่อนข้างยากที่จะทำนายอนาคตและรู้ล่วงหน้าว่าการเปลี่ยนแปลงใดที่จะเกิดขึ้นกับระบบ

  2. หลังจาก: เมื่อต้องการการเปลี่ยนแปลงให้ขยายคลาสแทนการแก้ไขเป็นรหัสปัจจุบัน

แบบฝึก # 1 ไม่ยากที่จะเข้าใจ อย่างไรก็ตามมันเป็นข้อปฏิบัติที่ 2 ที่ฉันมีปัญหาในการทำความเข้าใจวิธีการสมัคร

ยกตัวอย่างเช่น (ฉันเอามันจากวิดีโอบน YouTube): ขอบอกว่าเรามีวิธีการในระดับที่ยอมรับได้วัตถุ:CreditCard makePayment(CraditCard card)หนึ่งวันVoucherจะถูกเพิ่มเข้าไปในระบบ วิธีนี้ไม่รองรับพวกมันดังนั้นจึงต้องมีการแก้ไข

เมื่อนำวิธีการนี้ไปใช้ในตอนแรกเราไม่สามารถคาดการณ์อนาคตและโปรแกรมในแง่นามธรรมได้มากขึ้น (เช่นmakePayment(Payment pay)ดังนั้นตอนนี้เราต้องเปลี่ยนรหัสที่มีอยู่

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

หรือหลักการไม่ได้อ้างถึง 'วิธีการแก้ไข / เพิ่มฟังก์ชันการทำงานที่ถูกต้อง' แต่หมายถึง 'วิธีหลีกเลี่ยงการเปลี่ยนแปลงในตอนแรก (เช่นโปรแกรมไปยัง abstractions)?



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

คำตอบ:


14

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

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

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


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

1
นั่นคือความคิด
Karl Bielefeldt

2

ฉันคิดว่าคุณมองไกลเกินไปในอนาคต แก้ปัญหาปัจจุบันด้วยวิธีที่ยืดหยุ่นซึ่งยึดติดกับเปิด / ปิด

สมมติว่าคุณต้องใช้drive(Car car)วิธีการ ขึ้นอยู่กับภาษาของคุณคุณมีสองตัวเลือก

  • สำหรับภาษาที่รองรับการโอเวอร์โหลด (C ++) จากนั้นใช้ drive(const Car& car)

    ในบางจุดในภายหลังคุณอาจต้องแต่มันจะไม่ยุ่งเกี่ยวกับdrive(const Motorcycle& motorcycle) drive(const Car& car)ไม่มีปัญหา!

  • สำหรับภาษาที่ไม่สนับสนุนการบรรทุกเกินพิกัด (วัตถุประสงค์ C) -driveCar:(Car *)carแล้วรวมถึงชื่อชนิดในวิธีการ

    ในบางจุดในภายหลังคุณอาจต้องการ-driveMotorcycle:(Motorcycle *)motorcycleแต่อีกครั้งมันจะไม่รบกวน

สิ่งนี้อนุญาตให้drive(Car car)ปิดการปรับเปลี่ยนได้ แต่เปิดเพื่อขยายไปยังยานพาหนะประเภทอื่น การวางแผนในอนาคตแบบมินิมัลลิสต์ที่ช่วยให้คุณสามารถทำงานได้วันนี้ แต่จะป้องกันไม่ให้คุณปิดกั้นตัวเองในอนาคต

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


การปรับเปลี่ยนคลาสเพื่อเพิ่มวิธีการใหม่ของคุณจะเป็นการละเมิดหลักการ Open-Closed ข้อเสนอแนะของคุณยังกำจัดความสามารถในการใช้หลักการทดแทน Liskov กับรถยนต์ทุกคันที่สามารถขับซึ่งกำจัดส่วนที่แข็งแกร่งที่สุดของ OO
Dunk

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

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

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

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

2

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

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

นั่นหมายความว่าอย่างไร? ฉันควรคลาสย่อยคลาสที่มีอยู่แทนที่เพียงแค่เปลี่ยนเป็นรหัสเดิมหรือไม่

ได้.

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

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

พฤติกรรมใหม่ = คลาสใหม่

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

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