By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
440,130 Members | 2,100 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 440,130 IT Pros & Developers. It's quick & easy.

design question (converting class data to csv)

P: n/a
I have a class that I want to turn its contents into csv file. I want
to be able to set the value of the delimiter, the name of the file it
gets saved to, the path of that file, and maybe a few other things.
What would be a good design to accomplish these goals? Here are the
ideas that I have come up with:

Class X = class with data that I want to put into csv.

1. Nested Function Object
use a nested function object to do the conversion.
+ X does not get polluted with functions that really have no place.
e.g. setDelimeter()
+ behaves the way you would expect. e.g. X.toCsv() to convert X into a
csv file
- Setting state may not be clear to those not familar with function
objects e.g. X.toCsv.setDelimeter() Some may ask why a function is
being used like an object.

2. Create a class ConvertXToCsv that gets passed X to do the
conversion.
+ very straight forward and gets the job done.
- proliferation of classes.

Any suggestions?
-Thanks,

Jake

Oct 16 '06 #1
Share this Question
Share on Google+
4 Replies


P: n/a
<al******@gmail.comschrieb im Newsbeitrag
news:11**********************@k70g2000cwa.googlegr oups.com...
>I have a class that I want to turn its contents into csv file. I want
to be able to set the value of the delimiter, the name of the file it
gets saved to, the path of that file, and maybe a few other things.
What would be a good design to accomplish these goals? Here are the
ideas that I have come up with:

Class X = class with data that I want to put into csv.

1. Nested Function Object
use a nested function object to do the conversion.
+ X does not get polluted with functions that really have no place.
e.g. setDelimeter()
+ behaves the way you would expect. e.g. X.toCsv() to convert X into a
csv file
- Setting state may not be clear to those not familar with function
objects e.g. X.toCsv.setDelimeter() Some may ask why a function is
being used like an object.

2. Create a class ConvertXToCsv that gets passed X to do the
conversion.
+ very straight forward and gets the job done.
- proliferation of classes.
Writing data to a file (or whatever else) can be split into two tasks -
providing the data to be written and formating (and writing) data
"somewhere".

The provider of your data usually doesn't care (or shouldn't) where its data
will be written to. To write data to a CSV file or some other record
oriented medium, a set of functions like OpenRecord, CloseRecord, WriteInt,
WriteString etc. are all your data source needs to write its data. Such
functions can easyly be defined by an (abstract) interface class.

The consumer of such data usually shouldn't know where its input comes from.
But it might need additional information like delimiters or filenames, which
cannot be supplied by the data source itself. Also, such additional
information depends on the type of medium where the (formated) data should
be written to. That information must be supplied by that part of a program,
that actually wants data to be written to a well-known medium.

Your second idea is a step into that direction, but the fixed connection of
a given class X and a single medium (CSV files) makes it difficult to write
data from another class, or to another medium. You would end with x*y
ConvertXToY classes, where x is the number of data classes to write and y
the number of supported media. With an abstract writer class used by all
data classes and implementations for different media, otoh, you only need y
classes and one additional function in each data class.

The C++ stream library is a small step in this direction, but it only
abstracts the representation of data streams (as a file, a string or a
console device).

Consider this small (incomplete and buggy) example:

class Writer
{
public:
virtual void OpenRecord() = 0;
virtual void CloseRecord() = 0;
virtual void Write(int) = 0;
virtual void Write(std::string const&) = 0;
};

class CsvWriter: public Writer
{
// Implementation of Writer class
void OpenRecord()
{
first = true;
}
void CloseRecord()
{
file << std::endl;
}
void Write(int x)
{
NextField();
file << x;
}
void Write(std::string const& x)
{
NextField();
file << '"' << x << '"';
}
public:
CsvWriter(char const* fn, char d)
: file(fn)
, delimiter(d)
{}
private:
void NextFiled()
{
if (!first) file << delimiter;
first = false;
}
private:
std::ofstream file;
char delimiter;
bool first;
};
class MyData
{
public:
...
void Write(Writer& writer)
{
writer.OpenRecord();
writer.Write(name);
writer.Write(age);
writer.CloseRecord();
}
private:
std::string name;
int age;
};
int main()
{
MyData x;
MyData y;
...
CsvWriter datafile("datafile.csv", ';');
x.Write(datafile);
y.Write(datafile);
}

Regards
Heinz
Oct 16 '06 #2

P: n/a

al******@gmail.com wrote:
I have a class that I want to turn its contents into csv file. I want
to be able to set the value of the delimiter, the name of the file it
gets saved to, the path of that file, and maybe a few other things.
What would be a good design to accomplish these goals? Here are the
ideas that I have come up with:

Class X = class with data that I want to put into csv.

1. Nested Function Object
use a nested function object to do the conversion.
+ X does not get polluted with functions that really have no place.
e.g. setDelimeter()
+ behaves the way you would expect. e.g. X.toCsv() to convert X into a
csv file
- Setting state may not be clear to those not familar with function
objects e.g. X.toCsv.setDelimeter() Some may ask why a function is
being used like an object.

2. Create a class ConvertXToCsv that gets passed X to do the
conversion.
+ very straight forward and gets the job done.
- proliferation of classes.

Any suggestions?
-Thanks,

Jake
First item, if you have a class that needs to stream its members,
overload the global stream operator and make it a friend of the class.
That way you might one day come along and write a container class to
store all your X instances and you'll be able to stream the entire
container seamlessly using another overload of global op<<.

As far as conversion to csv and delimeters, all you are doing is
opening a std::ofstream to some specific file somewhere. Why not write
a container that will:
1. store your records into a well-known and common container (list,
vector, deque)
2. stream records around using std::ostreams for compatibility and
future expandeability
3. provide the container with a function to serialize the records to
file

I'ld suggest a new line at the end of each record to help later when
using getline(...) to read that file and load the container from file
records. A solution with exception handling would have been preferable
and error checking is kept to a minimum.
Since you have not shown your precious class, i've made one up called
X.
#include <iostream>
#include <ostream>
#include <string>
#include <vector>
#include <fstream>
#include <iterator// for std::ostream_iterator

class X
{
int n;
double d;
std::string s;
public:
X() : n(0), d(0.0), s() { }
X(int n_, double d_, std::string s_)
: n(n_), d(d_), s(s_) { }
/* member functions */
void serialize( std::ostream& ) const;
/* friends */
friend std::ostream& operator<<( std::ostream&, const X& );
};

void X::serialize(std::ostream& os) const
{
os << n << '$' << d << '$' << s << std::endl;
}

std::ostream& operator<<(std::ostream& os, const X& r_x)
{
os << "n = " << r_x.n;
os << "\nd = " << r_x.d;
os << "\ns = " << r_x.s << std::endl;
return os;
}

class XContainer
{
std::vector< X vx;
public:
XContainer() : vx() { }
void push_back( const X& r_x ) { vx.push_back( r_x ); }
size_t size() const { return vx.size(); }
/* member functions */
void serialize( std::ostream& ) const;
bool write( const std::string& ) const;
/* friends */
friend
std::ostream& operator<<( std::ostream&, const XContainer& );
};

void XContainer::serialize(std::ostream& os) const
{
typedef std::vector< X >::const_iterator VIter;
for (VIter xiter = vx.begin(); xiter != vx.end(); ++xiter)
{
(*xiter).serialize(os);
}
}

bool XContainer::write(const std::string& r_filename) const
{
std::ofstream ofs(r_filename.c_str());
if ( !ofs.is_open() )
{
return true;
} else {
serialize(ofs);
}
return false;
}

std::ostream&
operator<<(std::ostream& os, const XContainer& r_con)
{
std::copy( r_con.vx.begin(),
r_con.vx.end(),
std::ostream_iterator< X >(os) );
return os;
}

int main()
{
XContainer xcontainer;
xcontainer.push_back( X(0, 0.0, "string 0") );
xcontainer.push_back( X(1, 1.1, "string 1") );
xcontainer.push_back( X(2, 2.2, "string 2") );
std::cout << xcontainer;

// show what file contents will look like on console
std::cout << "\nnumber of records = " << xcontainer.size();
std::cout << std::endl;
xcontainer.serialize( std::cout );

// write the records to file - write protect file to test
const std::string sfilename( "data.cvs" );
if ( xcontainer.write( sfilename ) )
{
std::cout << "\nerror: failed to open " << sfilename;
std::cout << " !!!\n";
} else {
std::cout << "\nsuccess: records written to " << sfilename;
std::cout << std::endl;
}

return 0;
}

/*
n = 0
d = 0
s = string 0
n = 1
d = 1.1
s = string 1
n = 2
d = 2.2
s = string 2

number of records = 3
0$0$string 0
1$1.1$string 1
2$2.2$string 2

success: records written to data.cvs
*/

Oct 16 '06 #3

P: n/a
On Mon, 16 Oct 2006 09:23:08 +0200, "Heinz Ozwirk" wrote:
>class MyData
{
public:
...
void Write(Writer& writer)
{
writer.OpenRecord();
writer.Write(name);
writer.Write(age);
writer.CloseRecord();
}
private:
std::string name;
int age;
};
If you made Write() a non-member function you would completely
decouple class MyData from formatting and writing.

Best wishes,
Roland Pibinger

Oct 16 '06 #4

P: n/a

Salt_Peter wrote:
al******@gmail.com wrote:
I have a class that I want to turn its contents into csv file. I want
to be able to set the value of the delimiter, the name of the file it
gets saved to, the path of that file, and maybe a few other things.
What would be a good design to accomplish these goals? Here are the
ideas that I have come up with:

Class X = class with data that I want to put into csv.

1. Nested Function Object
use a nested function object to do the conversion.
+ X does not get polluted with functions that really have no place.
e.g. setDelimeter()
+ behaves the way you would expect. e.g. X.toCsv() to convert X into a
csv file
- Setting state may not be clear to those not familar with function
objects e.g. X.toCsv.setDelimeter() Some may ask why a function is
being used like an object.

2. Create a class ConvertXToCsv that gets passed X to do the
conversion.
+ very straight forward and gets the job done.
- proliferation of classes.

Any suggestions?
-Thanks,

Jake

First item, if you have a class that needs to stream its members,
overload the global stream operator and make it a friend of the class.
That way you might one day come along and write a container class to
store all your X instances and you'll be able to stream the entire
container seamlessly using another overload of global op<<.

As far as conversion to csv and delimeters, all you are doing is
opening a std::ofstream to some specific file somewhere. Why not write
a container that will:
1. store your records into a well-known and common container (list,
vector, deque)
2. stream records around using std::ostreams for compatibility and
future expandeability
3. provide the container with a function to serialize the records to
file

I'ld suggest a new line at the end of each record to help later when
using getline(...) to read that file and load the container from file
records. A solution with exception handling would have been preferable
and error checking is kept to a minimum.
Since you have not shown your precious class, i've made one up called
X.
#include <iostream>
#include <ostream>
#include <string>
#include <vector>
#include <fstream>
#include <iterator// for std::ostream_iterator

class X
{
int n;
double d;
std::string s;
public:
X() : n(0), d(0.0), s() { }
X(int n_, double d_, std::string s_)
: n(n_), d(d_), s(s_) { }
/* member functions */
void serialize( std::ostream& ) const;
/* friends */
friend std::ostream& operator<<( std::ostream&, const X& );
};

void X::serialize(std::ostream& os) const
{
os << n << '$' << d << '$' << s << std::endl;
}

std::ostream& operator<<(std::ostream& os, const X& r_x)
{
os << "n = " << r_x.n;
os << "\nd = " << r_x.d;
os << "\ns = " << r_x.s << std::endl;
return os;
}

class XContainer
{
std::vector< X vx;
public:
XContainer() : vx() { }
void push_back( const X& r_x ) { vx.push_back( r_x ); }
size_t size() const { return vx.size(); }
/* member functions */
void serialize( std::ostream& ) const;
bool write( const std::string& ) const;
/* friends */
friend
std::ostream& operator<<( std::ostream&, const XContainer& );
};

void XContainer::serialize(std::ostream& os) const
{
typedef std::vector< X >::const_iterator VIter;
for (VIter xiter = vx.begin(); xiter != vx.end(); ++xiter)
{
(*xiter).serialize(os);
}
}

bool XContainer::write(const std::string& r_filename) const
{
std::ofstream ofs(r_filename.c_str());
if ( !ofs.is_open() )
{
return true;
} else {
serialize(ofs);
}
return false;
}

std::ostream&
operator<<(std::ostream& os, const XContainer& r_con)
{
std::copy( r_con.vx.begin(),
r_con.vx.end(),
std::ostream_iterator< X >(os) );
return os;
}

int main()
{
XContainer xcontainer;
xcontainer.push_back( X(0, 0.0, "string 0") );
xcontainer.push_back( X(1, 1.1, "string 1") );
xcontainer.push_back( X(2, 2.2, "string 2") );
std::cout << xcontainer;

// show what file contents will look like on console
std::cout << "\nnumber of records = " << xcontainer.size();
std::cout << std::endl;
xcontainer.serialize( std::cout );

// write the records to file - write protect file to test
const std::string sfilename( "data.cvs" );
if ( xcontainer.write( sfilename ) )
{
std::cout << "\nerror: failed to open " << sfilename;
std::cout << " !!!\n";
} else {
std::cout << "\nsuccess: records written to " << sfilename;
std::cout << std::endl;
}

return 0;
}

/*
n = 0
d = 0
s = string 0
n = 1
d = 1.1
s = string 1
n = 2
d = 2.2
s = string 2

number of records = 3
0$0$string 0
1$1.1$string 1
2$2.2$string 2

success: records written to data.cvs
*/
Thank you for the time put into your suggestions. I mean it.

There seems to be two different approaches to the design. Either tell
the class to serialize itself or Have a class whose purpose it is to
serialize classes. What are the design trade offs to each? From what I
understand of OOP (and I am some what of a newb so please if you can
help me understand) the idea is that the class knows about its
internals and it should be the only object that does. So telling the
class to serialize its self makes more sense to me be cause the class
knows what members it has and you telling that class to run operations
on it own data which is part of the reason for classes.

I am interested first if my assessment of the serialize method is
correct. Also, I am interested in knowing more about the benefits of
having a class that does the serialization as in Heinz's design.

Thanks

Oct 16 '06 #5

This discussion thread is closed

Replies have been disabled for this discussion.