การเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์: มันคุ้มค่าเมื่อใด


19

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

ฉันกำลังพัฒนาแอปพลิเคชันขนาดเล็ก มันเป็นแอพที่ใช้งานง่ายและส่วนใหญ่ฟังก์ชั่นของมันคือ CRUD ขั้นพื้นฐาน

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

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

อะไรคือวิธีที่ดีที่สุดในกรณีนี้


2
ฉันจะบอกว่าอย่าใช้อะไรเลยเพียงแค่ใช้ Event Bus ที่มีอยู่แล้ว ซึ่งจะทำให้ชีวิตที่เรียบง่ายมาก ...
บอริสไปเดอร์

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

โดยทั่วไปแล้วเป็นเหตุการณ์ที่เกิดขึ้นหมายความว่ารหัสของคุณมีให้ในรูปแบบการโทรกลับ คำอธิบายของคุณฟังดูเหมือนว่าเมื่อมีบางอย่างเกิดขึ้นในรหัสของคุณคุณต้องทำมากกว่าการนำไปปฏิบัติที่ไร้เดียงสา เพียงรหัสการโทรพิเศษเข้ามา
Thorbjørn Ravn Andersen

มีคือความแตกต่างระหว่างเหตุการณ์ที่ขับเคลื่อนด้วยและเหตุการณ์ตาม ดูตัวอย่างพอดคาสต์. NET Rocks ที่สร้างความเร้าใจสูงตอน 355กับ Ted Faison, Ted Faison นำเหตุการณ์มาสู่จุดสูงสุด! ( โดยตรงดาวน์โหลด URL ) และหนังสือการเขียนโปรแกรมที่จัดกิจกรรมตาม: การเหตุการณ์ที่เกิดขึ้นถึงขีด จำกัด
Peter Mortensen

การสัมภาษณ์กับ Ted Faison เริ่มต้นที่ 13 นาที 10 วินาที
Peter Mortensen

คำตอบ:


31

ทำตามหลักการจูบ: รักษามันง่าย ๆ โง่ ๆ หรือหลักการ YAGNI: คุณไม่ต้องการมัน

คุณสามารถเขียนรหัสเช่น:

void updateSpecialData() {
    // do the update.
    backupData();
}

หรือคุณสามารถเขียนรหัสเช่น:

void updateSpecialData() {
     // do the update.
     emit SpecialDataUpdated();
}

void SpecialDataUpdatedHandler() {
     backupData();
}

void configureEventHandlers() {
     connect(SpecialDataUpdate, SpecialDataUpdatedHandler);
}

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

เหตุการณ์มีความสำคัญมากในสถานการณ์ที่ถูกต้อง (ลองจินตนาการถึงการทำโปรแกรม UI โดยไม่มีเหตุการณ์!) แต่อย่าใช้เมื่อคุณสามารถ KISS หรือ YAGNI แทน


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

13

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

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

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


1
ฉันสับสนไม่ใช่ผู้สังเกตการณ์เพียงวิธีเดียวที่จะนำเหตุการณ์มาใช้ใช่หรือไม่
svick

1
@svick ฉันไม่คิดอย่างนั้น ในการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์คุณมีลูปหลักที่ประมวลผลเหตุการณ์ในหลาย ๆ ความสัมพันธ์กับผู้ส่งและผู้สังเกตการณ์ที่แยกจากกัน ฉันคิดว่าผู้สังเกตการณ์สามารถมีส่วนร่วมได้โดยการประมวลผลเหตุการณ์ perticular แต่คุณไม่สามารถบรรลุ EDP ที่สมบูรณ์ได้ด้วยผู้สังเกตการณ์เท่านั้น ฉันคิดว่าความสับสนนั้นมาจากความจริงที่ว่าในซอฟต์แวร์ที่ขับเคลื่อนด้วยเหตุการณ์บางครั้งผู้สังเกตการณ์จะถูกนำไปใช้ด้านบนของการประมวลผลเหตุการณ์ (โดยทั่วไปคือ MVC กับ GUI)
Christophe

5

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

เหตุการณ์เป็นหนึ่งในเครื่องมือที่สำคัญที่สุดใน OO (อ้างอิงจาก Alan Kay - วัตถุสื่อสารโดยการส่งและรับข้อความ ) หากคุณใช้ภาษาที่มีการรองรับเหตุการณ์ในตัวหรือปฏิบัติหน้าที่ในฐานะพลเมืองชั้นหนึ่งการใช้ภาษานั้นเป็นเรื่องง่าย

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

คุ้มค่าในแอปพลิเคชั่นขนาดเล็กหรือไม่? ผมจะบอกว่าแน่นอนใช่

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

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

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

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

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


2

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

โค้ดผลลัพธ์จะมีความซับซ้อนน้อยกว่าการโทรหาโค้ดที่สร้างไฟล์โดยตรงเท่านั้น

นั่นจะทำให้คุณ "ดีที่สุดของทั้งสองโลก" โดยไม่ต้องเสียสละ decoupling สำหรับ "YAGNI"


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

2
@RubberDuck: OP กำลังมองหาวิธีการแก้ปัญหา "หลีกเลี่ยงความซับซ้อนที่ไม่จำเป็น" - หากมีเหตุการณ์ที่แตกต่าง / พฤติกรรมที่แตกต่างกันที่จำเป็นเขาอาจจะไม่พิจารณารูปแบบการสังเกตการณ์ที่ซับซ้อนเกินไป ดังนั้นฉันจึงเห็นด้วยกับสิ่งที่คุณพูดเมื่อสิ่งต่าง ๆ มีความซับซ้อนมากขึ้นผู้สังเกตการณ์เต็มรูปแบบจะรับใช้เขาได้ดีขึ้น แต่หลังจากนั้นเท่านั้น
Doc Brown

คำแถลงการณ์ที่เป็นธรรม แต่มันให้ความรู้สึกเหมือนหน้าต่างที่แตกสำหรับฉัน
RubberDuck

2
@RubberDuck: เพิ่มผู้สังเกตการณ์แบบเต็มโดยมีกลไกการเผยแพร่ / สมาชิก "ในกรณี" เมื่อมันไม่จำเป็นจริงๆรู้สึกว่าฉันชอบ overengineering - ที่ไม่ดีกว่า
Doc Brown

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