पाइप और फिल्टर। उदाहरण आवेदन और वसंत का उपयोग कर कार्यान्वयन

यह आलेख पाइप और फिल्टर पैटर्न के उपयोग पर चर्चा करेगा।


सबसे पहले, हम एक फ़ंक्शन के उदाहरण का विश्लेषण करेंगे, जिसे हम उपर्युक्त पैटर्न का उपयोग करके बाद में फिर से लिखेंगे। कोड में परिवर्तन धीरे-धीरे होगा और हर बार हम एक व्यावहारिक संस्करण बनाएंगे जब तक कि हम डीआई (इस स्प्रिंग उदाहरण में) का उपयोग करके समाधान पर ध्यान नहीं देते।


इस प्रकार, हम कई समाधान बनाएंगे, किसी भी उपयोग करने का अवसर प्रदान करेंगे।
अंत में, हम प्रारंभिक और अंतिम कार्यान्वयन की तुलना करते हैं, वास्तविक परियोजनाओं में आवेदन के उदाहरणों को देखते हैं और संक्षेप में प्रस्तुत करते हैं।


कार्य


मान लीजिए हमारे पास कपड़े का एक गुच्छा है जो हमें सूखने से मिलता है और जिसे अब हमें कोठरी में ले जाने की आवश्यकता है। यह पता चलता है कि डेटा (कपड़े) एक अलग सेवा से आते हैं और कार्य ग्राहक को यह डेटा सही रूप में प्रदान करना है (एक कोठरी जिसमें वह कपड़े प्राप्त कर सकता है)।


ज्यादातर मामलों में, आप प्राप्त डेटा का उपयोग उस रूप में नहीं कर सकते हैं जिस रूप में यह हमारे पास आता है। इस डेटा को जांचना, बदलना, क्रमबद्ध करना, आदि की आवश्यकता है।
मान लीजिए कि एक ग्राहक मांग करता है कि कपड़े टकसाल होने पर इस्त्री किए जाने चाहिए।


फिर पहली बार हम एक Modifier बनाते हैं, जिसमें हम परिवर्तनों को लिखते हैं:


  public class Modifier { public List<> modify(List<> ){ (); return ; } private void (List<> ) { .stream() .filter(::) .forEach(o -> { // }); } } 

इस स्तर पर, सब कुछ सरल और स्पष्ट है। आइए एक परीक्षण लिखें जो यह जांचता है कि सभी झुर्रियों वाले कपड़े इस्त्री किए गए हैं।


लेकिन समय के साथ, नई आवश्यकताएं दिखाई देती हैं और हर बार Modifier वर्ग की कार्यक्षमता बढ़ती है:


  • कोठरी में गंदे कपड़े न डालें।
  • शर्ट, जैकेट और पतलून कंधों पर लटकाए जाने चाहिए।
  • रिसावयुक्त मोजे को पहले सिलने की आवश्यकता होती है
  • ...

परिवर्तनों का क्रम भी महत्वपूर्ण है। उदाहरण के लिए, आप पहले उनके कंधों पर कपड़े लटका नहीं सकते हैं, और फिर लोहे।


इस प्रकार, कुछ बिंदु पर, Modifier निम्नलिखित रूप ले सकता है:


 public class Modifier { private static final Predicate<> ___ = ((Predicate<>).class::isInstance) .or(.class::isInstance) .or(.class::isInstance) ; public List<> modify(List<> ){ (); (); (); (); //   return ; } private void (List<> ) { .stream() .filter(.class::isInstance) .map(.class::cast) .filter(::) .forEach(o -> { // }); } private void (List<> ) { .stream() .filter(___) .forEach(o -> { //   }); } private void (List<> ) { .removeIf(::); } private void (List<> ) { .stream() .filter(::) .forEach(o -> { // }); } //  } 

इसके विपरीत, परीक्षण अधिक जटिल हो गए हैं, जो अब प्रत्येक चरण को कम से कम व्यक्तिगत रूप से जांचना चाहिए।


और जब एक नई आवश्यकता आती है, तो कोड को देखते हुए, हम तय करते हैं कि रिफैक्टरिंग का समय आ गया है।


पुनर्रचना


पहली चीज जो आपकी आंख को पकड़ती है, वह है सभी कपड़ों की लगातार हलचल। तो पहला कदम, हम सब कुछ एक चक्र में स्थानांतरित करते हैं, और सफाई की जांच को चक्र की शुरुआत में भी स्थानांतरित करते हैं:


 public class Modifier { private static final Predicate<> ___ = ((Predicate<>).class::isInstance) .or(.class::isInstance) .or(.class::isInstance) ; public List<> modify(List<> ){ List<> result = new ArrayList<>(); for(var o : ){ if(o.()){ continue; } result.add(o); (o); (o); (o); //   } return result; } private void ( ) { if( instanceof ){ // ()  } } private void ( ) { if(___.test()){ //   } } private void ( ) { if(.()){ // } } //  } 

अब, कपड़े के लिए प्रसंस्करण समय कम हो गया है, लेकिन एक वर्ग के लिए और चक्र के शरीर के लिए कोड अभी भी बहुत लंबा है। आइए पहले चक्र के शरीर को छोटा करने का प्रयास करें।


  • सफाई के लिए जाँच करने के बाद, आप सभी कॉल को एक अलग तरीके से modify( ) कर सकते हैं modify( ) :


     public List<> modify(List<> ){ List<> result = new ArrayList<>(); for(var o : ){ if(o.()){ continue; } result.add(o); modify(o); } return result; } private void modify( o) { (o); (o); (o); //   } 

  • आप सभी कॉल को एक Consumer में संयोजित कर सकते हैं:


     private Consumer<> modification = ((Consumer<>) this::) .andThen(this::) .andThen(this::); //   public List<> modify(List<> ){ return .stream() .filter(o -> !o.()) .peek(modification) .collect(Collectors.toList()); } 

    कुंद: झांकना
    मैंने शॉर्ट के लिए झांकना शुरू किया। सोनार कहेगा कि ऐसा कोड नहीं होना चाहिए, क्योंकि जावदोक झांकना बताता है कि विधि मुख्य रूप से डिबग के लिए मौजूद है। लेकिन अगर आप इसे मानचित्र पर फिर से लिखते हैं: .map (o -> {modification.accept (o); वापसी ओ;}), तो IDEA कहेंगे कि यह स्ट्रीमिंग का उपयोग करने के लिए बेहतर है।


ठोकर: उपभोक्ता
उपभोक्ता के साथ एक उदाहरण (और बाद में फ़ंक्शन के साथ) भाषा की क्षमताओं को दिखाने के लिए दिया जाता है।

अब चक्र का शरीर छोटा हो गया है, लेकिन अभी तक यह वर्ग अभी भी बहुत बड़ा है और इसमें बहुत अधिक जानकारी (सभी चरणों का ज्ञान) है।


आइए पहले से ही स्थापित प्रोग्रामिंग पैटर्न का उपयोग करके इस समस्या को हल करने का प्रयास करें। इस मामले में, हम Pipes & Filters उपयोग करेंगे।


पाइप और फिल्टर


चैनल और फिल्टर टेम्पलेट एक दृष्टिकोण का वर्णन करता है जिसमें आने वाला डेटा कई प्रसंस्करण चरणों से गुजरता है।


आइए इस दृष्टिकोण को हमारे कोड पर लागू करने का प्रयास करें।


चरण 1


वास्तव में, हमारा कोड पहले से ही इस पैटर्न के करीब है। प्राप्त डेटा कई स्वतंत्र चरणों से गुजरता है। अब तक, प्रत्येक विधि एक फिल्टर है, और modify करके चैनल का वर्णन करता है, पहले सभी गंदे कपड़े को छानता है।


अब हम प्रत्येक चरण को एक अलग वर्ग में स्थानांतरित करेंगे और देखेंगे कि हमें क्या मिलता है:


 public class Modifier { private final  ; private final  ; private final  ; //  public Modifier( ,  ,   //  ) { this. = ; this. = ; this. = ; //  } public List<> modify(List<> ) { return .stream() .filter(o -> !o.()) .peek(o -> { .(o); .(o); .(o); //  }) .collect(Collectors.toList()); } } 

इस प्रकार, हमने अलग-अलग कक्षाओं में कोड रखा, व्यक्तिगत परिवर्तनों के लिए परीक्षणों को सरल बनाया (और चरणों का पुन: उपयोग करने की संभावना पैदा की)। कॉल का क्रम चरणों का क्रम निर्धारित करता है।


लेकिन वर्ग अभी भी सभी व्यक्तिगत चरणों को जानता है, आदेश को नियंत्रित करता है और इस प्रकार निर्भरता की एक विशाल सूची है। एक नया कदम जोड़ने के अलावा, हमें न केवल एक नया वर्ग लिखने के लिए बाध्य किया जाएगा, बल्कि इसे Modfier भी जोड़ा Modfier


चरण 2


स्प्रिंग का उपयोग करके कोड को सरल बनाएं।
सबसे पहले, प्रत्येक व्यक्तिगत चरण के लिए एक इंटरफ़ेस बनाएँ:


 interface Modification { void modify( ); } 

Modifier अब बहुत छोटा होगा:


 public class Modifier { private final List<Modification> steps; @Autowired public Modifier(List<Modification> steps) { this.steps = steps; } public List<> modify(List<> ) { return .stream() .filter(o -> !o.()) .peek(o -> { steps.forEach(m -> m.modify(o)); }) .collect(Collectors.toList()); } } 

अब, एक नया कदम जोड़ने के लिए, आपको बस एक नया वर्ग लिखना होगा जो Modification इंटरफ़ेस को लागू करता है और इसके ऊपर @Component डाल @Component है। वसंत इसे ढूंढकर सूची में जोड़ देगा।


Modifer ही व्यक्तिगत चरणों के बारे में कुछ नहीं जानता है, जो घटकों के बीच एक "कमजोर संबंध" बनाता है।


अनुक्रम सेट करने के लिए केवल कठिनाई है। ऐसा करने के लिए, स्प्रिंग के पास @Order एनोटेशन है जिसमें आप एक इंट वैल्यू पास कर सकते हैं। सूची को आरोही क्रम में क्रमबद्ध किया गया है।
इस प्रकार, यह हो सकता है कि सूची के बीच में एक नया कदम जोड़कर, आपको मौजूदा चरणों के लिए छंटाई के मूल्यों को बदलना होगा।


यदि सभी ज्ञात कार्यान्वयन को मैन्युअल रूप से संशोधक निर्माता को पास कर दिया गया था, तो वसंत को तिरस्कृत किया जा सकता था। यह सॉर्टिंग समस्या को हल करने में मदद करेगा, लेकिन फिर से नए कदमों को जोड़ देगा।

चरण 3


अब हम एक अलग चरण में स्वच्छता के लिए परीक्षा पास करते हैं। ऐसा करने के लिए, हम अपने इंटरफ़ेस को फिर से लिखते हैं ताकि यह हमेशा एक मान लौटाए:


 interface Modification {  modify( ); } 

स्वच्छता के लिए जाँच करें:


 @Component @Order(Ordered.HIGHEST_PRECEDENCE) class CleanFilter implements Modification {  modify( ) { if(.()){ return null; } return ; } } 

Modifier.modify


  public List<> modify(List<> ) { return .stream() .map(o -> { var modified = o; for(var step : steps){ modified = step.modify(o); if(modified == null){ return null; } } return modified; }) .filter(Objects::nonNull) .collect(Collectors.toList()); } 

इस संस्करण में, Modifier पास कोई डेटा जानकारी नहीं है। वह बस उन्हें हर ज्ञात कदम पर पास करता है और परिणाम एकत्र करता है।


यदि कोई एक कदम शून्य हो जाता है, तो इस परिधान के लिए प्रसंस्करण बाधित हो जाता है।


इसी तरह के सिद्धांत का उपयोग वसंत में हैंडलरइंटरसेप्टर्स के लिए किया जाता है। नियंत्रक कॉल से पहले और बाद में, इस URL के सभी उपयुक्त इंटरसेप्टर को बुलाया जाता है। उसी समय, यह प्रीहैंडल विधि में सही या गलत रिटर्न देता है, यह इंगित करने के लिए कि क्या प्रसंस्करण और कॉलिंग बाद के इंटरसेप्टर जारी रख सकते हैं


चरण n


अगला चरण matches इंटरफ़ेस में matches विधि को जोड़ना है, जिसमें कपड़ों की एक अलग विशेषता के चरणों की जाँच की जाती है:


 interface Modification {  modify( ); default matches( ) {return true;} } 

इसके कारण, आप वर्गों और संपत्तियों की जांच को एक अलग विधि में modify तरीकों को modify में तर्क को थोड़ा सरल कर सकते हैं।


स्प्रिंग (रिक्वेस्ट) फ़िल्टर में एक समान दृष्टिकोण का उपयोग किया जाता है, लेकिन मुख्य अंतर यह है कि प्रत्येक फ़िल्टर अगले के आसपास एक आवरण होता है और प्रसंस्करण जारी रखने के लिए फ़िल्टरचैन.डोफ़िल्टर को स्पष्ट रूप से कॉल करता है।


कुल मिलाकर


अंतिम परिणाम प्रारंभिक संस्करण से बहुत अलग है। उनकी तुलना करते हुए, हम निम्नलिखित निष्कर्ष निकाल सकते हैं:


  • पाइप्स एंड फिल्टर्स पर आधारित कार्यान्वयन, Modifier वर्ग को सरल Modifier है।
  • बेहतर वितरित जिम्मेदारियों और घटकों के बीच "कमजोर" कनेक्शन।
  • अलग-अलग चरणों का परीक्षण करने के लिए आसान।
  • चरणों को जोड़ने और हटाने में आसान।
  • फ़िल्टर की एक पूरी श्रृंखला का परीक्षण करने के लिए थोड़ा कठिन। हमें पहले से ही इंटीग्रेशनटेस्ट की जरूरत है।
  • अधिक कक्षाएं

अंततः, मूल की तुलना में अधिक सुविधाजनक और लचीला विकल्प।


इसके अलावा, आप समान समरूपता का उपयोग करके केवल डेटा प्रोसेसिंग को समानांतर कर सकते हैं।


यह उदाहरण क्या हल नहीं करता है


  1. पैटर्न का वर्णन कहता है कि एक और फ़िल्टर श्रृंखला (चैनल) बनाकर व्यक्तिगत फ़िल्टर का पुन: उपयोग किया जा सकता है।
    • एक ओर, यह @Qualifier का उपयोग करना आसान है।
    • दूसरी ओर, @Order आदेश के साथ एक अलग क्रम @Order करना विफल हो जाएगा।
  2. अधिक जटिल उदाहरणों के लिए, आपको कई चेन का उपयोग करना होगा, नेस्टेड चेन का उपयोग करना होगा, और फिर भी मौजूदा कार्यान्वयन को बदलना होगा।
    • इसलिए उदाहरण के लिए, कार्य: "प्रत्येक जुर्राब के लिए, एक जोड़ी की तलाश करें और उन्हें एक उदाहरण में डालें <? फैली हुई वस्त्र>" वर्णित कार्यान्वयन में अच्छी तरह से फिट नहीं होगा, क्योंकि? अब, प्रत्येक पैर की अंगुली के लिए, आपको सभी लिनन के माध्यम से सॉर्ट करना होगा और प्रारंभिक डेटा सूची को बदलना होगा।
    • इसे हल करने के लिए, आप एक नया इंटरफ़ेस लिख सकते हैं जो एक सूची <वस्त्र> को स्वीकार और वापस करता है और एक नई श्रृंखला में स्थानांतरित करता है। लेकिन आपको खुद चेन के कॉल के अनुक्रम से सावधान रहने की जरूरत है, अगर मोजे केवल होटल द्वारा ही सीवन किए जा सकते हैं।

आपका ध्यान के लिए धन्यवाद।

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


All Articles