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 응답, 소켓 등이 될 수 있습니다. 이러한 문제가 직렬화 논리에 캡슐화되면 훨씬 더 복잡해집니다. 특정 유형의 스트림과 호출 할 생성자를 알아야합니다.
이 옵션 외에도 단순하고 유연하며 이러한 객체를 사용하는 코드에서 논리 오류를 방지하는 데 도움이되는 이러한 객체를 모델링하는 더 좋은 방법이 무엇인지 잘 모르겠습니다.
솔루션을 통합해야하는 사용 사례는 간단한 파일 선택 대화 상자 입니다. 사용자는 파일 메뉴에서 "열기 ..."또는 "다른 이름으로 저장 ..."을 선택하고 프로그램에서 WidgetDatabase를 열거 나 저장합니다. 개별 위젯에 대한 "가져 오기 ..."및 "내보내기 ..."옵션도 있습니다.
사용자가 열거 나 저장할 파일을 선택하면 wxWidgets가 파일 이름을 리턴합니다. 해당 이벤트에 응답하는 처리기는 파일 이름을 가져오고 serializer를 획득 한 후 무거운 리프팅을 수행하는 함수를 호출하는 범용 코드 여야합니다. 소켓을 통해 WidgetDatabase를 모바일 디바이스로 전송하는 것과 같이 다른 파일이 비 파일 I / O를 수행하는 경우에도이 디자인이 작동합니다.
위젯이 자체 형식으로 저장됩니까? 기존 형식과 상호 운용됩니까? 예! 무엇보다도. 파일 대화 상자로 돌아가서 Microsoft Word에 대해 생각해보십시오. Microsoft는 DOCX 형식을 자유롭게 개발할 수 있었지만 특정 제약 조건 내에서 원했습니다. 동시에 Word는 레거시 및 타사 형식 (예 : PDF)을 읽거나 씁니다. 이 프로그램은 다르지 않습니다. 제가 이야기하는 "이진"형식은 속도를 위해 아직 정의되지 않은 내부 형식입니다. 동시에 다른 소프트웨어와 작업 할 수 있도록 도메인에서 공개 표준 형식을 읽고 쓸 수 있어야합니다 (질문과 무관).
마지막으로 한 가지 유형의 위젯 만 있습니다. 자식 개체가 있지만이 직렬화 논리에 의해 처리됩니다. 프로그램은 위젯 과 스프로킷을 모두로드하지 않습니다 . 이 디자인 은 위젯 및 위젯 데이터베이스 에만 관심이 있으면됩니다.