OOP आवेदन में पैरामीटर प्रबंधन


15

मैं OOP सिद्धांतों का अभ्यास करने के तरीके के रूप में C ++ में एक मध्यम आकार का OOP एप्लिकेशन लिख रहा हूं।

मेरी परियोजना में मेरी कई कक्षाएं हैं, और उनमें से कुछ को रन-टाइम कॉन्फ़िगरेशन मापदंडों तक पहुंचने की आवश्यकता है। इन मापदंडों को एप्लिकेशन स्टार्ट-अप के दौरान कई स्रोतों से पढ़ा जाता है। कुछ को होम-डीआईआर में एक कॉन्फ़िगर फ़ाइल से पढ़ा जाता है, कुछ कमांड लाइन तर्क (argv) हैं।

इसलिए मैंने एक क्लास बनाई ConfigBlock। यह वर्ग सभी पैरामीटर स्रोतों को पढ़ता है और इसे एक उपयुक्त डेटा संरचना में संग्रहीत करता है। उदाहरण पथ हैं- और फ़ाइलनाम जो कि उपयोगकर्ता द्वारा कॉन्फ़िगर फ़ाइल, या --verbose CLI ध्वज में बदले जा सकते हैं। फिर, कोई व्यक्ति ConfigBlock.GetVerboseLevel()इस विशिष्ट पैरामीटर को पढ़ने के लिए कॉल कर सकता है ।

मेरा प्रश्न: क्या ऐसे सभी रनटाइम कॉन्फिग डेटा को एक कक्षा में इकट्ठा करना अच्छा है?

फिर, मेरी कक्षाओं को इन सभी मापदंडों तक पहुंच की आवश्यकता है। मैं इसे प्राप्त करने के कई तरीकों के बारे में सोच सकता हूं, लेकिन मुझे यकीन नहीं है कि कौन सा लेना है। एक क्लास 'कंस्ट्रक्टर को मेरे कॉन्फिगरब्लॉक की तरह दिया जा सकता है

public:
    MyGreatClass(ConfigBlock &config);

या वे सिर्फ एक शीर्षक "CodingBlock.h" शामिल करते हैं जिसमें मेरे कोडिंगब्लॉक की एक परिभाषा होती है:

extern CodingBlock MyCodingBlock;

उसके बाद, केवल .cpp फ़ाइल में कॉन्फ़िगब्लॉक सामान को शामिल करने और उसका उपयोग करने की आवश्यकता होती है।
.H फ़ाइल इस इंटरफ़ेस को क्लास के उपयोगकर्ता के लिए प्रस्तुत नहीं करती है। हालाँकि, कॉन्फ़िगब्लॉक का इंटरफ़ेस अभी भी है, हालाँकि, यह .h फ़ाइल से छिपा हुआ है।

क्या इसे इस तरह छिपाना अच्छा है?

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

जवाबों:


10

मैं काफी व्यावहारिक हूँ, लेकिन यहाँ मेरी मुख्य चिंता यह है कि आप इसे ConfigBlockअपने इंटरफ़ेस डिज़ाइन को संभवतः खराब तरीके से हावी होने दे सकते हैं । जब आपके पास ऐसा कुछ हो:

explicit MyGreatClass(const ConfigBlock& config);

... एक अधिक उपयुक्त इंटरफ़ेस इस तरह हो सकता है:

MyGreatClass(int foo, float bar, const string& baz);

... के रूप में सिर्फ चेरी के लिए इन foo/bar/bazक्षेत्रों में एक बड़े पैमाने पर लेने का विरोध किया ConfigBlock

आलसी इंटरफ़ेस डिजाइन

प्लस साइड पर, इस तरह का डिज़ाइन आपके कंस्ट्रक्टर के लिए एक स्थिर इंटरफ़ेस डिज़ाइन करना आसान बनाता है, उदाहरण के लिए, यदि आप कुछ नया करना चाहते हैं, तो आप बस इसे लोड कर सकते हैं ConfigBlock(संभवतः बिना किसी कोड परिवर्तन के) और फिर चेरी- किसी भी प्रकार के इंटरफ़ेस परिवर्तन के बिना आपको जो भी नया सामान चाहिए, उसे लागू करें MyGreatClass

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

इसलिए निश्चित रूप से कुछ पेशेवरों के यहाँ हैं, लेकिन वे विपक्ष द्वारा भारी पड़ सकते हैं।

युग्मन

इस परिदृश्य में, ऐसे सभी वर्गों को एक ConfigBlockउदाहरण से निर्मित किया जा रहा है, जिनकी निर्भरता इस प्रकार है:

यहाँ छवि विवरण दर्ज करें

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

किसी भी तरह के नए संदर्भ में (चाहे इकाई परीक्षण या पूरी नई परियोजना), किसी भी तरह की कक्षाएं (पुनः) उपयोग के बोझ को और अधिक बढ़ा सकती हैं, जैसा कि हम अंत ConfigBlockमें सवारी के लिए हमेशा साथ लाते हैं , और इसे स्थापित करते हैं। तदनुसार।

पुनर्प्रयोग / Deployability / Testability

इसके बजाय यदि आप इन इंटरफेस को उचित रूप से डिज़ाइन करते हैं, तो हम उन्हें ConfigBlockइस तरह से समाप्त कर सकते हैं और कुछ इस तरह से समाप्त कर सकते हैं :

यहाँ छवि विवरण दर्ज करें

यदि आप इस उपरोक्त आरेख में देखें, तो सभी वर्ग स्वतंत्र हो जाते हैं (उनके अभिवाही / बाहर जाने वाले युग्मन 1 से कम हो जाते हैं)।

यह बहुत अधिक स्वतंत्र वर्गों (कम से कम स्वतंत्र ConfigBlock) की ओर जाता है जो नए परिदृश्यों / परियोजनाओं में उपयोग (परीक्षण) / परीक्षण के लिए बहुत आसान हो सकता है।

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

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

निष्कर्ष

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

अंतिम पर कम नहीं:

extern CodingBlock MyCodingBlock;

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

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


1

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

जब से मैं भाषा को बेहतर जानता हूं, मैं C # का उपयोग करूंगा, लेकिन इसे C ++ में आसानी से स्थानांतरित किया जा सकता है।

public class ConfigBlock
{
    public ConfigBlock()
    {
        // Load config data and
        // connectionSettings = new ConnectionConfig();
        // connectionSettings...
    }

    private ConnectionConfig connectionSettings;

    public ConnectionConfig GetConnectionSettings()
    {
        return connectionSettings;
    }
}

public class FactoryProvider
{
    public FactoryProvider(ConfigBlock config)
    {
        this.config = config;
    }

    private ConfigBlock config;

    public ConnectionFactory GetConnectionFactory()
    {
        ConnectionConfig connectionSettings = config.GetConnectionSettings();

        return new ConnectionFactory(connectionSettings);
    }
}

public class ConnectionFactory
{
    public ConnectionFactory(ConnectionConfig settings)
    {
        this.settings = settings;
    }

    private ConnectionConfig settings;

    public Connection GetConnection()
    {
        return new Connection(settings.Hostname, settings.Port, settings.Username, settings.Password);
    }
}

उसके बाद आपको कुछ प्रकार के वर्ग की आवश्यकता होती है जो "एप्लिकेशन" के रूप में कार्य करता है जो आपकी मुख्य प्रक्रिया में तुरंत हो जाता है:

// Your main procedure (yeah I'm bending the rules of C# a tad here,
// but you get the point).
int Main(string[] args)
{
    Application app = new Application();

    app.Run();
}

public class Application
{
    public Application()
    {
        config = new ConfigBlock();
        factoryProvider = new FactoryProvider(config);
    }

    private ConfigBlock config;
    private FactoryProvider factoryProvider;

    public void Run()
    {
        ConnectionFactory connections = factoryProvider.GetConnectionFactory();
        Connection connection = connections.GetConnection();

        connection.Connect();

        // Enter into your main loop and do what this program is meant to do
    }
}

अंतिम नोट के रूप में, इसे .NET बोल में "प्रदाता वस्तु" के रूप में जाना जाता है। .NET में प्रदाता ऑब्जेक्ट फैक्ट्री ऑब्जेक्ट्स के लिए कॉन्फ़िगरेशन डेटा से शादी करते हैं, जो अनिवार्य रूप से आप यहां करना चाहते हैं।

शुरुआती के लिए प्रदाता पैटर्न भी देखें । फिर से, इसे .NET विकास की ओर बढ़ाया जाता है, लेकिन C # और C ++ दोनों ऑब्जेक्ट ओरिएंटेड भाषाओं के होने के साथ, पैटर्न ज्यादातर दोनों के बीच हस्तांतरणीय होना चाहिए।

एक और अच्छा पढ़ा जो इस पैटर्न से संबंधित है: प्रदाता मॉडल

अंत में, इस पैटर्न की एक आलोचना: प्रदाता एक पैटर्न नहीं है


प्रदाता मॉडल के लिंक को छोड़कर सभी अच्छा है। प्रतिबिंब c ++ द्वारा समर्थित नहीं है, और यह काम नहीं करेगा।
B:54овиЈ

@ B @овиЈ: सही है। वर्ग प्रतिबिंब मौजूद नहीं है, हालांकि आप एक मैनुअल वर्कअराउंड में बना सकते हैं, जो मूल रूप से एक switchस्टेटमेंट या एक ifस्टेटमेंट टेस्टिंग है जो कॉन्फिगर फाइल से पढ़े गए वैल्यू के खिलाफ है।
ग्रेग बरगद

0

पहला सवाल: क्या ऐसे सभी रनटाइम कॉन्फिग डेटा को एक कक्षा में इकट्ठा करना अच्छा है?

हाँ। रनटाइम स्थिरांक और मूल्यों को केंद्रीकृत करना और उन्हें पढ़ने के लिए कोड बेहतर है।

एक क्लास 'कंस्ट्रक्टर मेरे कॉन्फिगरब्लॉक का एक संदर्भ हो सकता है

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

पुराना कोड (आपका प्रस्ताव):

MyGreatClass(ConfigBlock &config);

नया कोड:

struct GreatClassData {/*...*/}; // initialization data for MyGreatClass
GreatClassData ConfigBlock::great_class_values();

तत्काल MyGreatClass:

auto x = MyGreatClass{ current_config_block.great_class_values() };

यहां, current_config_blockआपकी ConfigBlockकक्षा का उदाहरण है (वह जिसमें आपके सभी मूल्य हैं) और MyGreatClassवर्ग को एक GreatClassDataउदाहरण मिलता है । दूसरे शब्दों में, केवल उस डेटा को बनाने के लिए कंस्ट्रक्टरों के पास जाते हैं और ConfigBlockउस डेटा को बनाने के लिए आपके लिए सुविधाएं जोड़ते हैं ।

या वे सिर्फ एक शीर्षक "CodingBlock.h" शामिल करते हैं जिसमें मेरे कोडिंगब्लॉक की एक परिभाषा होती है:

 extern CodingBlock MyCodingBlock;

उसके बाद, केवल .cpp फ़ाइल में कॉन्फ़िगब्लॉक सामान को शामिल करने और उसका उपयोग करने की आवश्यकता होती है। .H फ़ाइल इस इंटरफ़ेस को क्लास के उपयोगकर्ता के लिए प्रस्तुत नहीं करती है। हालाँकि, कॉन्फ़िगब्लॉक का इंटरफ़ेस अभी भी है, हालाँकि, यह .h फ़ाइल से छिपा हुआ है। क्या इसे इस तरह छिपाना अच्छा है?

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

इसके अलावा, अपने ग्राहक वर्गों (अपने MyGreatClass) के प्रकार से टाई न करें CodingBlock; इसका मतलब है कि, यदि आपका MyGreatClassस्ट्रिंग और पाँच पूर्णांकों में ले जाता है, तो आप उस स्ट्रिंग और पूर्णांक में पास होने से बेहतर होंगे, जब आप एक में से गुजर रहे होंगे CodingBlock


मुझे लगता है कि यह विन्यास से कारखानों को डिकोड करने के लिए एक अच्छा विचार है। यह असंतोषजनक है कि कॉन्फ़िगरेशन कार्यान्वयन को पता होना चाहिए कि घटकों को कैसे त्वरित किया जाए, क्योंकि यह आवश्यक रूप से 2-तरह की निर्भरता में परिणाम देता है जहां पहले, केवल 1-तरफ़ा निर्भरता मौजूद थी। आपके कोड का विस्तार करते समय इसके बड़े निहितार्थ हैं, खासकर जब साझा पुस्तकालयों का उपयोग करते हुए जहां इंटरफेस वास्तव में मायने रखते हैं
जोएल कॉर्नेट

0

संक्षिप्त जवाब:

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


इस तरह मैं एक केंद्रीय स्थान में पार्सर कोड (पार्स कमांड लाइन और कॉन्फिग फाइल) इकट्ठा कर सकता हूं। फिर, प्रत्येक वर्ग वहां से अपने प्रासंगिक मापदंडों को चुन सकता है। आपकी राय में एक अच्छा डिजाइन क्या है?
lugge86

हो सकता है कि मैंने अभी इसे गलत लिखा है - मेरा मतलब है कि आपके पास (और इसकी एक अच्छी प्रथा है) विन्यास फाइल / पर्यावरण चर से प्राप्त सभी सेटिंग्स के साथ एक सामान्य अमूर्तता है - जो आपकी ConfigBlockकक्षा हो सकती है । यहाँ बिंदु सभी प्रदान करने के लिए नहीं है, इस मामले में, सिस्टम स्थिति का संदर्भ, विशेष रूप से, ऐसा करने के लिए आवश्यक मान।
दाविद पुरा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.