ฉันกำลังเขียนแอปพลิเคชัน C ++ แอปพลิเคชันส่วนใหญ่อ่านและเขียนข้อมูลที่จำเป็นและนี่ก็ไม่มีข้อยกเว้น ฉันสร้างการออกแบบระดับสูงสำหรับตัวแบบข้อมูลและตรรกะการทำให้เป็นอนุกรม คำถามนี้ขอให้ตรวจสอบการออกแบบของฉันโดยคำนึงถึงเป้าหมายเฉพาะเหล่านี้:
เพื่อให้มีวิธีที่ง่ายและยืดหยุ่นในการอ่านและเขียนข้อมูลในรูปแบบที่กำหนดเอง: raw binary, 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 แนวคิดคือการสรุปลำดับของข้อมูลจาก data ( 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, ซ็อกเก็ตหรืออะไรก็ได้ เมื่อความกังวลนั้นถูกห่อหุ้มในลอจิกการทำให้เป็นอนุกรมมันจะซับซ้อนมากขึ้น: มันจะต้องรู้ถึงชนิดของกระแสและตัวสร้างที่จะเรียก
นอกเหนือจากตัวเลือกนั้นฉันไม่แน่ใจว่าสิ่งที่จะเป็นวิธีที่ดีกว่าในการสร้างแบบจำลองวัตถุเหล่านี้ที่ง่ายยืดหยุ่นและช่วยป้องกันข้อผิดพลาดทางตรรกะในรหัสที่ใช้
กรณีการใช้งานด้วยซึ่งวิธีการแก้ปัญหาจะต้องบูรณาการคือการที่ง่ายแฟ้มกล่องโต้ตอบตัวเลือก ผู้ใช้เลือก "เปิด ... " หรือ "บันทึกเป็น ... " จากเมนูไฟล์และโปรแกรมจะเปิดหรือบันทึก WidgetDatabase นอกจากนี้ยังจะมีตัวเลือก "นำเข้า ... " และ "ส่งออก ... " สำหรับแต่ละวิดเจ็ต
เมื่อผู้ใช้เลือกไฟล์ที่จะเปิดหรือบันทึก wxWidgets จะส่งคืนชื่อไฟล์ ตัวจัดการที่ตอบสนองต่อเหตุการณ์นั้นจะต้องเป็นรหัสวัตถุประสงค์ทั่วไปที่ใช้ชื่อไฟล์, รับซีเรียลไลเซอร์และเรียกใช้ฟังก์ชั่นเพื่อทำการยกของหนัก เป็นการดีที่การออกแบบนี้จะทำงานหากโค้ดอื่นกำลังทำงานที่ไม่ใช่ไฟล์ I / O เช่นการส่ง WidgetDatabase ไปยังอุปกรณ์มือถือผ่านซ็อกเก็ต
วิดเจ็ตบันทึกเป็นรูปแบบของตัวเองหรือไม่? มันทำงานร่วมกับรูปแบบที่มีอยู่หรือไม่ ใช่ จากทั้งหมดที่กล่าวมา กลับไปที่กล่องโต้ตอบของไฟล์คิดถึง Microsoft Word Microsoft มีอิสระในการพัฒนารูปแบบ DOCX อย่างไรก็ตามพวกเขาต้องการภายในข้อ จำกัด บางอย่าง ในเวลาเดียวกัน Word ยังอ่านหรือเขียนรูปแบบดั้งเดิมและรูปแบบของบุคคลที่สาม (เช่น PDF) โปรแกรมนี้ไม่แตกต่าง: รูปแบบ "ไบนารี" ที่ฉันพูดถึงเป็นรูปแบบภายในที่ยังไม่ถูกกำหนดที่ออกแบบมาเพื่อความเร็ว ในขณะเดียวกันก็จะต้องสามารถอ่านและเขียนรูปแบบมาตรฐานแบบเปิดในโดเมนของตน (ไม่เกี่ยวข้องกับคำถาม) เพื่อให้สามารถทำงานกับซอฟต์แวร์อื่น ๆ ได้
ในที่สุดมี Widget เพียงประเภทเดียว มันจะมีวัตถุลูก แต่สิ่งเหล่านั้นจะได้รับการจัดการโดยตรรกะการทำให้เป็นอนุกรมนี้ โปรแกรมจะไม่โหลดทั้งวิดเจ็ตและเฟือง การออกแบบนี้เท่านั้นที่จะต้องมีความกังวลกับวิดเจ็ตและ WidgetDatabases