MVC: คอนโทรลเลอร์ทำลายหลักการความรับผิดชอบเดี่ยวหรือไม่?


16

หลักการความรับผิดชอบเดี่ยวระบุว่า "คลาสควรมีเหตุผลประการเดียวคือการเปลี่ยนแปลง"

ในรูปแบบ MVC งานของผู้ควบคุมจะเป็นสื่อกลางระหว่างมุมมองและรูปแบบ มันมีอินเทอร์เฟซสำหรับมุมมองเพื่อรายงานการกระทำของผู้ใช้บน GUI (เช่นการอนุญาตให้ดูการโทรcontroller.specificButtonPressed()) และสามารถเรียกวิธีการที่เหมาะสมในตัวแบบเพื่อจัดการข้อมูลหรือเรียกใช้การดำเนินการ (เช่นmodel.doSomething()) .

ซึ่งหมายความว่า:

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

นั่นหมายความว่ามีสองเหตุผลในการเปลี่ยนแปลง : การเปลี่ยนแปลงใน GUI และการเปลี่ยนแปลงในตรรกะธุรกิจ

หากมีการเปลี่ยนแปลง GUI เช่นมีการเพิ่มปุ่มใหม่ตัวควบคุมอาจต้องเพิ่มวิธีการใหม่เพื่อให้มุมมองรายงานผู้ใช้กดปุ่มนี้

และหากตรรกะทางธุรกิจในรูปแบบการเปลี่ยนแปลงผู้ควบคุมอาจต้องเปลี่ยนเพื่อที่จะเรียกใช้วิธีการที่ถูกต้องในรูปแบบ

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


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

1
ประเด็นของคำถามคืออะไร? หากเราทุกคนตอบว่า "ใช่มันก็เป็นเช่นนั้น" แล้วจะเป็นอย่างไร? เกิดอะไรขึ้นถ้าคำตอบคือ "ไม่"? ปัญหาจริงที่คุณพยายามแก้ไขด้วยคำถามนี้คืออะไร
ไบรอัน Oakley

1
"เหตุผลในการเปลี่ยนแปลง" ไม่ได้หมายความว่า "รหัสที่เปลี่ยนแปลง" หากคุณพิมพ์ชื่อผิดเจ็ดตัวในชื่อตัวแปรสำหรับชั้นเรียนตอนนี้ชั้นเรียนมีความรับผิดชอบ 7 ประการหรือไม่? ไม่หากคุณมีตัวแปรมากกว่าหนึ่งตัวหรือมากกว่าหนึ่งฟังก์ชันคุณอาจยังมีความรับผิดชอบเพียงอย่างเดียว
Bob

คำตอบ:


14

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

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


1
เช่นเดียวกับการเพิ่มบันทึก หากคุณมี controller.makeBreakfast () และ controller.wakeUpFamily () ดังนั้นคอนโทรลเลอร์นั้นจะทำลาย SRP แต่ไม่ใช่เพราะมันเป็นคอนโทรลเลอร์เพียงเพราะมีความรับผิดชอบมากกว่าหนึ่งรายการ
Bob

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

1
ใช่ฉันยอมรับได้ว่าผู้ควบคุมมีความรับผิดชอบมากกว่าหนึ่งคน อย่างไรก็ตามสิ่งเหล่านี้เป็นความรับผิดชอบ "ต่ำกว่า" และนั่นไม่ใช่ปัญหาเพราะในระดับที่เป็นนามธรรมสูงสุดนั้นมีเพียงหนึ่งความรับผิดชอบ
valenterry

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

คำตอบนี้สมเหตุสมผลสำหรับฉัน ขอบคุณ Valenterry
J86

9

หากคลาสมี "เหตุผลสองประการที่เป็นไปได้ที่จะเปลี่ยนแปลง" ดังนั้นก็ใช่ว่าเป็นการละเมิด SRP

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

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

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

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


4

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

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

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

จากบทความ Wikipedia เกี่ยวกับ SRP

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

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

รู้ว่าวัตถุมีวิธีการโทรไม่เหมือนรู้การทำงานของมัน


1
เหตุผลที่ฉันคิดว่าคอนโทรลเลอร์ควรมีวิธีการเช่นspecificButtonsPressed()นี้เพราะฉันอ่านว่ามุมมองไม่ควรรู้อะไรเกี่ยวกับการทำงานของปุ่มและองค์ประกอบ GUI อื่น ๆ ฉันได้รับการสอนว่าเมื่อกดปุ่มมุมมองควรรายงานต่อคอนโทรลเลอร์และผู้ควบคุมควรตัดสินใจว่า 'ความหมาย' คืออะไร (จากนั้นเรียกใช้วิธีการที่เหมาะสมกับโมเดล) การเรียกใช้มุมมองcontroller.saveData()หมายถึงมุมมองที่ต้องทราบเกี่ยวกับความหมายของการกดปุ่มนี้นอกเหนือจากความจริงที่ว่ามันถูกกด
Aviv Cohn

1
ถ้าฉันคิดว่าการแยกที่สมบูรณ์ระหว่างการกดปุ่มและมันหมายถึง (ซึ่งส่งผลให้คอนโทรลเลอร์มีวิธีการเช่นspecificButtonPressed()) แน่นอนว่าคอนโทรลเลอร์จะไม่เชื่อมโยงกับ GUI มากนัก ฉันควรทิ้งspecificButtonPressed()วิธีการหรือไม่ ฉันคิดว่าการใช้วิธีการเหล่านี้มีประโยชน์ต่อคุณหรือไม่? หรือมีbuttonPressed()วิธีการในคอนโทรลเลอร์ไม่คุ้มกับปัญหาหรือไม่
Aviv Cohn

1
ในคำอื่น ๆ (ขออภัยสำหรับความคิดเห็นยาว): ผมคิดว่าประโยชน์ของการมีspecificButtonPressed()วิธีการในการควบคุมคือว่ามันแยกดูจากความหมายของมันกดปุ่มสมบูรณ์ อย่างไรก็ตามข้อเสียคือเชื่อมโยงคอนโทรลเลอร์กับ GUI ในแง่หนึ่ง วิธีไหนดีกว่ากัน
Aviv Cohn

@prog IMO คอนโทรลเลอร์ควรจะตาบอดกับมุมมอง ปุ่มจะมีฟังก์ชั่นบางอย่าง แต่ไม่จำเป็นต้องรู้รายละเอียด มันแค่ต้องรู้ว่ามันกำลังส่งข้อมูลบางอย่างไปยังวิธีการควบคุม ชื่อนั้นไม่สำคัญ จะสามารถเรียกว่าหรือเพียงได้อย่างง่ายดายfoo fireZeMissilesจะรู้เพียงว่าควรรายงานไปยังฟังก์ชันเฉพาะ ไม่รู้ว่าฟังก์ชั่นทำอะไรที่จะเรียกมัน ตัวควบคุมไม่ได้เกี่ยวข้องกับวิธีการที่เรียกใช้เพียงแค่ว่ามันจะตอบสนองในลักษณะที่แน่นอนเมื่อพวกเขาเป็น
Schleis

1

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

นั่นคือทั้งหมดที่ดีและดี แต่การออกห่างจากสถาบันการศึกษาเล็กน้อย ตัวควบคุมใน MVC โดยทั่วไปประกอบด้วยวิธีการกระทำที่เล็กกว่าหลายอย่าง โดยทั่วไปการกระทำเหล่านี้สอดคล้องกับสิ่งที่สามารถทำได้ หากฉันขายสินค้าฉันอาจมี ProductController ตัวควบคุมนั้นจะมีการดำเนินการเช่น GetReviews, ShowSpecs, AddToCart และ ...

มุมมองมี SRP ในการแสดง UI และส่วนหนึ่งของ UI นั้นมีปุ่มที่ระบุว่า AddToCart

คอนโทรลเลอร์มี SRP ในการทราบมุมมองและโมเดลทั้งหมดที่เกี่ยวข้องในกระบวนการ

ตัวควบคุม AddToCart Action มี SRP เฉพาะที่รู้ว่าทุกคนต้องมีส่วนร่วมเมื่อมีการเพิ่มรายการลงในรถเข็น

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

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


0

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

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

source: wikipedia

ถ้าคุณมี "controllers" ในสไตล์ Rails (ซึ่งเล่นปาหี่อินสแตนซ์บันทึกที่ใช้งานอยู่และเทมเพลตโง่)แน่นอนว่าหลักสูตรนั้นกำลังทำลาย SRP

จากนั้นอีกครั้งแอปพลิเคชันสไตล์ Rails ไม่ใช่ MVC จริงๆที่จะเริ่มต้นด้วย

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