Как записать данные типа A в формат типа B - PullRequest
2 голосов
/ 06 мая 2011

Я реализую вещь, которая генерирует результаты и записывает их в файл определенного формата.

Довольно просто, но я хочу, чтобы это было динамически.

Я скину внизнесколько классов.

Данные - базовый класс для всех результатов
DataFile - базовый класс для всехТипы форматов файлов, имеет метод addData(Data * data)

ErrorData - выводится из данных, содержит данные об ошибке.
InfoData - получено из Data, содержит общую информацию.

XmlFile - получено из DataFile, содержит данные в формате XML.
BinaryFile - полученный из DataFile, содержит данные в двоичном формате.

У меня такой вопрос:

Куда я помещаю реализацию о том, как писать ErrorData в XmlFile ?

Что мне не нравится видеть в ответе:

  1. Новый мфункция ember в данные , файл данных , ErrorData или XmlFile (потому что это означает, что мне нужно добавлять их каждый раз, когда я добавляю новые данных или DataFile производный класс)
  2. Приведение типов к производным типам.
  3. Общее уродство:)

Я знаю свою базовую виртуализацию C ++ и все, не нужно быть сверхспецифичным.

Хотя я ценю несколько фрагментов кода, они оставляют меньше неопределенности.

У меня были некоторые мысли по поводу создания базового класса DataWriter инаследовать классы от того, кто знает, как записать Данные определенного типа в DataFile определенного типа, но янемного неуверен в деталях этого.

РЕДАКТИРОВАТЬ:

Я поясню немного больше в форме примера.

Давайте иметь 2 новых формата файлов, FormatATxtFile и FormatBTxtFile .

Предположим, что у нас есть объект InfoData и он имеетпараметры:

Номер строки сообщения: 34
Содержание сообщения: Hello World

Объект, записанный в FormatATxtFile , в файле выглядит так:

Строка: 34; Txt: Hello World; Тип: Info

И в FormatBTxtFile это будет выглядеть примерно так:

@ Info, 34, Hello World

Это своего рода способ экспорта данных в другой формат.Мне не нужно импортировать, по крайней мере, сейчас.

Как будет выглядеть код, использующий это:

DataFile * file = DataFileFactory::createFile(type);

std::vector<Data*> data = generateData();

file->setData(data);
file->writeTo("./FileName"); // correct end is added by DataFile type, ie .txt

Редактировать:

Кажется, я этого не делалпроясните, какие проблемы возникают с Xml и двоичными форматами файлов.Извините.

Давайте использовать тот же объект InfoData , как указано выше, и перенести его в формат XmlFile.Это может привести к созданию чего-то подобного в определенном элементе:

<InfoLog>
    <Info Line="34">Hello World</Info>
</InfoLog>

Предположим, что класс ErrorData будет иметь следующие параметры:

Номер строки ошибки: 56
Текст ошибки: LINK: фатальная ошибка LNK1168
10 Строки до сообщения об ошибке: text1 ...
10 Строки после сообщения об ошибке: text2 ...
Весь журнал о том, что произошло: text3 ...

Теперь, когда это помещено в формат XML, это должно быть что-то совершенно другое.

<Problems>
    <Error>
        <TextBefore>text1...</TextBefore>
        <Text line = 56>
            LINK : fatal error LNK1168
        </Text>
        <TextAfter>text1...</TextAfter>
    </Error>
    ...
</Problems>

Весьфайл может выглядеть примерно так:

<Operation>
    <InfoLog>
        <Info Line="34">Hello World</Info>
        <Info Line="96">Goodbye cruel World</Info>
    </InfoLog>
    <Problems>
        <Error>
            <TextBefore>text1...</TextBefore>
            <Text line = 56>
                LINK : fatal error LNK1168
            </Text>
            <TextAfter>text1...</TextAfter>
        </Error>
        <Error>
            <TextBefore>sometext</TextBefore>
            <Text line = 59>
                Out of cheese error
            </Text>
            <TextAfter>moretext</TextAfter>
        </Error>
    </Problems>
</Operation>

Ответы [ 4 ]

2 голосов
/ 06 мая 2011

Делайте так, как это делает стандартная библиотека - используйте virtual функции / операторы.Мы все можем использовать istream& и извлекать из него то, что нам нужно, с помощью operator>>, в то время как нам совершенно безразличен основной поток, будь то cin, fstream или stringstream.И вместо этого возьмите data по ссылке (Data& data).

2 голосов
/ 06 мая 2011

Вместо того, чтобы пытаться найти место для помещения этого в класс, как насчет новой функции?

void copyData(const ErrorData *data, DataFile *output)
{
    // ...
}

Затем вы можете перегрузить эту функцию для любых типов данных, которые вы хотите преобразовать.

В качестве альтернативы вы можете использовать шаблон:

template<typename A, typename B> copyData(const A *data, const B *output);

Затем вы можете специализировать шаблон для определенных типов, которые вам нужно поддерживать.

1 голос
/ 06 мая 2011

Я немного поигрался с вашим вопросом, и вот что я придумал:

#include <iostream>
#include <list>
#include <map>
#include <string>

using namespace std;

class DataFile;

class Data {
public:
    virtual void serializeTo(DataFile*) = 0;
};

class DataFile {
public:
    void addData(Data* d) {
        _data.push_back(d);
    }
    virtual void accept(string paramName, string paramValue) {
        _map[paramName] = paramValue;
    }

    virtual void writeTo(string const& filename) = 0;

protected:
    list<Data*> _data;
    map<string, string> _map;
};

class FormatATxtFile: public DataFile {
public:
    void writeTo(string const& filename) {
        cout << "writing to " << filename << ".txt:" << endl;
        for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
            (*it)->serializeTo(this);       

            cout << "Line:" << _map["Line"] << "; "
                << "Txt:" << _map["Txt"] << "; "
                << "Type: " << _map["Type"]
                << endl;
        }
        cout << endl;
    }
};

class FormatBTxtFile: public DataFile {
public:
    void writeTo(string const& filename) {
        cout << "writing to " << filename << ".b-txt" << endl;
        for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
            (*it)->serializeTo(this);

            cout << "@" << _map["Type"] << "," << _map["Line"] << "," << _map["Txt"]
                << endl;
        }
        cout << endl;
    }
};

class InfoData: public Data {
    public:
        void serializeTo(DataFile* storage) {
            storage->accept("Line", line);
            storage->accept("Txt", txt);
            storage->accept("Type", "Info");
        }
        string line;
        string txt;
    };

int main()
{
    FormatATxtFile fileA;
    FormatBTxtFile fileB;
    InfoData info34;
    info34.line = "34";
    info34.txt = "Hello World";
    InfoData info39;
    info39.line = "39";
    info39.txt = "Goodbye cruel World";
    fileA.addData(&info34);
    fileA.addData(&info39);
    fileB.addData(&info34);
    fileB.addData(&info39);
    fileA.writeTo("./Filename");
    fileB.writeTo("./Filename");    
}

на самом деле, он не записывает в файл, но его легко изменить в соответствии с вашимneeds.

Вывод этого примера кода:

запись в ./Filename.txt: Строка: 34;
Txt: Hello World;Тип: Информационная линия: 39;
Текст: Прощай, жестокий мир;Введите: Info

запись в ./Filename.b-txt
@ Info, 34, Hello World
@ Info, 39, До свидания, жестокий мир

Как вывидите, Data нужно предоставить DataFile параметрами, идентифицированными по имени и значению, и каждая специализация DataFile будет обрабатывать его так, как ему нравится.

HTH

1 голос
/ 06 мая 2011

Если вы рассмотрите приведенный ниже код, он представляет собой минимальную иллюстрацию того, как произвольно комбинировать общий доступ к полям с общей потоковой передачей полей, учитывая ваши различные требования.Если применимость или полезность не ясны, дайте мне знать ....

#include <iostream>
#include <string>

struct X
{
    int i;
    double d;

    template <typename Visitor>
    void visit(Visitor& visitor)
    {
        visitor(i, "i");
        visitor(d, "d");
    }
};

struct XML
{
    XML(std::ostream& os) : os_(os) { }

    template <typename T>
    void operator()(const T& x, const char name[]) const
    {
        os_ << '<' << name << '>' << x << "</" << name << ">\n";
    }

    std::ostream& os_;
};

struct Delimiter
{
    Delimiter(std::ostream& os,
              const std::string& kvs = "=", const std::string& fs = "|")
      : os_(os), kvs_(kvs), fs_(fs)
    { }

    template <typename T>
    void operator()(const T& x, const char name[]) const
    {
        os_ << name << kvs_ << x << fs_;
    }

    std::ostream& os_;
    std::string kvs_, fs_;
};

int main()
{
    X x;
    x.i = 42;
    x.d = 3.14;

    XML xml(std::cout);
    Delimiter delimiter(std::cout);

    x.visit(xml);
    x.visit(delimiter);
}
...