सी ++ क्रमांकन डिजाइन की समीक्षा


9

मैं C ++ एप्लिकेशन लिख रहा हूं। अधिकांश एप्लिकेशन डेटा उद्धरण को पढ़ना और लिखना आवश्यक है और यह कोई अपवाद नहीं है। मैंने डेटा मॉडल और क्रमांकन तर्क के लिए एक उच्च स्तरीय डिज़ाइन बनाया। यह प्रश्न इन विशिष्ट लक्ष्यों को ध्यान में रखते हुए मेरे डिजाइन की समीक्षा का अनुरोध कर रहा है:

  • कच्चे बाइनरी, XML, JSON, एट: मनमाने प्रारूपों में डेटा मॉडल पढ़ने और लिखने का एक आसान और लचीला तरीका है। अल। डेटा का प्रारूप डेटा से और साथ ही उस कोड से भी अलग होना चाहिए जो क्रमबद्धता का अनुरोध कर रहा है।

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

  • यह प्रोजेक्ट C ++ का उपयोग करता है। आप इसे प्यार करते हैं या नफरत करते हैं, भाषा का काम करने का अपना तरीका है और डिजाइन का उद्देश्य भाषा के साथ काम करना है, इसके खिलाफ नहीं

  • अंत में, इस परियोजना को wxWidgets के शीर्ष पर बनाया गया है । जब मैं एक अधिक सामान्य मामले के लिए लागू समाधान की तलाश कर रहा हूं, तो इस विशिष्ट कार्यान्वयन को उस टूलकिट के साथ अच्छी तरह से काम करना चाहिए।

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


सबसे पहले, कुछ नमूना DAO:

#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

// One widget represents one record in the application.
class Widget {
public:
  using id_type = int;
private:
  id_type id;
};

// Container for widgets. Much more than a dumb container,
// it will also have indexes and other metadata. This represents
// one data file the user may open in the application.
class WidgetDatabase {
  ::std::map<Widget::id_type, ::std::shared_ptr<Widget>> widgets;
};

अगला, मैं DAO पढ़ने और लिखने के लिए शुद्ध आभासी कक्षाएं (इंटरफेस) को परिभाषित करता हूं। विचार डेटा ( SRP ) से डेटा के क्रमांकन को अमूर्त करना है ।

class WidgetReader {
public:
  virtual Widget read(::std::istream &in) const abstract;
};

class WidgetWriter {
public:
  virtual void write(::std::ostream &out, const Widget &widget) const abstract;
};

class WidgetDatabaseReader {
public:
  virtual WidgetDatabase read(::std::istream &in) const abstract;
};

class WidgetDatabaseWriter {
public:
  virtual void write(::std::ostream &out, const WidgetDatabase &widgetDb) const abstract;
};

अंत में, यहां वह कोड है जो वांछित I / O प्रकार के लिए उचित रीडर / लेखक प्राप्त करता है। पाठकों / लेखकों के उपवर्ग भी परिभाषित होंगे, लेकिन ये डिज़ाइन समीक्षा में कुछ नहीं जोड़ते हैं:

enum class WidgetIoType {
  BINARY,
  JSON,
  XML
  // Other types TBD.
};

WidgetIoType forFilename(::std::string &name) { return ...; }

class WidgetIoFactory {
public:
  static ::std::unique_ptr<WidgetReader> getWidgetReader(const WidgetIoType &type) {
    return ::std::unique_ptr<WidgetReader>(/* TODO */);
  }

  static ::std::unique_ptr<WidgetWriter> getWidgetWriter(const WidgetIoType &type) {
    return ::std::unique_ptr<WidgetWriter>(/* TODO */);
  }

  static ::std::unique_ptr<WidgetDatabaseReader> getWidgetDatabaseReader(const WidgetIoType &type) {
    return ::std::unique_ptr<WidgetDatabaseReader>(/* TODO */);
  }

  static ::std::unique_ptr<WidgetDatabaseWriter> getWidgetDatabaseWriter(const WidgetIoType &type) {
    return ::std::unique_ptr<WidgetDatabaseWriter>(/* TODO */);
  }
};

मेरे डिजाइन के निर्धारित लक्ष्यों के अनुसार, मुझे एक विशिष्ट चिंता है। C ++ स्ट्रीम को टेक्स्ट या बाइनरी मोड में खोला जा सकता है, लेकिन पहले से ही ओपन स्ट्रीम को चेक करने का कोई तरीका नहीं है। यह एक XML या JSON रीडर / लेखक को बाइनरी स्ट्रीम प्रदान करने के लिए प्रोग्रामर त्रुटि के माध्यम से संभव हो सकता है। यह सूक्ष्म (या इतनी सूक्ष्म नहीं) त्रुटियों का कारण बन सकता है। मैं कोड को तेजी से विफल करना पसंद करूंगा, लेकिन मुझे यकीन नहीं है कि यह डिजाइन ऐसा करेगा।

इसका एक तरीका पाठक या लेखक को स्ट्रीम खोलने की जिम्मेदारी को उतारना हो सकता है, लेकिन मेरा मानना ​​है कि SRP का उल्लंघन करता है और कोड को अधिक जटिल बना देगा। DAO लिखते समय, लेखक को इस बात की परवाह नहीं करनी चाहिए कि धारा कहाँ जा रही है: यह एक फ़ाइल, मानक आउट, एक HTTP प्रतिक्रिया, एक सॉकेट, कुछ भी हो सकता है। एक बार जब यह चिंता क्रमिकता के तर्क में ढल जाती है तो यह और अधिक जटिल हो जाती है: यह विशिष्ट प्रकार की धारा को जानना चाहिए और किस निर्माता को कॉल करना चाहिए।

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


उपयोग मामला जिसके साथ समाधान को एकीकृत किया जाना चाहिए, एक साधारण फ़ाइल चयन संवाद बॉक्स है । उपयोगकर्ता फ़ाइल मेनू से "Open ..." या "इस रूप में सहेजें ..." का चयन करता है, और प्रोग्राम WidgetDatabase खोलता या सहेजता है। व्यक्तिगत विजेट के लिए "आयात ..." और "निर्यात ..." विकल्प भी होंगे।

जब उपयोगकर्ता फ़ाइल खोलने या सहेजने के लिए चयन करता है, तो wxWidgets एक फ़ाइल नाम लौटाएगा। उस घटना पर प्रतिक्रिया देने वाला हैंडलर सामान्य प्रयोजन कोड होना चाहिए जो फ़ाइल का नाम लेता है, एक धारावाहिक को प्राप्त करता है, और भारी उठाने के लिए एक फ़ंक्शन को कॉल करता है। आदर्श रूप से यह डिज़ाइन भी काम करेगा यदि कोई अन्य कोड नॉन-फ़ाइल I / O का प्रदर्शन कर रहा हो, जैसे कि सॉकेट के ऊपर एक मोबाइल डिवाइस पर एक WidgetDatabase भेजना।


क्या एक विजेट अपने स्वयं के प्रारूप को बचाता है? क्या यह मौजूदा प्रारूपों के साथ हस्तक्षेप करता है? हाँ! ऊपर के सभी। फ़ाइल संवाद पर वापस जाना, Microsoft Word के बारे में सोचें। Microsoft DOCX प्रारूप को विकसित करने के लिए स्वतंत्र थे, हालांकि वे कुछ बाधाओं के भीतर चाहते थे। उसी समय, Word विरासत या तृतीय-पक्ष प्रारूप (उदा PDF) को भी पढ़ता या लिखता है। यह कार्यक्रम अलग नहीं है: मैं जिस "बाइनरी" प्रारूप के बारे में बात करता हूं वह गति के लिए डिज़ाइन किया गया एक अभी तक परिभाषित आंतरिक प्रारूप है। उसी समय, इसे अपने डोमेन में खुले मानक स्वरूपों को पढ़ने और लिखने में सक्षम होना चाहिए (प्रश्न के लिए अप्रासंगिक) इसलिए यह अन्य सॉफ़्टवेयर के साथ काम करने में सक्षम हो सकता है।

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


1
क्या आपने इसके लिए बूस्ट सीरियललाइज़ेशन लाइब्रेरी का उपयोग करने पर विचार किया है? इसमें आपके द्वारा डिज़ाइन किए गए सभी लक्ष्य शामिल हैं।
बार्ट वैन इनगेन शेनौ

1
@BartvanIngenSchenau मेरे पास मुख्य रूप से बूस्ट के साथ प्यार / नफरत के रिश्ते के कारण नहीं था। मुझे लगता है कि इस मामले में मुझे जिन कुछ प्रारूपों का समर्थन करने की आवश्यकता है, वे अधिक जटिल हो सकते हैं, बूस्ट सीरियलाइज़ेशन पर्याप्त जटिलता को जोड़े बिना संभाल सकता है कि इसका उपयोग करने से मुझे ज्यादा खरीदना नहीं पड़ता है।

आह! तो आप विजेट उदाहरणों को क्रमबद्ध नहीं कर रहे हैं (डी-) (यह अजीब होगा ...), लेकिन इन विजेट्स को केवल संरचित डेटा पढ़ने और लिखने की आवश्यकता है? क्या आपको मौजूदा फ़ाइल स्वरूपों को लागू करना है, या क्या आप एक तदर्थ प्रारूप को परिभाषित करने के लिए स्वतंत्र हैं? क्या विभिन्न विगेट्स सामान्य या समान स्वरूपों का उपयोग करते हैं जिन्हें एक सामान्य मॉडल के रूप में लागू किया जा सकता है? आप तब एक WXWidget भगवान वस्तु के रूप में एक साथ सब कुछ munging के बजाय एक उपयोगकर्ता इंटरफ़ेस-डोमेन लॉजिक-मॉडल-डीएएल विभाजन कर सकते हैं। वास्तव में, मैं नहीं देखता कि विगेट्स यहां क्यों प्रासंगिक हैं।
amon

@ वामन ने प्रश्न को फिर से संपादित किया। wxWidgets केवल उपयोगकर्ता के साथ इंटरफेस के रूप में ही प्रासंगिक हैं: मैं जिस विजेट के बारे में बात करता हूं उसका wxWidgets फ्रेमवर्क (यानी कोई देव वस्तु) से कोई लेना-देना नहीं है। मैं सिर्फ एक प्रकार के DAO के लिए एक सामान्य नाम के रूप में उस शब्द का उपयोग करता हूं।

1
@LarsViklund आप एक सम्मोहक तर्क देते हैं और आपने इस मामले पर मेरी राय बदल दी है। मैंने उदाहरण कोड को अपडेट किया।

जवाबों:


7

मैं गलत हो सकता हूं, लेकिन आपका डिजाइन बुरी तरह से खत्म हो रहा है। सिर्फ एक क्रमानुसार करने Widgetके लिए, आप को परिभाषित करना चाहते WidgetReader, WidgetWriter, WidgetDatabaseReader, WidgetDatabaseWriterइंटरफेस है जो प्रत्येक एक्सएमएल, JSON, और बाइनरी एनकोडिंग, और एक कारखाने उन सभी वर्गों को एक साथ बांधने के लिए के लिए कार्यान्वयन की है। यह निम्नलिखित कारणों से समस्याग्रस्त है:

  • मैं एक गैर क्रमानुसार करने चाहते हैं Widgetवर्ग, चलो यह कॉल Fooमैं वर्गों के इस पूरे चिड़ियाघर reimplement करने के लिए है, और बनाने FooReader, FooWriter, FooDatabaseReader, FooDatabaseWriterइंटरफेस, बार प्रत्येक क्रमबद्धता प्रारूप, प्लस एक कारखाने के लिए तीन तो यह और भी दूर से प्रयोग करने योग्य बनाने के लिए। मुझे मत बताओ कि वहाँ कोई कॉपी और पेस्ट नहीं होगा! यह दहनशील विस्फोट काफी हद तक अप्राप्य प्रतीत होता है, भले ही उनमें से प्रत्येक वर्ग में अनिवार्य रूप से केवल एक ही विधि हो।

  • Widgetयथोचित एनकैप्सुलेट नहीं किया जा सकता है। या तो आप उन सभी चीजों को खोलते हैं जिन्हें गेट्टर विधियों के साथ खुली दुनिया में क्रमबद्ध किया जाना चाहिए, या आपको friendप्रत्येक और WidgetWriter(और शायद सभी भी WidgetReader) कार्यान्वयन करना होगा। या तो मामले में, आप क्रमिक कार्यान्वयन और के बीच काफी युग्मन पेश करेंगे Widget

  • पाठक / लेखक चिड़ियाघर विसंगतियों को आमंत्रित करता है। जब भी आप किसी सदस्य को जोड़ते हैं Widget, तो आपको उस सदस्य को संग्रहीत / पुनः प्राप्त करने के लिए सभी संबंधित क्रमांकन वर्गों को अपडेट करना होगा। यह कुछ ऐसा है जिसे सही ढंग से सही करने के लिए जाँच नहीं की जा सकती है, इसलिए आपको प्रत्येक पाठक और लेखक के लिए एक अलग परीक्षा लिखनी होगी। आपके वर्तमान डिज़ाइन पर, वह 4 * 3 = 12 परीक्षण प्रति कक्षा है जिसे आप क्रमबद्ध करना चाहते हैं।

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

  • शायद Widgetअब यह SRP को संतुष्ट करता है क्योंकि यह क्रमांकन के लिए जिम्मेदार नहीं है। लेकिन पाठक और लेखक कार्यान्वयन स्पष्ट रूप से नहीं करते हैं, "SRP = प्रत्येक वस्तु के पास बदलने का एक कारण है" व्याख्या: कार्यान्वयन या तो परिवर्तन होना चाहिए जब या तो क्रमांकन प्रारूप बदलता है, या जब Widgetपरिवर्तन होता है।

यदि आप पहले से कम से कम समय का निवेश करने में सक्षम हैं, तो कृपया कक्षाओं के इस तदर्थ की तुलना में अधिक सामान्य क्रमबद्ध रूपरेखा तैयार करने का प्रयास करें। उदाहरण के लिए, आप एक सामान्य इंटरचेंज प्रतिनिधित्व को परिभाषित कर सकते हैं, चलो इसे SerializationInfoजावास्क्रिप्ट-जैसे ऑब्जेक्ट मॉडल के साथ कहते हैं: अधिकांश वस्तुओं को ए std::map<std::string, SerializationInfo>, या ए std::vector<SerializationInfo>, या आदिम के रूप में देखा जा सकता है int

प्रत्येक क्रमांकन प्रारूप के लिए, आपके पास एक वर्ग होगा जो उस धारा से क्रमांकन प्रतिनिधित्व पढ़ने और लिखने का प्रबंधन करता है। और प्रत्येक वर्ग के लिए जिसे आप क्रमबद्ध करना चाहते हैं, आपके पास कुछ तंत्र होगा जो उदाहरणों को / से क्रमांकन प्रतिनिधित्व में परिवर्तित करता है।

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

class Point {
  int _x;
  int _y;
public:
  Point(x, y) : _x(x), _y(y) {}
  int x() const { return _x; }
  int y() const { return _y; }
};

void operator <<= (SerializationInfo& si, const Point& p) {
  si.addMember("x") <<= p.x();
  si.addMember("y") <<= p.y();
}

void operator >>= (const SerializationInfo& si, Point& p) {
  int x;
  si.getMember("x") >>= x;  // will throw if x entry not found
  int y;
  si.getMember("y") >>= y;
  p = Point(x, y);
}

int main() {
  // cxxtools::Json<T>(T&) wrapper sets up a SerializationInfo and manages Json I/O
  // wrappers for other formats also exist, e.g. cxxtools::Xml<T>(T&)

  Point a(42, -15);
  std::cout << cxxtools::Json(a);
  ...
  Point b(0, 0);
  std::cin >> cxxtools::Json(p);
}

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

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


आपकी सलाह कैसे दी जाएगी कि ये डीएओ मूल रूप से एक "क्रमांकन जानकारी" वर्ग हैं? ये C ++ POJOs के समकक्ष हैं । मैं अपने प्रश्न को थोड़ा और जानकारी के साथ संपादित करने जा रहा हूं कि इन वस्तुओं का उपयोग कैसे किया जाएगा।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.