कालेब का जवाब, जबकि वह सही रास्ते पर है, वास्तव में गलत है। उनकी 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
_di
Database
यदि, PostgreSQL का उपयोग करने के 3 सप्ताह बाद, आप MySQL पर वापस जाना चाहते हैं, तो आप एक एकल कॉन्फ़िगरेशन फ़ाइल खोलते हैं और DatabaseDriver
फ़ील्ड के मान को इसमें से बदल DatabaseEnum.PostgreSQL
देते हैं DatabaseEnum.MySQL
। और आप कर रहे हैं। अचानक एक बार एक लाइन बदलकर आपके बाकी एप्लिकेशन सही ढंग से MySQL का उपयोग करते हैं।
यदि आप अभी भी चकित नहीं हैं, तो मैं आपको आईओसी में थोड़ा और गोता लगाने की सलाह देता हूं। आप एक कॉन्फ़िगरेशन से नहीं, बल्कि उपयोगकर्ता इनपुट से कुछ निर्णय कैसे ले सकते हैं। इस aproach को स्ट्रैटिजी पैटर्न कहा जाता है और हालाँकि इसे एंटरप्राइज़ एप्लिकेशन में उपयोग किया जा सकता है और इसका उपयोग कंप्यूटर गेम को विकसित करने में अक्सर किया जाता है।
DbQuery
उदाहरण के लिए, अपनी वस्तु लें । मान लें कि ऑब्जेक्ट में SQL क्वेरी स्ट्रिंग को निष्पादित करने के लिए कोई सदस्य है, तो कोई उस सामान्य को कैसे बना सकता है?