इवेंट-ड्रिव्ड प्रोग्रामिंग के लिए फ्रेमवर्क

कुछ गीत


छवि
जितना अधिक समय तक मैं कार्यक्रम करता हूं, मुझे उतना अधिक पसंद नहीं आता है कि बड़ी संख्या में असमान इकाइयों (मॉड्यूल) से युक्त सिस्टम एक दूसरे के बारे में कुछ भी नहीं जानते हैं, लेकिन दूसरों के अस्तित्व को मानते हैं। ऐसी प्रणालियों को आदर्श रूप से एक निर्माता की तरह इकट्ठा किया जाना चाहिए, बिना निर्भरता के और एक-दूसरे के अनुकूल होने के बिना। आदर्श रूप से, ऐसी प्रणाली के संचालन के दौरान, सभी आवश्यक कार्यों को सिस्टम को रोकने के बिना किया जाना चाहिए, बस एक नया मॉड्यूल दुनिया में पेश करना (जैसे, क्लासपाथ में जार फेंककर), और सिस्टम तुरंत नए मॉड्यूल के साथ बातचीत करना शुरू कर देगा।
इस संबंध में, ईवेंट-चालित (या इवेंट-ओरिएंटेड ) प्रोग्रामिंग प्रतिमान बहुत आकर्षक लगता है।

इसका निर्विवाद लाभ यह है कि कुछ मॉड्यूल को जोड़ने या हटाते समय मौजूदा कोड को संपादित करना आवश्यक नहीं है, सिस्टम गैर-स्टॉप काम करना जारी रखेगा, हम बस कुछ कार्यक्षमता प्राप्त करेंगे या खो देंगे।
यह ध्यान दिया जाना चाहिए कि माइनस इसका प्लस है - परिवर्तनों के दौरान व्यवहार की अप्रत्याशितता, साथ ही साथ प्रणाली पर नियंत्रण को कमजोर करना (जो दोष देना है, यह गलत काम क्यों करता है?) - पारंपरिक प्रोग्रामिंग के विपरीत, जहां सिस्टम ऐसी विसंगतियों का तुरंत और कठोर रूप से जवाब देता है। कारणों का संकेत (वांछित मॉड्यूल नहीं मिला)।

क्यों बदल जाती है परंपराएं


कुछ हद तक, घटना-उन्मुख प्रोग्रामिंग हर जगह दिखाई देती है - आधुनिक मल्टी-टास्किंग ओएस से लेकर सभी प्रकार के चौखटे। प्रत्येक भागीदार (मॉड्यूल) को सभी घटनाओं को सुनने के बजाय, वह केवल उन लोगों की सदस्यता लेता है जो उसे रुचि रखते हैं, जिससे कम मशीन संसाधनों का उपभोग होता है। वास्तव में, यहां तक ​​कि किसी वस्तु पर विधि कॉल को भी डिलीवरी और रसीद की गारंटी के साथ संदेश भेजने और प्राप्त करने के रूप में माना जा सकता है। तो अतिरिक्त जटिलता क्यों?
इसका उत्तर यह है कि सब कुछ किसी भी परिणाम के लिए काम करता है। हमें इस बात में कोई दिलचस्पी नहीं है कि हमारे संदेश को किसने प्राप्त किया, कितने प्राप्तकर्ताओं को प्राप्त किया, और क्या वह इसका जवाब देगा। हम बस धीरे से सूचित करते हैं। कोई भी दिलचस्पी नहीं है - और ठीक है। दिलचस्प - यह बहुत अच्छा है, हम काम कर रहे हैं।

वास्तविकताओं


उदाहरण के लिए, जावा स्विंग में समान घटना प्रणाली। कुछ घटनाओं को सुनने के लिए, प्रत्येक घटक के लिए addXXXListener, removeXXXListener विधियाँ हैं। इस तरह के संदेश भेजने के लिए फायरएक्सएक्सएक्सईवेंट विधि है। सब कुछ सही है। लेकिन, मान लीजिए, हम अपने घटक को एक ही दर्शन में लिखते हैं। और हम विभिन्न घटनाओं को भेजना चाहते हैं या उनका जवाब देना चाहते हैं, जबकि इनकैप्सुलेशन बनाए रखते हैं। इसलिए, हर बार प्रत्येक घटक के लिए प्रत्येक XXX घटना के लिए इन तरीकों को लागू करना आवश्यक है ...

निर्णय


कोड हमेशा घृणा के समान होता है, इसलिए मैं इसे कुछ पंक्तियों के साथ बदलना चाहूंगा। मैंने सोचा, और परिणामस्वरूप, एक दिन से अधिक मैंने ऐसे कार्यों के लिए एक सहायक को लागू किया। यह स्टैटिक विधियों वाला एक वर्ग होगा जिसे कार्यक्रम में कहीं से भी बुलाया जा सकता है। तो हमें क्या चाहिए?
सबसे पहले, हम किसी भी घटना पर प्रतिक्रिया देना चाहते हैं। चलो, निश्चितता के लिए, हमारी घटनाएं मानक java.util.EventObject इंटरफ़ेस को लागू करेंगी, और श्रोता java.util.EventListener इंटरफ़ेस को लागू करेंगे। इसलिए, एक तरफ, हम किसी भी चीज़ तक सीमित नहीं हैं, और दूसरी तरफ, इसे AWT \ Swing घटना के प्रतिमान के साथ जोड़ना जितना संभव हो उतना सरल है। तो, शायद, एक घटना सदस्यता इस तरह दिखना चाहिए:
public static synchronized void listen(final Class<? extends EventObject> event, final EventListener listener); 

एक भोली कार्यान्वयन इस तरह दिखता है:
  if (!Events.listeners.containsKey(event)) { Events.listeners.put(event, new ArrayList<EventListener>()); } @SuppressWarnings("unchecked") final List<EventListener> list = (List<EventListener>) Events.listeners.get(event); if (!list.contains(listener)) { list.add(listener); } 


इसलिए हमने EventObject के कुछ उपवर्गों की घटनाओं के बराबर रहने की इच्छा व्यक्त की, यह वादा करते हुए कि हम EventListener इंटरफ़ेस को लागू करेंगे (यह विधियों को परिभाषित नहीं करता है, उस पर बाद में और अधिक)। लेकिन एक निश्चित घटना होने पर हमें निश्चित रूप से सूचित किया जाएगा।

इसके अलावा, एक साफ निकास के लिए, हमें एक घटना से सदस्यता समाप्त करने की क्षमता की आवश्यकता है, उपयुक्त
 public static synchronized void forget(final Class<? extends EventObject> event, final EventListener listener); 

और तुच्छ कोड:
  public static synchronized void forget(final Class<? extends EventObject> event, final EventListener listener) { if (Events.listeners.containsKey(event)) { Events.listeners.get(event).remove(listener); } } 


और एक बहुत साफ निकास के लिए (हालांकि यहां एक विवादास्पद बिंदु है):
 public static synchronized <E extends EventObject> void forget(final Class<? extends EventObject> event); 

और प्राकृतिक कोड:
  public static synchronized <E extends EventObject> void forget(final Class<? extends EventObject> event) { if (Events.listeners.containsKey(event)) { Events.listeners.get(event).clear(); Events.listeners.remove(event); } } 


बाहर निकलने पर अच्छा लगा। लगता तो सब है। और, नहीं, सभी नहीं - आपको अभी भी कुछ घटनाओं के बारे में कई स्थानों पर एक-पंक्ति को सूचित करने की आवश्यकता है, यह इस तरह दिखता है:
 public static synchronized void fire(final EventObject event, final String method); 

कोड एकल-पिरोया गया:
  public static synchronized void fire(final EventObject event, final String method) { final Class<? extends EventObject> eventClass = event.getClass(); if (Events.listeners.containsKey(eventClass)) { for (final EventListener listener : Events.listeners.get(eventClass)) { try { listener.getClass().getMethod(method, eventClass).invoke(listener, event); } catch (final Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } } } } 

कार्यान्वयन का नुकसान, ज़ाहिर है, प्रसंस्करण क्रमिक रूप से किया जाता है, और कॉलिंग थ्रेड में। अधिकांश अनुप्रयोगों के लिए, यह दृष्टिकोण पर्याप्त होना चाहिए, लेकिन, जाहिरा तौर पर, यह AWTshny EventQueue या कुछ इसी तरह के माध्यम से प्रसंस्करण के आयोजन के लायक है। भविष्य के संस्करणों में मैं इसे ठीक कर दूंगा।
एक और दोष यह है कि हैंडलर में एक अपवाद को फेंकना विधि कॉल में अपवाद को फेंक देता है, और श्रोताओं को सूचित करना बंद कर देता है (एक "बेईमान" श्रोता के कारण, कुछ अन्य लोग संदेश प्राप्त नहीं कर सकते हैं)। अंतिम संस्करण में, मैंने इस व्यवहार को मानक लॉगिंग के साथ अनदेखा करने के लिए निर्धारित किया, और हैंडलर्स में अपवाद वाली घटनाओं के लिए वैकल्पिक रूप से सदस्यता ली।

अब, तरीकों को लागू करने के बजाय, सब कुछ डेमो की तरह लग सकता है (उदाहरण के लिए, मैंने java.awt.event.ActionEvent चुना:)
  final ActionListener listener = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { System.out.println(e); } }; Events.listen(ActionEvent.class, listener);//   // final ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "command"); Events.fire(event, "actionPerformed");// ActionListener.actionPerformed(ActionEvent) // Events.forget(ActionEvent.class); //  

केवल असुविधा यह है कि Event.fire विधि में स्ट्रिंग के रूप में विधि का नाम निर्दिष्ट करना आवश्यक है, ताकि यह आवश्यक रूप से एक तर्क ले - हमारी घटना का उद्देश्य। ऐसा इसलिए हुआ क्योंकि विभिन्न श्रोताओं ने संदेशों की प्रतिक्रिया के विभिन्न तरीकों को लागू किया है, और यहां तक ​​कि एक श्रोता के पास इन विधियों में से कई हो सकते हैं, घटना के प्रकार के अनुसार (ऐसा लगता है जैसे माउसलिस्टनर कई तरीकों को परिभाषित करता है, उदाहरण के लिए, माउसओवर और माउसओट, साथ ही साथ अन्य)।
खैर, निष्कर्ष में, अभी भी पश्चाताप है: सभी विधियां सांख्यिकीय रूप से सिंक्रनाइज़ हैं, थ्रेड-सुरक्षित वाले नियमित संग्रह को बदलना आवश्यक है। काम और भी धीमा हो जाता है (नैनोसेकंड के संदर्भ में, एक प्रत्यक्ष विधि कॉल के साथ तुलना में) प्रतिबिंब - जो एक चक्र में होता है जब प्रत्येक श्रोता के लिए एक घटना को उठाया जाता है, लेकिन मुझे लगता है कि यह एक आवश्यक बुराई है।

कुटिल, असुविधाजनक और अनावश्यक


मुझे स्वयं यह दृष्टिकोण इतना पसंद आया कि मैंने पुस्तकालय को समुदाय के साथ साझा करने का निर्णय लिया ( https://github.com/lure0xaos/Events.git )। यदि आप इसे पसंद नहीं करते हैं, तो सहमति दें, लेकिन सामान्य तौर पर मुझे टिप्पणियों और समझदार सुझावों और चर्चाओं में रचनात्मक आलोचना करने में खुशी होगी।

Source: https://habr.com/ru/post/In198024/


All Articles