कई डेटाबेस प्रकारों का समर्थन करने के लिए सार डेटाबेस इंटरफेस कैसे लिखे जाते हैं?


12

कोई अपने बड़े अनुप्रयोग में एक अमूर्त वर्ग को कैसे डिज़ाइन करना शुरू करता है जो कई प्रकार के डेटाबेस के साथ इंटरफेस कर सकता है, जैसे कि MySQL, SQLLite, MSSQL, आदि?

इस डिज़ाइन पैटर्न को क्या कहा जाता है और यह कहाँ से शुरू होता है?

मान लीजिए कि आपको एक वर्ग लिखने की आवश्यकता है जिसमें निम्नलिखित विधियाँ हैं

public class Database {
   public DatabaseType databaseType;
   public Database (DatabaseType databaseType){
      this.databaseType = databaseType;
   }

   public void SaveToDatabase(){
       // Save some data to the db
   }
   public void ReadFromDatabase(){
      // Read some data from db
   }
}

//Application
public class Foo {
    public Database db = new Database (DatabaseType.MySQL);
    public void SaveData(){
        db.SaveToDatabase();
    }
}

केवल एक चीज जिसके बारे में मैं सोच सकता हूं, वह है कि हर एक Databaseविधि में एक बयान

public void SaveToDatabase(){
   if(databaseType == DatabaseType.MySQL){

   }
   else if(databaseType == DatabaseType.SQLLite){

   }
}

जवाबों:


11

आप जो चाहते हैं, आपके एप्लिकेशन द्वारा उपयोग किए जाने वाले इंटरफ़ेस के लिए कई कार्यान्वयन हैं

इस तरह:

public interface IDatabase
{
    void SaveToDatabase();
    void ReadFromDatabase();
}

public class MySQLDatabase : IDatabase
{
   public MySQLDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //MySql implementation
   }
   public void ReadFromDatabase(){
      //MySql implementation
   }
}

public class SQLLiteDatabase : IDatabase
{
   public SQLLiteDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //SQLLite implementation
   }
   public void ReadFromDatabase(){
      //SQLLite implementation
   }
}

//Application
public class Foo {
    public IDatabase db = GetDatabase();

    public void SaveData(){
        db.SaveToDatabase();
    }

    private IDatabase GetDatabase()
    {
        if(/*some way to tell if should use MySql*/)
            return new MySQLDatabase();
        else if(/*some way to tell if should use MySql*/)
            return new SQLLiteDatabase();

        throw new Exception("You forgot to configure the database!");
    }
}

जहाँ तक IDatabaseआपके आवेदन में सही समय पर सही कार्यान्वयन की स्थापना का एक बेहतर तरीका है , आपको " फ़ैक्टरी विधि ", और " डिपेंडेंसी इंजेक्शन " जैसी चीजों पर ध्यान देना चाहिए ।


25

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


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

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

डिपेंडेंसी इंजेक्शन, फ़ैक्टरी डिज़ाइन पैटर्न के साथ मिलकर, स्ट्रिप स्टोन और स्ट्रेटेजी डिज़ाइन पैटर्न को कोड करने का एक आसान तरीका है , जो IoC सिद्धांत का एक हिस्सा है ।

हमें फोन न करें, हम आपको कॉल करेंगे । (एकेए हॉलीवुड सिद्धांत )।


अमूर्त का उपयोग कर एक आवेदन की घोषणा

1. अमूर्त परत बनाना

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

आपका (बहुत सरल उदाहरण) डेटाबेस इंटरफ़ेस इस तरह दिख सकता है (डेटाबेस डेटाबेस या DbQuery कक्षाएं क्रमशः डेटाबेस डेटाबेस का प्रतिनिधित्व करने वाले आपके स्वयं के कार्यान्वयन होंगे):

public interface Database
{
    DatabaseResult DoQuery(DbQuery query);
    void BeginTransaction();
    void RollbackTransaction();
    void CommitTransaction();
    bool IsInTransaction();
}

क्योंकि यह एक इंटरफ़ेस है, यह वास्तव में कुछ भी नहीं करता है। इसलिए आपको इस इंटरफ़ेस को लागू करने के लिए एक वर्ग की आवश्यकता है।

public class MyMySQLDatabase : Database
{
    private readonly CSharpMySQLDriver _mySQLDriver;

    public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
    {
        _mySQLDriver = mySQLDriver;
    }

    public DatabaseResult DoQuery(DbQuery query)
    {
        // This is a place where you will use _mySQLDriver to handle the DbQuery
    }

    public void BeginTransaction()
    {
        // This is a place where you will use _mySQLDriver to begin transaction
    }

    public void RollbackTransaction()
    {
    // This is a place where you will use _mySQLDriver to rollback transaction
    }

    public void CommitTransaction()
    {
    // This is a place where you will use _mySQLDriver to commit transaction
    }

    public bool IsInTransaction()
    {
    // This is a place where you will use _mySQLDriver to check, whether you are in a transaction
    }
}

अब आपके पास एक वर्ग है जो Databaseइंटरफ़ेस को लागू करता है , इंटरफ़ेस बस उपयोगी हो गया।

2. अमूर्त परत का उपयोग करना

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

अब आपके पास एक इंटरफ़ेस है, जिसे आप सीधे नहीं बना सकते हैं (उह, मैं इसे कैसे उपयोग करता हूं), लेकिन आपके पास एक वर्ग है MyMySQLDatabase, जिसका निर्माण newकीवर्ड का उपयोग करके किया जा सकता है ।

महान! मैं एक डेटाबेस का उपयोग करना चाहता हूं, इसलिए मैं डेटाबेस का उपयोग करूंगा MyMySQLDatabase

आपकी विधि इस तरह दिख सकती है:

public void SecretMethod()
{
    var database = new MyMySQLDatabase(new CSharpMySQLDriver());

    // you will use the database here, which has the DoQuery,
    // BeginTransaction, RollbackTransaction and CommitTransaction methods
}

यह अच्छा नहीं है। आप सीधे इस पद्धति के अंदर एक वर्ग बना रहे हैं, और यदि आप इसे अंदर कर रहे हैं SecretMethod, तो यह मान लेना सुरक्षित है कि आप 30 अन्य तरीकों से भी ऐसा ही करेंगे। यदि आप MyMySQLDatabaseएक अलग वर्ग में परिवर्तन करना चाहते हैं , जैसे कि MyPostgreSQLDatabase, आपको इसे अपने सभी 30 तरीकों में बदलना होगा।

एक अन्य समस्या यह है कि यदि MyMySQLDatabaseअसफलता का निर्माण होता है, तो विधि कभी समाप्त नहीं होगी और इसलिए अमान्य होगी।

हम MyMySQLDatabaseइसे विधि के पैरामीटर के रूप में पारित करके निर्माण को फिर से शुरू करते हैं (इसे निर्भरता इंजेक्शन कहा जाता है)।

public void SecretMethod(MyMySQLDatabase database)
{
    // use the database here
}

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


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

एक और रीफैक्टरिंग का उद्देश्य

आप देख सकते हैं, अभी SecretMethodएक MyMySQLDatabaseवस्तु का उपयोग करता है । मान लेते हैं कि आप MySQL से MSSQL में चले गए हैं। आपको वास्तव में ऐसा महसूस नहीं होता है कि आपके अंदर के सभी तर्क बदल सकते हैं SecretMethod, एक ऐसा तरीका जो एक पैरामीटर के रूप में पारित चर पर BeginTransactionऔर CommitTransactionविधियों को कॉल databaseकरता है, इसलिए आप एक नया वर्ग बनाते हैं MyMSSQLDatabase, जिसमें BeginTransactionऔर CommitTransactionविधियां भी होंगी ।

फिर आप आगे बढ़ते हैं और SecretMethodनिम्नलिखित की घोषणा को बदलते हैं ।

public void SecretMethod(MyMSSQLDatabase database)
{
    // use the database here
}

और क्योंकि कक्षाएं MyMSSQLDatabaseऔर MyMySQLDatabaseसमान तरीके हैं, इसलिए आपको कुछ और बदलने की आवश्यकता नहीं है और यह अभी भी काम करेगा।

अरे रुको!

आपके पास एक Databaseइंटरफ़ेस है, जो MyMySQLDatabaseलागू होता है, आपके पास MyMSSQLDatabaseवर्ग भी होता है , जिसमें बिल्कुल वैसी ही विधियाँ होती हैं MyMySQLDatabase, जैसे शायद MSSQL ड्राइवर Databaseइंटरफ़ेस को लागू कर सकता है, इसलिए आप इसे परिभाषा में जोड़ते हैं।

public class MyMSSQLDatabase : Database { }

लेकिन क्या होगा अगर मैं भविष्य में, MyMSSQLDatabaseअब और उपयोग नहीं करना चाहता , क्योंकि मैंने PostgreSQL पर स्विच किया है? मुझे फिर से, की परिभाषा को बदलना होगा SecretMethod?

हाँ, आप करेंगे। और यह सही नहीं लगता है। अभी हम जानते हैं, कि MyMSSQLDatabaseऔर MyMySQLDatabaseएक ही तरीके का है और दोनों को लागू Databaseइंटरफ़ेस। तो आप SecretMethodइस तरह दिखने के लिए रिफ्लेक्टर करें।

public void SecretMethod(Database database)
{
    // use the database here
}

ध्यान दें, SecretMethodअब कोई नहीं जानता कि आप MySQL, MSSQL या PotgreSQL का उपयोग कर रहे हैं या नहीं। यह जानता है कि यह एक डेटाबेस का उपयोग करता है, लेकिन विशिष्ट कार्यान्वयन के बारे में परवाह नहीं करता है।

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

3. के वांछित कार्यान्वयन प्राप्त करना Database

आपको अभी भी फैसला करना है, जिसे कॉल करने से पहले SecretMethod, Databaseआप जिस इंटरफ़ेस को चाहते हैं (चाहे वह MySQL, MSSQL या PostgreSQL हो)। इसके लिए, आप फ़ैक्टरी डिज़ाइन पैटर्न का उपयोग कर सकते हैं।

public class DatabaseFactory
{
    private Config _config;

    public DatabaseFactory(Config config)
    {
        _config = config;
    }

    public Database getDatabase()
    {
        var databaseType = _config.GetDatabaseType();

        Database database = null;

        switch (databaseType)
        {
        case DatabaseEnum.MySQL:
            database = new MyMySQLDatabase(new CSharpMySQLDriver());
            break;
        case DatabaseEnum.MSSQL:
            database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
            break;
        case DatabaseEnum.PostgreSQL:
            database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
            break;
        default:
            throw new DatabaseDriverNotImplementedException();
            break;
        }

        return database;
    }
}

फ़ैक्टरी, जैसा कि आप देख सकते हैं, जानते हैं कि कौन सी डेटाबेस एक कॉन्फ़िगर फ़ाइल से उपयोग होती है (फिर से, Configक्लास आपका अपना कार्यान्वयन हो सकती है)।

आदर्श रूप से, आपके DatabaseFactoryअंदर अपने निर्भरता इंजेक्शन कंटेनर होगा। आपकी प्रक्रिया तब इस तरह दिख सकती है।

public class ProcessWhichCallsTheSecretMethod
{
    private DIContainer _di;
    private ClassWithSecretMethod _secret;

    public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
    {
        _di = di;
        _secret = secret;
    }

    public void TheProcessMethod()
    {
        Database database = _di.Factories.DatabaseFactory.getDatabase();
        _secret.SecretMethod(database);
    }
}

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

यदि, PostgreSQL का उपयोग करने के 3 सप्ताह बाद, आप MySQL पर वापस जाना चाहते हैं, तो आप एक एकल कॉन्फ़िगरेशन फ़ाइल खोलते हैं और DatabaseDriverफ़ील्ड के मान को इसमें से बदल DatabaseEnum.PostgreSQLदेते हैं DatabaseEnum.MySQL। और आप कर रहे हैं। अचानक एक बार एक लाइन बदलकर आपके बाकी एप्लिकेशन सही ढंग से MySQL का उपयोग करते हैं।


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


अपने जवाब से प्यार करो, डेविड। लेकिन इस तरह के सभी जवाबों की तरह, यह वर्णन करना कम हो जाता है कि कोई इसे कैसे व्यवहार में ला सकता है। असली समस्या अलग डेटाबेस इंजन में कॉल करने की क्षमता को दूर नहीं कर रही है, समस्या वास्तविक SQL सिंटैक्स है। DbQueryउदाहरण के लिए, अपनी वस्तु लें । मान लें कि ऑब्जेक्ट में SQL क्वेरी स्ट्रिंग को निष्पादित करने के लिए कोई सदस्य है, तो कोई उस सामान्य को कैसे बना सकता है?
DonBoitnott

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

2
समझ लिया। लेकिन मेरा वास्तव में मतलब था कि शाब्दिक अर्थ है। एक बार जब आपके पास आपकी सारगर्भित कक्षा होती है, और आप उस बिंदु पर पहुंच जाते हैं, जहाँ आप कॉल करना चाहते हैं _secret.SecretMethod(database);कि कोई व्यक्ति इस तथ्य के साथ कैसे काम करता है कि अब SecretMethodभी मुझे यह जानना है कि उचित SQL बोली का उपयोग करने के लिए मैं किस DB के साथ काम कर रहा हूँ? ? आपने उस तथ्य से अनभिज्ञ अधिकांश कोड को रखने के लिए बहुत मेहनत की है, लेकिन फिर 11 वें घंटे में, आपको फिर से पता होना चाहिए। मैं अभी इस स्थिति में हूं और यह पता लगाने की कोशिश कर रहा हूं कि अन्य लोगों ने इस समस्या को कैसे हल किया है।
डॉनबोइटनॉट

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

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