2014-12-31 6 views
8

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

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

मैंने डेटा एक्सेस ऑब्जेक्ट्स को देखा है लेकिन वे बहुत अच्छी तरह से परिभाषित नहीं हैं और मुझे कार्यान्वयन के किसी भी अच्छे उदाहरण नहीं मिल रहे हैं (जावा या इसी तरह की भाषा में)। मेरे पास टैब ऑफ पूरा करने के लिए उपयोगकर्ता नाम ढूंढने जैसे मामलों में से कई भी हैं जो उचित नहीं लगते हैं और डीएओ को बड़ा और फुलाएंगे।

क्या कोई डिज़ाइन पैटर्न है जो बिना डेटाबेस विशिष्ट होने के ऑब्जेक्ट्स को प्राप्त करने और सहेजने में मदद करेगा? कार्यान्वयन के अच्छे उदाहरण सहायक होंगे (अधिमानतः जावा में)।

उत्तर

33

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

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

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

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

public interface GreatDAO { 
    User getUser(int id); 
    void saveUser(User u); 
} 
public class TheGreatestDAO implements GreatDAO { 
    protected TheGeatestDAO(){} 
    ... 
} 
public class GreatDAOFactory { 
    private static GreatDAO dao = null; 
    protected static synchronized GreatDao setDAO(GreatDAO d) { 
     GreatDAO old = dao; 
     dao = d; 
     return old; 
    } 
    public static synchronized GreatDAO getDAO() { 
     return dao == null ? dao = new TheGreatestDAO() : dao; 
    } 
} 

public class App { 
    void setUserName(int id, String name) { 
      GreatDAO dao = GreatDAOFactory.getDao(); 
      User u = dao.getUser(id); 
      u.setName(name); 
      dao.saveUser(u); 
    } 
} 

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

इस दृष्टिकोण के साथ, तुम सब करने की जरूरत है GreatDAOFactory.getDAO() बदल सकते हैं और यह एक अलग वर्ग का एक उदाहरण बनाने बनाने के लिए है, और अपने सभी आवेदन कोड बिना किसी परिवर्तन के नए डेटाबेस का उपयोग कर सकेंगे।

वास्तविक जीवन में, यह अक्सर कोड में किसी भी बदलाव के बिना किया जाता है: फैक्ट्री विधि को संपत्ति सेटिंग के माध्यम से कार्यान्वयन कक्षा का नाम मिलता है, और प्रतिबिंब का उपयोग करके इसे तुरंत चालू करता है, इसलिए, आपको कार्यान्वयन स्विच करने के लिए केवल इतना करना है एक संपत्ति फ़ाइल संपादित है।वास्तव में ढांचे हैं - spring या guice - जो आपके लिए इस "निर्भरता इंजेक्शन" तंत्र का प्रबंधन करते हैं, लेकिन मैं विवरण में नहीं जाऊंगा, क्योंकि यह वास्तव में आपके प्रश्न के दायरे से बाहर है, और यह भी कि क्योंकि मैं नहीं हूं जरूरी रूप से आश्वस्त है कि उन ढांचे का उपयोग करने से प्राप्त लाभ अधिकांश अनुप्रयोगों के लिए उनके साथ एकीकृत समस्या के लायक है।

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

ऐसा करने के लिए, आप एक परीक्षण ढांचे, Mockito की तरह, कि पूर्वनिर्धारित व्यवहार के साथ, आप किसी भी वस्तु या विधि की कार्यक्षमता "बाहर नकली" करने के लिए, एक "डमी" वस्तु और इसकी जगह की अनुमति देता है चाहता हूँ (मैं छोड़ देंगे विवरण, क्योंकि, यह फिर से दायरे से बाहर है)। तो, आप अपने डीएओ को प्रतिस्थापित करने के लिए इस डमी ऑब्जेक्ट को बना सकते हैं, और GreatDAOFactory को परीक्षण से पहले GreatDAOFactory.setDAO(dao) पर कॉल करके और इसे बाद में बहाल करके वास्तविक चीज़ की बजाय अपनी डमी वापस कर सकते हैं। यदि आप इंस्टेंस क्लास के बजाय स्थिर विधियों का उपयोग कर रहे थे, तो यह संभव नहीं होगा।

एक और लाभ, जो ऊपर वर्णित डेटाबेस स्विचिंग के समान है, अतिरिक्त कार्यक्षमता के साथ आपके दाओ को "पंपिंग" कर रहा है। मान लीजिए कि आपका एप्लिकेशन धीमा हो गया है क्योंकि डेटाबेस में डेटा की मात्रा बढ़ती है, और आप तय करते हैं कि आपको कैश परत की आवश्यकता है। एक रैपर वर्ग को कार्यान्वित करें, जो डाटाबेस तक पहुंचने के लिए असली दाओ इंस्टेंस (इसे कन्स्ट्रक्टर पैराम के रूप में प्रदान किया जाता है) का उपयोग करता है, और स्मृति में पढ़ने वाली वस्तुओं को कैश करता है, ताकि उन्हें तेज़ी से वापस किया जा सके। एप्लिकेशन को इसका लाभ उठाने के लिए आप अपने GreatDAOFactory.getDAO को इस रैपर को तुरंत चालू कर सकते हैं।

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

एक समान रूप से मोटे तौर पर उपयोग (लेकिन, मेरी राय में, अवर) "फैक्टरी" विधि के लिए वैकल्पिक dao सभी वर्गों यह जरूरत है कि में एक सदस्य चर बना रही है:

public class App { 
    GreatDao dao; 
    public App(GreatDao d) { dao = d; } 
} 

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

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

+0

मैं वास्तव में डिस्क/डेटाबेस को जारी रखने के लिए सफलतापूर्वक काम करने के लिए ऑपरेशन को सहेजने/हटाने के लिए ''' बूलियन'' 'वापस कर दूंगा। – ambodi

+1

नहीं, अगर आप किसी भी तरह विफल हो जाते हैं तो आप अपवाद फेंकना चाहते हैं: यह आपको कॉलर को विफलता के बारे में बहुत मूल्यवान जानकारी संवाद करने की अनुमति देता है, केवल झूठी वापसी की तुलना में, और कॉलर पक्ष पर त्रुटि को कम बोझिल बनाता है : प्रत्येक कॉल की स्थिति की जांच करने और इसे ढेर तक सभी तरह से प्रचारित करने के बजाय, उन्हें केवल उस स्तर पर अपवाद पकड़ना होगा जहां इसे संभालना होगा। – Dima

+1

यह एक अंतर्निहित उत्तर है, अच्छी तरह से किया गया है! एक प्रश्न, 'सेटडाओ' ने कुछ भी वापस करने की बजाए पुराने/पिछले डीएओ को क्यों वापस नहीं किया? इसका उपयोग कैसे किया जाएगा? –

संबंधित मुद्दे