स्वच्छ वास्तुकला: प्रस्तुतकर्ता या रिटर्निंग डेटा वाले मामले का उपयोग करें?


42

स्वच्छ वास्तुकला एक उपयोग के मामले प्रस्तोता (जो इंजेक्ट किया जाता है DIP निम्नलिखित,) के वास्तविक क्रियान्वयन फोन interactor प्रतिक्रिया / प्रदर्शन को संभालने के लिए जाने के लिए पता चलता है। हालाँकि, मैं देख रहा हूँ कि लोग इस आर्किटेक्चर को कार्यान्वित कर रहे हैं, इंटरेक्टर से आउटपुट डेटा लौटा रहे हैं, और फिर कंट्रोलर (एडॉप्टर लेयर) को यह तय करने दें कि इसे कैसे संभालना है। दूसरा समाधान अनुप्रयोग परत से अनुप्रयोग जिम्मेदारियों को लीक करना है, इसके अलावा अंतःक्रियात्मक को इनपुट और आउटपुट पोर्ट को स्पष्ट रूप से परिभाषित नहीं करने के अलावा?

इनपुट और आउटपुट पोर्ट

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

हेक्सागोनल वास्तुकला की तरह स्वच्छ वास्तुकला, प्राथमिक बंदरगाहों (तरीकों) और माध्यमिक बंदरगाहों (एडेप्टर द्वारा लागू किए जाने वाले इंटरफेस) के बीच अंतर करता है। संचार प्रवाह के बाद, मुझे उम्मीद है कि "केस केस इनपुट पोर्ट का उपयोग करें" एक प्राथमिक पोर्ट (इस प्रकार, बस एक विधि), और "केस केस आउटपुट पोर्ट का उपयोग करें" को लागू करने के लिए एक इंटरफ़ेस, शायद एक वास्तविक निर्माता को लेने वाला तर्क है, ताकि इंटरएक्टर इसका इस्तेमाल कर सके।

कोड उदाहरण

एक कोड उदाहरण बनाने के लिए, यह नियंत्रक कोड हो सकता है:

Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();

प्रस्तुतकर्ता इंटरफ़ेस:

// Use Case Output Port
interface Presenter
{
    public void present(Data data);
}

अंत में, अपने आप से बातचीत करने वाला:

class UseCase
{
    private Repository repository;
    private Presenter presenter;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.repository = repository;
        this.presenter = presenter;
    }

    // Use Case Input Port
    public void doSomething()
    {
        Data data = this.repository.getData();
        this.presenter.present(data);
    }
}

प्रस्तोता को फोन करने वाले पर

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

इसके अलावा, एक अन्य प्रश्न के उत्तर में , रॉबर्ट मार्टिन ने वास्तव में एक उपयोग के मामले का वर्णन किया है जहां इंटरैक्टर प्रस्तुतकर्ता को पढ़ने के अनुरोध पर कहता है:

मानचित्र पर क्लिक करने से या तो प्लेसपिनकंट्रोलर को आमंत्रित किया जाता है। यह क्लिक के स्थान को इकट्ठा करता है, और किसी भी अन्य प्रासंगिक डेटा को बनाता है, एक प्लेसप्रिनिस्टर डेटा संरचना का निर्माण करता है और इसे प्लेसपिनइंटरएक्टर के पास भेज देता है जो पिन के स्थान की जांच करता है, यदि आवश्यक हो, तो पिन रिकॉर्ड करने के लिए एक प्लेस इकाई बनाएं, एक EditPlaceReponse का निर्माण करता है ऑब्जेक्ट और इसे EditPlacePresenter में पास करता है जो स्थान संपादक स्क्रीन को लाता है।

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

इस लेयर का सॉफ्टवेयर एडेप्टरों का एक समूह है जो डेटा को उपयोग के मामलों और संस्थाओं के लिए सबसे सुविधाजनक प्रारूप से परिवर्तित करता है, किसी बाहरी एजेंसी जैसे डेटाबेस या वेब के लिए सबसे सुविधाजनक प्रारूप में।

मूल लेख से, इंटरफ़ेस एडेप्टर के बारे में बात करना।

इंटरेक्टर रिटर्निंग डेटा पर

हालांकि, इस दृष्टिकोण के साथ मेरी समस्या यह है कि उपयोग के मामले को प्रस्तुतिकरण का ध्यान रखना चाहिए। अब, मैं देख रहा हूं कि Presenterइंटरफ़ेस का उद्देश्य कई अलग-अलग प्रकार के प्रस्तुतकर्ता (GUI, Web, CLI, आदि) का प्रतिनिधित्व करने के लिए पर्याप्त सार है, और यह वास्तव में "आउटपुट" का अर्थ है, जो एक उपयोग मामला हो सकता है बहुत अच्छा है, लेकिन फिर भी मैं इसके साथ पूरी तरह से आश्वस्त नहीं हूं।

अब, साफ वास्तुकला के अनुप्रयोगों के लिए वेब के चारों ओर देख रहा हूं, मुझे लगता है कि लोग केवल कुछ डीटीओ को लौटाने वाली विधि के रूप में आउटपुट पोर्ट की व्याख्या कर रहे हैं। यह कुछ इस तरह होगा:

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);

// I'm omitting the changes to the classes, which are fairly obvious

यह आकर्षक है क्योंकि हम उपयोग के मामले में प्रस्तुति को "कॉल" करने की ज़िम्मेदारी से आगे बढ़ रहे हैं, इसलिए उपयोग मामला केवल डेटा प्रदान करने के साथ ही क्या करना है, यह जानने की चिंता नहीं करता है। इसके अलावा, इस मामले में हम अभी भी निर्भरता नियम नहीं तोड़ रहे हैं, क्योंकि उपयोग के मामले में अभी भी बाहरी परत के बारे में कुछ भी नहीं पता है।

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

मुद्दे पर

तो, इन दोनों विकल्पों में से कोई भी क्लीन आर्किटेक्चर के अनुसार उपयोग केस आउटपुट पोर्ट की "सही" व्याख्या है? क्या वे दोनों व्यवहार्य हैं?


3
क्रॉस-पोस्टिंग को दृढ़ता से हतोत्साहित किया जाता है। यदि यह वह जगह है जहाँ आप अपने प्रश्न को जीना चाहते हैं, तो आपको इसे स्टैक ओवरफ्लो से हटा देना चाहिए।
रॉबर्ट हार्वे

जवाबों:


48

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

यह निश्चित रूप से स्वच्छ , प्याज या हेक्सागोनल वास्तुकला नहीं है। यही कारण है इस :

यहाँ छवि विवरण दर्ज करें

ऐसा नहीं है कि MVC को इस तरह से किया जाना है

यहाँ छवि विवरण दर्ज करें

आप मॉड्यूल के बीच संचार करने और इसे MVC कहने के लिए कई अलग-अलग तरीकों का उपयोग कर सकते हैं । मुझे कुछ बताना एमवीसी का उपयोग करता है वास्तव में मुझे यह नहीं बताता कि घटक कैसे संवाद करते हैं। यह मानकीकृत नहीं है। यह सब मुझे बताता है कि उनकी तीन जिम्मेदारियों पर केंद्रित कम से कम तीन घटक हैं।

उन तरीकों में से कुछ को अलग-अलग नाम दिए गए हैं : यहाँ छवि विवरण दर्ज करें

और उनमें से हर एक को MVC कहा जा सकता है।

वैसे भी, उन लोगों में से कोई भी वास्तव में कब्जा नहीं करता है जो बज़बर्ड आर्किटेक्चर (क्लीन, ओनियन, और हेक्स) सभी आपको करने के लिए कह रहे हैं।

यहाँ छवि विवरण दर्ज करें

चारों ओर प्रवाहित की जा रही डेटा संरचनाएं जोड़ें (और किसी कारण से इसे उल्टा कर दें) और आप प्राप्त करें :

यहाँ छवि विवरण दर्ज करें

यहां एक बात जो स्पष्ट होनी चाहिए वह यह है कि रिस्पांस मॉडल कंट्रोलर के माध्यम से नहीं जाता है।

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

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

दूसरा समाधान अनुप्रयोग परत से अनुप्रयोग जिम्मेदारियों को लीक करना है, इसके अलावा अंतःक्रियात्मक को इनपुट और आउटपुट पोर्ट को स्पष्ट रूप से परिभाषित नहीं करने के अलावा?

चूँकि नियंत्रक से प्रस्तुतकर्ता तक संचार का अर्थ अनुप्रयोग "परत" से गुजरना है, तो हाँ नियंत्रक को प्रस्तुतकर्ता नौकरी का हिस्सा बनाने की संभावना एक रिसाव है। यह VIPER वास्तुकला की मेरी मुख्य आलोचना है ।

इन्हें अलग करना इतना महत्वपूर्ण क्यों है, यह शायद कमांड क्वेरी रिस्पॉन्सिबिलिटी सेग्रीगेशन का अध्ययन करके समझा जा सकता है ।

इनपुट और आउटपुट पोर्ट

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

यह एपीआई है कि आप इस विशेष उपयोग के मामले के माध्यम से आउटपुट भेजते हैं। यह इससे अधिक नहीं है। इस उपयोग के मामले के लिए इंटरेक्टर को जानने की आवश्यकता नहीं है, और न ही जानना चाहते हैं, यदि आउटपुट GUI, CLI, लॉग या ऑडियो स्पीकर के लिए जा रहा है। सभी इंटरैक्टर को जानने की जरूरत है कि यह सबसे सरल एपीआई संभव है जो इसे काम करने के परिणामों की रिपोर्ट करने देगा।

हेक्सागोनल वास्तुकला की तरह स्वच्छ वास्तुकला, प्राथमिक बंदरगाहों (तरीकों) और माध्यमिक बंदरगाहों (एडेप्टर द्वारा लागू किए जाने वाले इंटरफेस) के बीच अंतर करता है। संचार प्रवाह के बाद, मुझे उम्मीद है कि "केस केस इनपुट पोर्ट का उपयोग करें" एक प्राथमिक पोर्ट (इस प्रकार, बस एक विधि), और "केस केस आउटपुट पोर्ट का उपयोग करें" को लागू करने के लिए एक इंटरफ़ेस, शायद एक वास्तविक निर्माता को लेने वाला तर्क है, ताकि इंटरएक्टर इसका इस्तेमाल कर सके।

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

यह इनपुट पोर्ट के विपरीत है जो कि इसके सार के परत के स्वामित्व में है। केवल अनुप्रयोग परत लेखक को यह तय करना चाहिए कि क्या यह इनपुट पोर्ट बदलना चाहिए।

इन नियमों का पालन इस विचार को बनाए रखता है कि आवेदन की परत, या किसी भी आंतरिक परत, बाहरी परतों के बारे में कुछ भी नहीं जानता है।


प्रस्तोता को फोन करने वाले पर

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

उस "सफेद" तीर के बारे में महत्वपूर्ण बात यह है कि यह आपको ऐसा करने देता है:

यहाँ छवि विवरण दर्ज करें

आप नियंत्रण के प्रवाह को निर्भरता के विपरीत दिशा में जाने दे सकते हैं! इसका मतलब है कि आंतरिक परत को बाहरी परत के बारे में जानने की जरूरत नहीं है और फिर भी आप आंतरिक परत में गोता लगा सकते हैं और वापस बाहर आ सकते हैं!

"इंटरफ़ेस" कीवर्ड का उपयोग करने से कोई लेना-देना नहीं है। आप इसे अमूर्त वर्ग के साथ कर सकते हैं। हेक आप इसे एक (ick) ठोस वर्ग के साथ कर सकते हैं ताकि इसे बढ़ाया जा सके। यह केवल ऐसा कुछ करने के लिए अच्छा है जो केवल उस एपीआई को परिभाषित करने पर केंद्रित है जिसे प्रस्तुतकर्ता को लागू करना चाहिए। खुला तीर केवल बहुरूपता के लिए पूछ रहा है। आप पर किस तरह का प्रभाव है?

उस निर्भरता की दिशा को उलटना इतना महत्वपूर्ण क्यों है यह डिपेंडेंसी इनवर्जन सिद्धांत का अध्ययन करके सीखा जा सकता है । मैंने उस सिद्धांत को इन आरेखों पर यहाँ मैप किया ।

इंटरेक्टर रिटर्निंग डेटा पर

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

नहीं, यह वास्तव में है। यह सुनिश्चित करने की बात है कि बाहरी परतों के बारे में पता नहीं है कि हम बाहरी परतों को हटा सकते हैं, प्रतिस्थापित कर सकते हैं, या रिफ्लेक्टर कर सकते हैं। वे जो नहीं जानते हैं, वे उन्हें चोट नहीं पहुंचाएंगे। अगर हम ऐसा कर सकते हैं कि हम बाहरी लोगों को अपनी इच्छानुसार बदल सकते हैं।

अब, साफ वास्तुकला के अनुप्रयोगों के लिए वेब के चारों ओर देख रहा हूं, मुझे लगता है कि लोग केवल कुछ डीटीओ को लौटाने वाली विधि के रूप में आउटपुट पोर्ट की व्याख्या कर रहे हैं। यह कुछ इस तरह होगा:

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious

यह आकर्षक है क्योंकि हम उपयोग के मामले में प्रस्तुति को "कॉल" करने की ज़िम्मेदारी से आगे बढ़ रहे हैं, इसलिए उपयोग मामला केवल डेटा प्रदान करने के साथ ही क्या करना है, यह जानने की चिंता नहीं करता है। इसके अलावा, इस मामले में हम अभी भी निर्भरता नियम नहीं तोड़ रहे हैं, क्योंकि उपयोग के मामले में अभी भी बाहरी परत के बारे में कुछ भी नहीं पता है।

यहाँ समस्या यह है कि अब जो कुछ भी पता है कि डेटा कैसे मांगना है वह भी डेटा को स्वीकार करने वाली चीज़ है। इससे पहले कि नियंत्रक यूसेक इंटरेक्टर को इस बात से अनभिज्ञ रूप से अनभिज्ञता से बुला सकता है कि रिस्पांस मॉडल कैसा दिखेगा, उसे कहां जाना चाहिए, और, हे, इसे कैसे प्रस्तुत करना है।

फिर से, कृपया कमांड क्वेरी जिम्मेदारी सेगमेंटेशन का अध्ययन करें कि यह क्यों महत्वपूर्ण है।

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

हाँ! बताना, न पूछना, इस वस्तु को प्रक्रियात्मक के बजाय उन्मुख रखने में मदद करेगा।

मुद्दे पर

तो, इन दोनों विकल्पों में से कोई भी क्लीन आर्किटेक्चर के अनुसार उपयोग केस आउटपुट पोर्ट की "सही" व्याख्या है? क्या वे दोनों व्यवहार्य हैं?

जो कुछ भी काम करता है वह व्यवहार्य है। लेकिन मैं यह नहीं कहूँगा कि दूसरा विकल्प जो आपने ईमानदारी से प्रस्तुत किया है वह क्लीन आर्किटेक्चर का अनुसरण करता है। यह कुछ ऐसा हो सकता है जो काम करता है। लेकिन यह वह नहीं है जो क्लीन आर्किटेक्चर पूछता है।


4
इस तरह की गहन व्याख्या लिखने के लिए समय निकालने के लिए धन्यवाद।
स्वेनी

1
मैं क्लीन आर्किटेक्चर के चारों ओर अपना सिर लपेटने की कोशिश कर रहा हूं, और यह जवाब एक शानदार संसाधन रहा है। बहुत अच्छा किया!
नाथन

महान और विस्तार से उत्तर .. इसके लिए धन्यवाद .. क्या आप मुझे UseCase रन के दौरान GUI को अपडेट करने के बारे में कुछ सुझाव (या स्पष्टीकरण के लिए) दे सकते हैं, यानी बड़ी फ़ाइल अपलोड करते समय प्रगति बार अपडेट?
ईवोक

1
@ इवोक, आपके प्रश्न के त्वरित उत्तर के रूप में, आपको ऑब्जर्वेबल पैटर्न में देखना चाहिए। आपका उपयोग मामला एक विषय लौटा सकता है और प्रगति अपडेट के विषय को सूचित कर सकता है। प्रस्तुतकर्ता सब्जेक्ट की सदस्यता लेगा और अधिसूचनाओं पर प्रतिक्रिया देगा।
नाथन

7

आपके प्रश्न से संबंधित एक चर्चा में , अंकल बॉब ने अपने क्लीन आर्किटेक्चर में प्रस्तुतकर्ता के उद्देश्य को समझाया:

इस कोड नमूने को देखते हुए:

namespace Some\Controller;

class UserController extends Controller {
    public function registerAction() {
        // Build the Request object
        $request = new RegisterRequest();
        $request->name = $this->getRequest()->get('username');
        $request->pass = $this->getRequest()->get('password');

        // Build the Interactor
        $usecase = new RegisterUser();

        // Execute the Interactors method and retrieve the response
        $response = $usecase->register($request);

        // Pass the result to the view
        $this->render(
            '/user/registration/template.html.twig', 
            array('id' =>  $response->getId()
        );
    }
}

चाचा बॉब ने यह कहा:

" प्रस्तुतकर्ता का उद्देश्य यूआई के प्रारूप से उपयोग के मामलों को कम करना है। आपके उदाहरण में, इंटरप्रेटर द्वारा $ प्रतिसाद चर बनाया गया है, लेकिन इसका उपयोग दृश्य द्वारा किया जाता है। यह जोड़े को देखने के लिए सहभागिता करता है। उदाहरण के लिए। , मान लें कि $ प्रतिक्रिया ऑब्जेक्ट में से एक फ़ील्ड एक दिनांक है। वह फ़ील्ड एक बाइनरी दिनांक ऑब्जेक्ट होगी जिसे कई अलग-अलग दिनांक प्रारूपों में प्रदान किया जा सकता है। एक बहुत विशिष्ट तिथि प्रारूप, शायद डीडी / एमएम / वाईवाईवाई चाहता है। प्रारूप बनाने की ज़िम्मेदारी किसकी है? यदि इंटरैक्टर उस प्रारूप को बनाता है, तो यह व्यू के बारे में बहुत अधिक जानता है। लेकिन यदि दृश्य बाइनरी दिनांक ऑब्जेक्ट लेता है, तो यह इंटरैक्टर के बारे में बहुत अधिक जानता है।

"प्रस्तुतकर्ता का काम लेना है। प्रतिक्रिया ऑब्जेक्ट से डेटा और दृश्य के लिए इसे प्रारूपित करें। न तो दृश्य और न ही इंटरैक्टर को एक दूसरे के स्वरूपों के बारे में पता है।

--- " अंकल बॉब

(अद्यतन: ३१ मई २०१ ९)

अंकल बॉब के उस जवाब को देखते हुए, मुझे लगता है कि इससे कोई फर्क नहीं पड़ता कि हम विकल्प # 1 करते हैं ( क्या इंटरप्रेटर प्रस्तोता का उपयोग करते हैं) ...

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... या हम विकल्प # 2 करते हैं (इंटरएक्टर रिटर्न रिस्पॉन्स दें, कंट्रोलर के अंदर एक प्रस्तोता बनाएं, फिर प्रस्तोता को प्रतिक्रिया दें) ...

class Controller
{
    public void ExecuteUseCase(Data data)
    {
        Request request = ...
        UseCase useCase = new UseCase(repository);
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        presenter.Show(response);
    }
}

व्यक्तिगत रूप से, मैं विकल्प # 1 पसंद करते हैं क्योंकि मैं सक्षम नियंत्रण होना चाहता हूँ अंदर interactor जब डेटा और त्रुटि संदेश प्रदर्शित करने के लिए नीचे दिए गए इस उदाहरण की तरह:

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... मैं ऐसा करने में सक्षम होना चाहता हूं जो इंटरएक्टर के बाहर और if/elseअंदर interactorनहीं , प्रस्तुति से संबंधित हो ।

दूसरी ओर हम विकल्प # 2 करते हैं तो हम में त्रुटि संदेश (रों) स्टोर करने के लिए होता है response, उद्देश्य यह है कि लौटने responseसे वस्तु interactorके लिए controller, और बनाने के controller पार्सresponse वस्तु ...

class UseCase
{
    public Response Execute(Request request)
    {
        Response response = new Response();
        if (<invalid request>) 
        {
            response.AddError("...");
        }

        if (<there is another error>) 
        {
            response.AddError("another error...");
        }

        if (response.HasNoErrors)
        {
            response.Whatever = ...
        }

        ...
        return response;
    }
}
class Controller
{
    private UseCase useCase;

    public Controller(UseCase useCase)
    {
        this.useCase = useCase;
    }

    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        if (response.ErrorMessages.Count > 0)
        {
            if (response.ErrorMessages.Contains(<invalid request>))
            {
                presenter.ShowError("...");
            }
            else if (response.ErrorMessages.Contains("another error")
            {
                presenter.ShowError("another error...");
            }
        }
        else
        {
            presenter.Show(response);
        }
    }
}

मुझे responseअंदर त्रुटियों के लिए डेटा पार्स करना पसंद नहीं है controllerक्योंकि अगर हम ऐसा करते हैं कि हम निरर्थक काम कर रहे हैं --- अगर हम कुछ बदलते हैं interactor, तो हमें भी कुछ बदलना होगा controller

इसके अलावा, अगर हम बाद में हमारे पुन: उपयोग करने का फैसला interactorकंसोल का उपयोग कर, उदाहरण के लिए वर्तमान आंकड़ों के, हम सब उन कॉपी-पेस्ट करने के लिए याद if/elseमें controllerहमारे सांत्वना app की।

// in the controller for our console app
if (response.ErrorMessages.Count > 0)
{
    if (response.ErrorMessages.Contains(<invalid request>))
    {
        presenterForConsole.ShowError("...");
    }
    else if (response.ErrorMessages.Contains("another error")
    {
        presenterForConsole.ShowError("another error...");
    }
}
else
{
    presenterForConsole.Present(response);
}

यदि हम विकल्प # 1 का उपयोग करते हैं तो हमारे पास if/else केवल एक ही स्थान पर होगा : the interactor


यदि आप ASP.NET MVC (या अन्य समान MVC फ्रेमवर्क) का उपयोग कर रहे हैं, तो विकल्प # 2 जाने का आसान तरीका है।

लेकिन हम अभी भी उस तरह के वातावरण में विकल्प # 1 कर सकते हैं। यहाँ ASP.NET MVC में विकल्प # 1 करने का एक उदाहरण दिया गया है:

(ध्यान दें कि हमें public IActionResult Resultअपने ASP.NET MVC ऐप के प्रस्तुतकर्ता में होना चाहिए)

class UseCase
{
    private Repository repository;

    public UseCase(Repository repository)
    {
        this.repository = repository;
    }

    public void Execute(Request request, Presenter presenter)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {
            ...
        }
        this.presenter.Show(response);
    }
}
// controller for ASP.NET app

class AspNetController
{
    private UseCase useCase;

    public AspNetController(UseCase useCase)
    {
        this.useCase = useCase;
    }

    [HttpPost("dosomething")]
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new AspNetPresenter();
        useCase.Execute(request, presenter);
        return presenter.Result;
    }
}
// presenter for ASP.NET app

public class AspNetPresenter
{
    public IActionResult Result { get; private set; }

    public AspNetPresenter(...)
    {
    }

    public async void Show(Response response)
    {
        Result = new OkObjectResult(new { });
    }

    public void ShowError(string errorMessage)
    {
        Result = new BadRequestObjectResult(errorMessage);
    }
}

(ध्यान दें कि हमें public IActionResult Resultअपने ASP.NET MVC ऐप के प्रस्तुतकर्ता में होना चाहिए)

हम कंसोल के लिए किसी अन्य ऐप्स बनाने का निर्णय लेते हैं, तो हम पुनः उपयोग कर सकते UseCaseऊपर और बनाने के सिर्फ Controllerऔर Presenterकंसोल के लिए:

// controller for console app

class ConsoleController
{    
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new ConsolePresenter();
        useCase.Execute(request, presenter);
    }
}
// presenter for console app

public class ConsolePresenter
{
    public ConsolePresenter(...)
    {
    }

    public async void Show(Response response)
    {
        // write response to console
    }

    public void ShowError(string errorMessage)
    {
        Console.WriteLine("Error: " + errorMessage);
    }
}

(ध्यान दें कि हम public IActionResult Resultअपने कंसोल ऐप के प्रस्तुतकर्ता में नहीं हैं )


योगदान के लिए धन्यवाद। वार्तालाप को पढ़ना, हालांकि, एक बात है जो मुझे समझ में नहीं आती है: वह कहता है कि प्रस्तुतकर्ता को प्रतिक्रिया से आने वाले डेटा को प्रस्तुत करना चाहिए, और एक ही समय में प्रतिक्रिया को इंटरएक्टर द्वारा नहीं बनाया जाना चाहिए। लेकिन फिर प्रतिक्रिया कौन पैदा कर रहा है? मैं कहूंगा कि इंटरप्रेटर को प्रस्तुतकर्ता को डेटा प्रदान करना चाहिए, एप्लिकेशन विशिष्ट प्रारूप में, जिसे प्रस्तुतकर्ता द्वारा पता किया जाता है, क्योंकि एडेप्टर परत आवेदन परत पर निर्भर कर सकती है (लेकिन अन्य तरीके से नहीं)।
स्वेनी

मुझे माफ कर दो। शायद यह भ्रमित हो जाता है क्योंकि मैंने चर्चा से कोड उदाहरण को शामिल नहीं किया था। मैं कोड उदाहरण को शामिल करने के लिए इसे अपडेट करूंगा।
जॉय फ्लैग

अंकल बॉब ने यह नहीं कहा कि प्रतिक्रिया को इंटरएक्टर द्वारा नहीं बनाया जाना चाहिए। इंटरप्रेटर द्वारा प्रतिक्रिया बनाई जाएगी । अंकल बॉब क्या कह रहे हैं कि इंटरप्रेटर द्वारा बनाई गई प्रतिक्रिया का उपयोग प्रस्तुतकर्ता द्वारा किया जाएगा। प्रस्तोता तब "इसे प्रारूपित करेगा", एक दृश्यमॉडल के लिए स्वरूपित प्रतिक्रिया डालें, फिर उस दृश्य को देखने के लिए पास करें। <br/> इस तरह से मैं इसे समझता हूं।
जॉय फ्लैग

1
यह अधिक समझ में आता है। मैं इस धारणा के तहत था कि "दृश्य" "प्रस्तुतकर्ता" का पर्याय था, क्योंकि क्लीन आर्किटेक्चर "दृश्य" या "दृश्यमॉडल" का उल्लेख नहीं करता है, जो मुझे लगता है कि पूरी तरह से एमवीसी अवधारणाएं हैं, जिन्हें लागू करते समय उपयोग नहीं किया जा सकता है या नहीं। अनुकूलक।
स्वेनी

2

एक उपयोग के मामले में प्रस्तुतकर्ता या रिटर्निंग डेटा शामिल हो सकते हैं, यह निर्भर करता है कि आवेदन प्रवाह के लिए क्या आवश्यक है।

आइए विभिन्न अनुप्रयोग प्रवाह को समझने से पहले कुछ शब्दों को समझें:

  • डोमेन ऑब्जेक्ट : एक डोमेन ऑब्जेक्ट डोमेन परत में डेटा कंटेनर है जिस पर व्यावसायिक तर्क संचालन किया जाता है।
  • मॉडल देखें : उपयोगकर्ता इंटरफ़ेस के अनुकूल और अनुकूल बनाने के लिए डोमेन ऑब्जेक्ट को आमतौर पर मॉडल को एप्लिकेशन परत में देखने के लिए मैप किया जाता है।
  • प्रस्तुतकर्ता : जबकि अनुप्रयोग परत में एक नियंत्रक आमतौर पर एक उपयोग के मामले को आमंत्रित करता है, लेकिन मॉडल मैपिंग लॉजिक को अलग वर्ग (एकल जिम्मेदारी सिद्धांत), जिसे "प्रस्तुतकर्ता" कहा जाता है, को देखने के लिए डोमेन को सौंपना उचित है।

रिटर्निंग डेटा का उपयोग करने वाला केस केस

एक सामान्य स्थिति में, एक यूज़ केस केवल एक डोमेन ऑब्जेक्ट को एप्लिकेशन लेयर पर लौटाता है जिसे आगे यूआई में दिखाने के लिए इसे अनुकूल बनाने के लिए एप्लिकेशन लेयर में प्रोसेस किया जा सकता है।

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

यहाँ एक सरल कोड नमूना है:

namespace SimpleCleanArchitecture
{
    public class OutputDTO
    {
        //fields
    }

    public class Presenter 
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    public class Domain
    {
        //fields
    }

    public class UseCaseInteractor
    {
        public Domain Process(Domain domain)
        {
            // additional processing takes place here
            return domain;
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            UseCaseInteractor userCase = new UseCaseInteractor();
            var domain = userCase.Process(new Domain());//passing dummy domain(for demonstration purpose) to process
            var presenter = new Presenter();//presenter might be initiated via dependency injection.

            return new View(presenter.Present(domain));
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

प्रस्तुतकर्ता सहित एक उपयोग मामला

हालांकि आम नहीं है, लेकिन यह संभव है कि उपयोग के मामले में प्रस्तुतकर्ता को कॉल करने की आवश्यकता हो। प्रस्तुतकर्ता के ठोस संदर्भ को रखने के बजाय उस मामले में, एक इंटरफ़ेस (या अमूर्त वर्ग) को संदर्भ बिंदु (जिसे निर्भरता इंजेक्शन के माध्यम से रन समय में आरंभीकृत किया जाना चाहिए) पर विचार करना उचित है।

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

यहाँ छवि विवरण दर्ज करें

नीचे मूल लेख में दिए गए नियंत्रण प्रवाह का सरलीकृत कार्यान्वयन है, यह दर्शाता है कि यह कैसे किया जा सकता है। कृपया ध्यान दें कि आरेख में दिखाया गया है, इसके विपरीत सादगी के लिए UseCaseInteractor एक ठोस वर्ग है।

namespace CleanArchitectureWithPresenterInUseCase
{
    public class Domain
    {
        //fields
    }

    public class OutputDTO
    {
        //fields
    }

    // Use Case Output Port
    public interface IPresenter
    {
        OutputDTO Present(Domain domain);
    }

    public class Presenter: IPresenter
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    // Use Case Input Port / Interactor   
    public class UseCaseInteractor
    {
        IPresenter _presenter;
        public UseCaseInteractor (IPresenter presenter)
        {
            _presenter = presenter;
        }

        public OutputDTO Process(Domain domain)
        {
            return _presenter.Present(domain);
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            IPresenter presenter = new Presenter();//presenter might be initiated via dependency injection.
            UseCaseInteractor userCase = new UseCaseInteractor(presenter);
            var outputDTO = userCase.Process(new Domain());//passing dummy domain (for demonstration purpose) to process
            return new View(outputDTO);
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

1

भले ही मैं आम तौर पर @CandiedOrange के जवाब से सहमत हूं, मैं उस दृष्टिकोण में भी लाभ देखूंगा जहां इंटरएक्टर सिर्फ डेटा को वापस करता है जो तब नियंत्रक द्वारा प्रस्तुतकर्ता को दिया जाता है।

उदाहरण के लिए यह Asp.Net MVC के संदर्भ में स्वच्छ वास्तुकला (निर्भरता नियम) के विचारों का उपयोग करने का एक सरल तरीका है।

मैंने इस चर्चा में गहरा गोता लगाने के लिए एक ब्लॉग पोस्ट लिखी है: https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/


1

प्रस्तुतकर्ता या रिटर्निंग डेटा वाले मामले का उपयोग करें?

तो, इन दोनों विकल्पों में से कोई भी क्लीन आर्किटेक्चर के अनुसार उपयोग केस आउटपुट पोर्ट की "सही" व्याख्या है? क्या वे दोनों व्यवहार्य हैं?


संक्षेप में

हां, वे दोनों व्यवहार्य हैं जब तक दोनों दृष्टिकोण व्यवसाय परत और वितरण तंत्र के बीच नियंत्रण के व्युत्क्रम को ध्यान में रखते हैं । दूसरे दृष्टिकोण के साथ, हम अभी भी पर्यवेक्षक, मध्यस्थ कुछ अन्य डिजाइन पैटर्न का उपयोग करके आईओसी शुरू करने में सक्षम हैं ...

अपने क्लीन आर्किटेक्चर के साथ , अंकल बॉब का प्रयास ओओपी सिद्धांतों का व्यापक रूप से पालन करने के लिए हमारे लिए महत्वपूर्ण अवधारणाओं और घटकों को प्रकट करने के लिए ज्ञात आर्किटेक्चर के एक समूह को संश्लेषित करना है।

यह यूएमएल वर्ग आरेख (नीचे आरेख) को अद्वितीय क्लीन आर्किटेक्चर डिजाइन के रूप में विचार करने के लिए काउंटर उत्पादक होगा । यह आरेख ठोस उदाहरणों की खातिर तैयार किया जा सकता है ... हालाँकि, चूंकि यह सामान्य वास्तुकला अभ्यावेदन की तुलना में बहुत कम अमूर्त है, इसलिए उन्हें बीच में ठोस विकल्प बनाने पड़े, जिसके लिए अंतःक्रियात्मक आउटपुट पोर्ट डिज़ाइन जो केवल एक कार्यान्वयन विवरण है ...

क्लीन आर्किटेक्चर के अंकल बॉब का यूएमएल क्लास आरेख


मेरे दो सेंट

कारण है कि मैं वापस आ रहा पसंद करते हैं मुख्य कारण UseCaseResponseहै कि इस दृष्टिकोण मेरी उपयोग के मामलों रहता है लचीला , दोनों की इजाजत दी रचना उन्हें और के बीच genericity ( सामान्यीकरण और विशिष्ट पीढ़ी )। एक मूल उदाहरण:

// A generic "entity type agnostic" use case encapsulating the interaction logic itself.
class UpdateUseCase implements UpdateUseCaseInterface
{
    function __construct(EntityGatewayInterface $entityGateway, GetUseCaseInterface $getUseCase)
    {
        $this->entityGateway = $entityGateway;
        $this->getUseCase = $getUseCase;
    }

    public function execute(UpdateUseCaseRequestInterface $request) : UpdateUseCaseResponseInterface
    {
        $getUseCaseResponse = $this->getUseCase->execute($request);

        // Update the entity and build the response...

        return $response;
    }
}

// "entity type aware" use cases encapsulating the interaction logic WITH the specific entity type.
final class UpdatePostUseCase extends UpdateUseCase;
final class UpdateProductUseCase extends UpdateUseCase;

ध्यान दें कि यह यूएमएल के उपयोग के मामलों में एक-दूसरे के करीब / समीप है और विभिन्न विषयों (संस्थाओं) पर पुन: प्रयोज्य के रूप में परिभाषित किया गया है।


इंटरेक्टर रिटर्निंग डेटा पर

हालांकि, उपयोग का मामला उस क्षण को नियंत्रित नहीं करता है जब वास्तविक प्रस्तुति अब की जाती है (जो उपयोगी हो सकती है, उदाहरण के लिए उस बिंदु पर अतिरिक्त सामान करने के लिए, जैसे लॉगिंग, या यदि आवश्यक हो तो इसे पूरी तरह से समाप्त करना)।

यह समझने के लिए निश्चित नहीं है कि आप इसके साथ क्या मतलब है, आपको प्रस्तुति के विरूपण को "नियंत्रित" करने की आवश्यकता क्यों होगी? क्या आप इसे तब तक नियंत्रित नहीं करते हैं जब तक आप उपयोग के मामले की प्रतिक्रिया वापस नहीं करते हैं?

उपयोग की स्थिति ग्राहक प्रतिक्रिया को उसके ऑपरेशन के दौरान वास्तव में क्या हुआ, यह बताने के लिए एक स्थिति कोड के जवाब में वापस आ सकती है। HTTP प्रतिक्रिया स्थिति कोड विशेष रूप से उपयोग केस के संचालन की स्थिति का वर्णन करने के लिए अनुकूल हैं ...

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.