सी # में एक इंटरफ़ेस में एक पूर्व शर्त (एलएसपी) कैसे निर्दिष्ट करें?


11

मान लीजिए कि हमारे पास निम्नलिखित इंटरफ़ेस हैं -

interface IDatabase { 
    string ConnectionString{get;set;}
    void ExecuteNoQuery(string sql);
    void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

पूर्व शर्त यह है कि किसी भी तरीके को चलाने से पहले ConnectionString को सेट / इंटिग्रेटेड किया जाना चाहिए।

यदि IDatabase एक अमूर्त या ठोस वर्ग था, तो यह पूर्व-निर्माण किसी निर्माणकर्ता के माध्यम से कनेक्शन पास करके प्राप्त किया जा सकता है -

abstract class Database { 
    public string ConnectionString{get;set;}
    public Database(string connectionString){ ConnectionString = connectionString;}

    public void ExecuteNoQuery(string sql);
    public void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

वैकल्पिक रूप से, हम प्रत्येक पद्धति के लिए कनेक्शनस्ट्रिंग को एक पैरामीटर बना सकते हैं, लेकिन यह केवल एक अमूर्त वर्ग बनाने से भी बदतर लगता है -

interface IDatabase { 
    void ExecuteNoQuery(string connectionString, string sql);
    void ExecuteNoQuery(string connectionString, string[] sql);
    //Various other methods all with the connectionString parameter
}

प्रशन -

  1. क्या इंटरफ़ेस के भीतर इस पूर्व शर्त को निर्दिष्ट करने का कोई तरीका है? यह एक मान्य "अनुबंध" है, इसलिए मैं सोच रहा हूं कि क्या इसके लिए कोई भाषा सुविधा या पैटर्न है (सार वर्ग समाधान दो प्रकार के बनाने की आवश्यकता के अलावा एक हैक इमो है - एक इंटरफ़ेस और एक सार वर्ग - हर बार इसकी आवश्यकता है)
  2. यह एक सैद्धांतिक जिज्ञासा अधिक है - क्या यह पूर्व शर्त वास्तव में एक पूर्व शर्त की परिभाषा में आती है जैसा कि एलएसपी के संदर्भ में है?

2
"एलएसपी" द्वारा आप लोग लिस्कोव प्रतिस्थापन सिद्धांत के बारे में बात कर रहे हैं? "अगर इसकी बत्तख एक बतख की तरह है, लेकिन बैटरी की जरूरत है न कि एक बतख" सिद्धांत? क्योंकि जैसा कि मैं देख रहा हूं कि यह आईएसपी और एसआरपी का उल्लंघन है, शायद ओसीपी भी, लेकिन वास्तव में एलएसपी नहीं।
सेबस्टियन

2
जैसा कि आप जानते, इस पूरी अवधारणा "ConnectionString सेट होना चाहिए / intialized से पहले तरीकों चलाया जा सकता है की किसी भी" अस्थायी युग्मन का एक उदाहरण है blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling और बचा जाना चाहिए, अगर मुमकिन।
रिचीबन

सीमन वास्तव में एब्सट्रैक्ट फैक्ट्री का बहुत बड़ा प्रशंसक है।
एड्रियन इफोड

जवाबों:


10
  1. हाँ। .Net 4.0 से ऊपर की ओर, Microsoft कोड कॉन्ट्रैक्ट प्रदान करता है । इनका उपयोग फॉर्म में पूर्व शर्त को परिभाषित करने के लिए किया जा सकता है Contract.Requires( ConnectionString != null );। हालांकि, एक इंटरफ़ेस के लिए यह काम करने के लिए, आपको अभी भी एक सहायक वर्ग की आवश्यकता होगी IDatabaseContract, जो कि संलग्न हो जाता है IDatabase, और आपके इंटरफ़ेस के प्रत्येक अलग-अलग तरीके के लिए पूर्व शर्त को परिभाषित करने की आवश्यकता होती है, जहां यह धारण करेगा। इंटरफेस के लिए एक व्यापक उदाहरण के लिए यहां देखें

  2. हां , एलएसपी एक अनुबंध के दोनों वाक्यात्मक और शब्दार्थ भागों से संबंधित है।


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

1
@ रोबर्टहवे: हाँ, आप सही हैं। तकनीकी रूप से, आपको निश्चित रूप से दूसरी श्रेणी की आवश्यकता होती है, लेकिन एक बार परिभाषित होने के बाद, अनुबंध इंटरफ़ेस के प्रत्येक कार्यान्वयन के लिए स्वचालित रूप से काम करता है।
डॉक ब्राउन

21

कनेक्ट करना और क्वेरी करना दो अलग-अलग चिंताएँ हैं। जैसे, उनके पास दो अलग-अलग इंटरफेस होने चाहिए।

interface IDatabaseConnection
{
    IDatabase Connect(string connectionString);
}

interface IDatabase
{
    public void ExecuteNoQuery(string sql);
    public void ExecuteNoQuery(string[] sql);
}

यह दोनों सुनिश्चित करता है कि IDatabaseउपयोग किए जाने पर जुड़ा होगा और क्लाइंट को उस इंटरफ़ेस पर निर्भर नहीं करेगा जो इसकी आवश्यकता नहीं है।


के बारे में और अधिक स्पष्ट हो सकता है "यह प्रकारों के माध्यम से
पूर्वगंधों

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

विशिष्ट requrement कि कुछ करने से पहले होता है कुछ और व्यापक रूप से लागू होता है। मुझे यह भी लगता है कि आपका उत्तर इस प्रश्न को बेहतर ढंग से फिट करता है , लेकिन इस उत्तर को बेहतर बनाया जा सकता है
केलथ

1
यह उत्तर पूरी तरह से बिंदु को याद करता है। IDatabaseइंटरफ़ेस एक वस्तु एक डेटाबेस से संबंध स्थापित करने के लिए और फिर मनमाना प्रश्नों को क्रियान्वित करने में सक्षम परिभाषित करता है। यह है उद्देश्य यह है कि डेटाबेस और कोड के बाकी के बीच सीमा के रूप में कार्य करता है। इस प्रकार, इस ऑब्जेक्ट को राज्य (जैसे लेनदेन) बनाए रखना होगा जो प्रश्नों के व्यवहार को प्रभावित कर सकता है। उन्हें एक ही कक्षा में रखना बहुत व्यावहारिक है।
jpmc26

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

5

आइए एक कदम पीछे लें और यहां की बड़ी तस्वीर देखें।

IDatabaseजिम्मेदारी क्या है ?

इसके कुछ अलग ऑपरेशन हैं:

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

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

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

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

क्या हमें सेट होने के लिए वास्तव में कनेक्शन स्ट्रिंग की आवश्यकता है?

हाँ। जब तक आपके पास कनेक्शन स्ट्रिंग नहीं है, तब तक आप कनेक्शन नहीं खोल सकते हैं , और जब तक आप कनेक्शन नहीं खोलते हैं , तब तक आप प्रोटोकॉल के साथ कुछ भी नहीं कर सकते हैं। इसलिए एक के बिना एक कनेक्शन ऑब्जेक्ट होना व्यर्थ है।

हम कनेक्शन स्ट्रिंग की आवश्यकता की समस्या को कैसे हल करेंगे?

हम जिस समस्या को हल करने का प्रयास कर रहे हैं, वह यह है कि हम चाहते हैं कि वस्तु हर समय उपयोगी स्थिति में रहे। OO भाषाओं में राज्य का प्रबंधन करने के लिए किस तरह की इकाई का उपयोग किया जाता है? ऑब्जेक्ट्स , इंटरफेस नहीं। इंटरफेस के पास प्रबंधन करने के लिए राज्य नहीं है। क्योंकि आप जिस समस्या को हल करने का प्रयास कर रहे हैं वह राज्य प्रबंधन की समस्या है, एक इंटरफ़ेस वास्तव में यहाँ उपयुक्त नहीं है। एक अमूर्त वर्ग बहुत अधिक प्राकृतिक है। इसलिए एक कंस्ट्रक्टर के साथ एक सार वर्ग का उपयोग करें।

आप वास्तव में कंस्ट्रक्टर के दौरान कनेक्शन खोलने पर भी विचार कर सकते हैं , क्योंकि कनेक्शन खुलने से पहले ही बेकार हो जाता है। protected Openडेटाबेस को विशिष्ट बनाने के लिए कनेक्शन खोलने की प्रक्रिया के बाद से एक अमूर्त विधि की आवश्यकता होगी । ConnectionStringसंपत्ति को केवल इस मामले में पढ़ा जाना अच्छा होगा , क्योंकि कनेक्शन खुला होने के बाद कनेक्शन स्ट्रिंग को बदलना अर्थहीन होगा। (ईमानदारी से, मैं इसे केवल वैसे ही पढ़ूंगा। यदि आप एक अलग स्ट्रिंग के साथ एक कनेक्शन चाहते हैं, तो दूसरी वस्तु बनाएं।)

क्या हमें एक इंटरफ़ेस की आवश्यकता है?

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

यदि हम इस मार्ग पर जाते हैं, तो हमारा कोड कुछ इस तरह दिखाई दे सकता है:

interface IDatabase {
    void ExecuteNoQuery(string sql);
    void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

abstract class ConnectionStringDatabase : IDatabase { 

    public string ConnectionString { get; }

    public Database(string connectionString) {
        this.ConnectionString = connectionString;
        this.Open();
    }

    protected abstract void Open();

    public abstract void ExecuteNoQuery(string sql);
    public abstract void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

सराहना करेंगे अगर डाउनवॉटर असहमति के लिए उनके कारण की व्याख्या करेगा।
jpmc26

सहमत, फिर से: नीच। यह सही उपाय है। कनेक्शन स्ट्रिंग को कंस्ट्रक्टर में कंक्रीट / सार वर्ग में प्रदान किया जाना चाहिए। कनेक्शन खोलने / बंद करने का गन्दा व्यवसाय इस वस्तु का उपयोग करने वाले कोड की चिंता नहीं है, और इसे कक्षा में ही आंतरिक रहना चाहिए। मेरा तर्क है कि Openविधि होनी चाहिए privateऔर आपको एक संरक्षित Connectionसंपत्ति को उजागर करना चाहिए जो कनेक्शन बनाता है और जोड़ता है। या एक संरक्षित OpenConnectionविधि को उजागर करें ।
ग्रेग बरगार्ड

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

फिर भी मैं इसे बढ़ाता हूं।
सेबस्टियन

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

0

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

public interface IDatabase : IDisposable
{
    string ConnectionString { get; }
    void ExecuteNoQuery(string sql);
    void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

public class SqlDatabase : IDatabase
{
    public string ConnectionString { get; }
    SqlConnection sqlConnection;
    SqlTransaction sqlTransaction; // optional

    public SqlDatabase(string connectionStr)
    {
        if (String.IsNullOrEmpty(connectionStr)) throw new ArgumentException("connectionStr empty");
        ConnectionString = connectionStr;
        instantiateSqlProps();
    }

    private void instantiateSqlProps()
    {
        sqlConnection.Open();
        sqlTransaction = sqlConnection.BeginTransaction();
    }

    public void ExecuteNoQuery(string sql) { /*run query*/ }
    public void ExecuteNoQuery(string[] sql) { /*run query*/ }

    public void Dispose()
    {
        sqlTransaction.Commit();
        sqlConnection.Dispose();
    }

    public void Commit()
    {
        Dispose();
        instantiateSqlProps();
    }
}

उपयोग इस तरह दिख सकता है:

using (IDatabase dbase = new SqlDatabase("Data Source = servername; Initial Catalog = MyDb; Integrated Security = True"))
{
    dbase.ExecuteNoQuery("delete from dbo.Invoices");
    dbase.ExecuteNoQuery("delete from dbo.Customers");
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.