मैं एक इंटरफ़ेस कैसे डिज़ाइन करूंगा जैसे कि यह स्पष्ट है कि कौन से गुण उनके मूल्य को बदल सकते हैं, और जो स्थिर रहेगा?


12

मैं .NET प्रॉपर्टीज के बारे में एक डिज़ाइन इश्यू दे रहा हूं।

interface IX
{
    Guid Id { get; }
    bool IsInvalidated { get; }
    void Invalidate();
}

संकट:

इस इंटरफ़ेस में दो रीड-ओनली गुण हैं, Idऔर IsInvalidated। तथ्य यह है कि वे केवल-पढ़ने के लिए हैं, हालांकि, कोई गारंटी नहीं है कि उनके मूल्य स्थिर रहेंगे।

मान लीजिए कि यह मेरा उद्देश्य था कि मैं इसे बहुत स्पष्ट करूं ...

  • Id एक निरंतर मूल्य का प्रतिनिधित्व करता है (जो इसलिए सुरक्षित रूप से कैश किया जा सकता है), जबकि
  • IsInvalidatedकिसी IXवस्तु के जीवनकाल के दौरान इसका मूल्य बदल सकता है (और इसलिए इसे कैश नहीं किया जाना चाहिए)।

मैं interface IXउस अनुबंध को पर्याप्त रूप से स्पष्ट करने के लिए कैसे संशोधित कर सकता हूं ?

एक समाधान में मेरे अपने तीन प्रयास:

  1. इंटरफ़ेस पहले से ही अच्छी तरह से डिज़ाइन किया गया है। एक विधि की उपस्थिति Invalidate()एक प्रोग्रामर को यह अनुमान लगाने की अनुमति देती है कि समान रूप से नामित संपत्ति का मूल्य IsInvalidatedइससे प्रभावित हो सकता है।

    यह तर्क केवल उन मामलों में है जहां विधि और संपत्ति का नाम समान है।

  2. एक घटना के साथ इस इंटरफ़ेस को संवर्धित करें IsInvalidatedChanged:

    bool IsInvalidated { get; }
    event EventHandler IsInvalidatedChanged;

    किसी …Changedईवेंट की मौजूदगी IsInvalidatedबताती है कि यह प्रॉपर्टी उसके मूल्य को बदल सकती है, और इसके लिए एक समान ईवेंट की अनुपस्थिति Idयह वादा करती है कि वह संपत्ति उसके मूल्य को नहीं बदलेगी।

    मुझे यह समाधान पसंद है, लेकिन यह बहुत अधिक अतिरिक्त सामान है जो शायद उपयोग नहीं किया जा सकता है।

  3. संपत्ति IsInvalidatedको विधि से बदलें IsInvalidated():

    bool IsInvalidated();

    यह बहुत सूक्ष्म परिवर्तन हो सकता है। यह एक संकेत माना जाता है कि एक मूल्य हर बार नए सिरे से गणना की जाती है - जो कि एक स्थिर होने पर आवश्यक नहीं होगा। MSDN विषय "गुण और विधियों के बीच चयन" के बारे में यह कहना है:

    निम्नलिखित स्थितियों में, संपत्ति के बजाय एक विधि का उपयोग करें। […] ऑपरेशन को हर बार एक अलग परिणाम देता है जिसे कहा जाता है, भले ही पैरामीटर बदल न जाएं।

मुझे किस तरह के जवाब की उम्मीद है?

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

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

    यदि दोष मामूली हैं, और इसे ध्यान में रखने के बाद एक से अधिक समाधान बने हुए हैं, तो कृपया टिप्पणी करें।

  • बहुत कम से कम, मैं कुछ प्रतिक्रिया देना चाहूंगा, जिस पर आपका पसंदीदा समाधान है, और किस कारण से।


की IsInvalidatedजरूरत नहीं है private set?
जेम्स

2
@ नाम: आवश्यक नहीं, न तो इंटरफ़ेस घोषणा में, न ही एक कार्यान्वयन में:class Foo : IFoo { private bool isInvalidated; public bool IsInvalidated { get { return isInvalidated; } } public void Invalidate() { isInvalidated = true; } }
stakx

@ इंटरफ़ेस में गुण गुण स्वचालित गुण के समान नहीं होते हैं, भले ही वे एक ही वाक्यविन्यास का उपयोग करते हों।
14

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

@heltonbiker दुर्भाग्य से, अधिकांश भाषाएँ (मुझे पता है) इस प्रकार की बाधाओं को व्यक्त करने की अनुमति नहीं देती हैं, लेकिन उन्हें निश्चित रूप से करना चाहिए । यह विनिर्देश का विषय है, कार्यान्वयन का नहीं। मेरे विचार में, जो बात याद आ रही है, वह यह है कि कक्षा के आक्रमणकारियों और भाषा में सीधे बाद की स्थिति को व्यक्त करना । InvalidStateExceptionउदाहरण के लिए , हमें कभी भी एस के बारे में चिंता नहीं करनी चाहिए , लेकिन मुझे यकीन नहीं है कि यह सैद्धांतिक रूप से भी संभव है। हालांकि अच्छा होगा।
१६:०

जवाबों:


2

मैं 1 और 2 के समाधान 3 को प्राथमिकता दूंगा।

समाधान 1 के साथ मेरी समस्या यह है : अगर कोई Invalidateविधि नहीं है तो क्या होगा ? एक IsValidसंपत्ति है कि रिटर्न के साथ एक इंटरफ़ेस मान लें DateTime.Now > MyExpirationDate; आपको SetInvalidयहां एक स्पष्ट विधि की आवश्यकता नहीं हो सकती है। क्या होगा यदि एक विधि कई गुणों को प्रभावित करती है? के साथ एक कनेक्शन प्रकार मान लें IsOpenऔर IsConnectedगुण - दोनों से प्रभावित हैं Closeविधि।

समाधान 2 : यदि घटना का एकमात्र बिंदु डेवलपर्स को सूचित करना है कि एक समान नाम वाली संपत्ति प्रत्येक कॉल पर विभिन्न मूल्यों को वापस कर सकती है, तो मैं इसके खिलाफ दृढ़ता से सलाह दूंगा। आपको अपने इंटरफेस को छोटा और स्पष्ट रखना चाहिए; इसके अलावा, सभी कार्यान्वयन आपके लिए उस ईवेंट को आग लगाने में सक्षम नहीं हो सकते हैं। मैं IsValidऊपर से अपने उदाहरण का फिर से उपयोग करूंगा : जब आप पहुंचेंगे तो आपको एक टाइमर लागू करना होगा और एक घटना को फायर करना होगा MyExpirationDate। आखिरकार, अगर वह घटना आपके सार्वजनिक इंटरफ़ेस का हिस्सा है, तो इंटरफ़ेस के उपयोगकर्ता उस घटना को काम करने की उम्मीद करेंगे।

इन समाधानों के बारे में कहा जा रहा है कि: वे खराब नहीं हैं। किसी विधि या किसी ईवेंट की मौजूदगी से पता चलता है कि एक समान नाम वाली प्रॉपर्टी प्रत्येक कॉल पर अलग-अलग मान लौटा सकती है। मैं केवल इतना कह रहा हूं कि वे केवल उस अर्थ को व्यक्त करने के लिए पर्याप्त नहीं हैं।

समाधान 3 वह है जो मैं जा रहा हूं। जैसा कि अवीव ने उल्लेख किया है, यह केवल C # डेवलपर्स के लिए काम कर सकता है। मेरे लिए एक C # -देव के रूप में, यह तथ्य कि IsInvalidatedकोई संपत्ति नहीं है तुरंत "का अर्थ है कि यह सिर्फ एक साधारण गौण नहीं है, कुछ यहाँ चल रहा है"। यह हालांकि सभी के लिए लागू नहीं होता है, और जैसा कि मेनमा ने बताया है, .NET फ्रेमवर्क ही यहाँ सुसंगत नहीं है।

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

तो मेरी सलाह होगी:

समाधान 3 को एक सम्मेलन घोषित करें और इसे लगातार पालन करें। संपत्तियों के दस्तावेज़ीकरण में यह स्पष्ट करें कि संपत्ति में बदलते मूल्य हैं या नहीं। साधारण गुणों के साथ काम करने वाले डेवलपर्स सही ढंग से उन्हें गैर-परिवर्तनशील मानेंगे (अर्थात केवल वस्तु जब वस्तु को संशोधित किया जाता है)। एक विधि है कि यह की तरह लगता है एक संपत्ति हो सकता है का सामना डेवलपर्स ( Count(), Length(), IsOpen()) जानते हैं कि someting हो रहा है और (उम्मीद) को समझने के लिए वास्तव में क्या विधि करता है और कैसे बर्ताव करता है विधि डॉक्स पढ़ें।


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

8

एक चौथा समाधान है: प्रलेखन पर भरोसा करें

आप कैसे जानते हैं कि stringवर्ग अपरिवर्तनीय है? आप बस यह जानते हैं, क्योंकि आपने MSDN प्रलेखन पढ़ा है। StringBuilderदूसरी ओर, परिवर्तनशील है, क्योंकि, फिर से, प्रलेखन आपको बताता है कि।

/// <summary>
/// Represents an index of an element stored in the database.
/// </summary>
private interface IX
{
    /// <summary>
    /// Gets the immutable globally unique identifier of the index, the identifier being
    /// assigned by the database when the index is created.
    /// </summary>
    Guid Id { get; }

    /// <summary>
    /// Gets a value indicating whether the index is invalidated, which means that the
    /// indexed element is no longer valid and should be reloaded from the database. The
    /// index can be invalidated by calling the <see cref="Invalidate()" /> method.
    /// </summary>
    bool IsInvalidated { get; }

    /// <summary>
    /// Invalidates the index, without forcing the indexed element to be reloaded from the
    /// database.
    /// </summary>
    void Invalidate();
}

आपका तीसरा समाधान खराब नहीं है, लेकिन .NET फ्रेमवर्क इसका पालन नहीं करता है । उदाहरण के लिए:

StringBuilder.Length
DateTime.Now

गुण हैं, लेकिन उन्हें हर बार स्थिर रहने की उम्मीद नहीं है।

.NET फ्रेमवर्क में लगातार इसका उपयोग किया जाता है:

IEnumerable<T>.Count()

एक विधि है, जबकि:

IList<T>.Length

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


कक्षाओं के साथ, बस कोड को देखने से स्पष्ट रूप से पता चलता है कि किसी संपत्ति का मूल्य वस्तु के जीवनकाल के दौरान नहीं बदलेगा। उदाहरण के लिए,

public class Product
{
    private readonly int price;

    public Product(int price)
    {
        this.price = price;
    }

    public int Price
    {
        get
        {
            return this.price;
        }
    }
}

स्पष्ट है: कीमत समान रहेगी।

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


यहां तक ​​कि "कोई अतिरिक्त काम नहीं" दिशानिर्देश का लगातार उपयोग नहीं किया जाता है। उदाहरण के लिए, Lazy<T>.Valueगणना करने के लिए बहुत लंबा समय लग सकता है (जब यह पहली बार एक्सेस किया जाता है)।
15

1
मेरी राय में, इससे कोई फर्क नहीं पड़ता कि .NET फ्रेमवर्क समाधान के सम्मेलन 3 का अनुसरण करता है। मुझे उम्मीद है कि डेवलपर्स यह जानने के लिए पर्याप्त स्मार्ट होंगे कि क्या वे इन-हाउस प्रकार या फ्रेमवर्क प्रकारों के साथ काम कर रहे हैं। देवता जो नहीं जानते हैं कि डॉक्स नहीं पढ़ते हैं और इस प्रकार समाधान 4 भी मदद नहीं करेगा। यदि समाधान 3 को एक कंपनी / टीम सम्मेलन के रूप में लागू किया जाता है, तो मेरा मानना ​​है कि यह कोई फर्क नहीं पड़ता कि अन्य एपीआई भी इसका पालन करते हैं या नहीं (हालांकि यह बहुत सराहना की जाएगी, निश्चित रूप से)।
enzi

4

मेरे 2 सेंट:

विकल्प 3: एक गैर-सी # के रूप में, यह एक सुराग का ज्यादा नहीं होगा; हालाँकि, आपके द्वारा लाए गए उद्धरण को देखते हुए, यह C # प्रोग्रामर को प्रवीण करने के लिए स्पष्ट होना चाहिए कि इसका मतलब कुछ है। हालाँकि आपको इसके बारे में अभी भी डॉक्स जोड़ना चाहिए।

विकल्प 2: जब तक आप हर उस चीज़ में घटनाओं को जोड़ने की योजना नहीं बनाते हैं जो बदल सकती है, वहाँ मत जाओ।

अन्य विकल्प:

  • इंटरफ़ेस IXMutable का नाम बदल रहा है, इसलिए यह स्पष्ट है कि यह अपनी स्थिति बदल सकता है। आपके उदाहरण में एकमात्र अपरिवर्तनीय मूल्य है id, जिसे आमतौर पर उत्परिवर्तनीय वस्तुओं में भी अपरिवर्तनीय माना जाता है।
  • इसका उल्लेख नहीं है। व्यवहार व्यवहार का वर्णन करने में उतने अच्छे नहीं हैं जितना कि हम उन्हें पसंद करेंगे; आंशिक रूप से, ऐसा इसलिए है क्योंकि भाषा वास्तव में आपको ऐसी चीजों का वर्णन नहीं करने देती है जैसे "यह विधि किसी विशिष्ट वस्तु के लिए हमेशा समान मान लौटाएगी," या "इस विधि को तर्क x <0 के साथ कॉल करना एक अपवाद को फेंक देगा।"
  • सिवाय इसके कि भाषा का विस्तार करने और निर्माण के बारे में अधिक सटीक जानकारी प्रदान करने के लिए एक तंत्र है - एनोटेशन / विशेषताएँ । उन्हें कभी-कभी कुछ काम की आवश्यकता होती है, लेकिन आप अपने तरीकों के बारे में मनमाने ढंग से जटिल बातें निर्दिष्ट करने की अनुमति देते हैं। एक एनोटेशन खोजें या आविष्कार करें जो कहता है कि "इस पद्धति / संपत्ति का मूल्य किसी वस्तु के जीवनकाल में बदल सकता है", और इसे विधि में जोड़ें। इसे लागू करने के तरीकों को प्राप्त करने के लिए आपको कुछ तरकीबें अपनाने की आवश्यकता हो सकती है, और उपयोगकर्ताओं को उस एनोटेशन के लिए डॉक्टर को पढ़ने की आवश्यकता हो सकती है, लेकिन यह एक ऐसा उपकरण होगा जो आपको कहने के बजाए वही कहना चाहता है जो आप कहना चाहते हैं। इस पर।

3

इंटरफ़ेस को विभाजित करने के लिए एक और विकल्प होगा: यह मानते हुए कि Invalidate()प्रत्येक आह्वान को कॉल करने के बाद IsInvalidatedउसी मूल्य को वापस करना चाहिए true, IsInvalidatedउसी भाग के लिए आह्वान करने का कोई कारण नहीं लगता है जो आह्वान किया गया था Invalidate()

इसलिए, मेरा सुझाव है कि या तो आप अमान्य होने की जाँच करें या आप इसका कारण बनें , लेकिन ऐसा करना अनुचित है। इसलिए यह Invalidate()पहले भाग के लिए ऑपरेशन वाले एक इंटरफ़ेस और दूसरे के लिए चेकिंग ( IsInvalidated) के लिए एक अन्य इंटरफ़ेस की पेशकश करने के लिए समझ में आता है । चूंकि यह पहले इंटरफ़ेस के हस्ताक्षर से बहुत स्पष्ट है कि यह उदाहरण को अमान्य बना देगा, शेष प्रश्न (दूसरे इंटरफ़ेस के लिए) वास्तव में काफी सामान्य है: यह निर्दिष्ट करने के लिए कि क्या दिए गए प्रकार अपरिवर्तनीय हैं

interface Invalidatable
{
    bool IsInvalidated { get; }
}

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

[Immutable]
interface IX
{
    Guid Id { get; }
    void Invalidate();
}

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


विचार बुरा नहीं है! हालाँकि, मैं यह तर्क दूंगा कि जब तक यह एक [Immutable]ऐसा तरीका है जिसमें इंटरफ़ेस को चिह्नित करना गलत है, जिसका एकमात्र उद्देश्य उत्परिवर्तन का कारण है। मैं इंटरफ़ेस के लिए Invalidate()विधि को आगे Invalidatableबढ़ाऊंगा।
स्टैक

1
@stakx मैं असहमत हूं। :) मुझे लगता है कि आप अपरिवर्तनीयता और पवित्रता (साइड इफेक्ट्स की अनुपस्थिति) की अवधारणाओं को भ्रमित करते हैं । वास्तव में, प्रस्तावित इंटरफ़ेस IX के दृष्टिकोण से, कोई भी अवलोकन योग्य उत्परिवर्तन नहीं है। जब भी आप कॉल Idकरेंगे, आपको हर बार समान मूल्य मिलेगा। वही लागू होता है Invalidate()(आपको कुछ भी नहीं मिलता है, क्योंकि इसकी वापसी का प्रकार है void)। इसलिए, प्रकार अपरिवर्तनीय है। बेशक, आपके साइड इफेक्ट्स होंगे , लेकिन आप इंटरफ़ेस के माध्यम से उनका निरीक्षण नहीं कर पाएंगे IX, इसलिए उनका क्लाइंट्स पर कोई प्रभाव नहीं पड़ेगा IX
21

ठीक है, हम सहमत हो सकते हैं कि IXअपरिवर्तनीय है। और वे दुष्प्रभाव हैं; वे सिर्फ दो विभाजित इंटरफेस के बीच वितरित किए जाते हैं जैसे कि ग्राहकों को देखभाल करने की आवश्यकता नहीं है ( IXया मामले में ) और यह स्पष्ट है कि दुष्प्रभाव क्या हो सकता है (मामले में Invalidatable)। लेकिन ऊपर क्यों नहीं जाना Invalidateहै Invalidatable? यह IXअपरिवर्तनीय और शुद्ध बना देगा , और Invalidatableअधिक उत्परिवर्तित नहीं होगा , और न ही अधिक अशुद्ध (क्योंकि यह इन दोनों पहले से ही है)।
स्टाक्स

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

1
@stakx सबसे पहले, आपने किसी तरह अपने इंटरफेस के शब्दार्थ को और अधिक स्पष्ट करने के लिए आगे की संभावनाओं के बारे में पूछा। मेरा विचार समस्या को अपरिवर्तनीयता से कम करना था, जिसके लिए इंटरफ़ेस को विभाजित करना आवश्यक था। लेकिन न केवल एक इंटरफ़ेस को अपरिवर्तनीय बनाने के लिए विभाजन की आवश्यकता थी, यह भी बहुत अच्छा अर्थ होगा, क्योंकि इंटरफ़ेस के एक ही उपभोक्ता द्वारा और दोनों को आह्वान करने का कोई कारण नहीं हैInvalidate()IsInvalidated । यदि आप फोन करते हैं Invalidate(), तो आपको पता होना चाहिए कि यह अमान्य हो गया है, इसलिए इसकी जांच क्यों करें? इस प्रकार आप विभिन्न प्रयोजनों के लिए दो अलग-अलग इंटरफेस प्रदान कर सकते हैं।
प्रस्कर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.