Should an object load itself?

although I’m programming in C++ for some time now, I’m always faced with design decisions (probably due to the language’s flexibility). One such problem is deciding if a method should be part of the class or not.

As an example, oftentimes, I have a Simulation class similar to the following,


class Simulation{
public:
    Simulation(void);
    bool init(const char* init_file){
        //...
        particles_ = read_particles(init_file);
        //etc
    }
    //or even,
    bool init(int argc, char* argv[]);
    void run(void);
private:
    std::vector particles_;
    //Other private members
};

Here, I’m not sure if this init method, which parses a file and initializes the simulation should really be a class method. A different way to implement similar functionality, would be to have an external function or class which handles the parsing of either the command line arguments or the initialization file, and calls appropriate methods of the Simulation class.


bool init_simulation(Simulation& sim, const char* init_file){
    //...
    Particles particles = read_particles(init_file);
    sim.set_particles(particles);
    //etc
}

On the one hand it’s easier to implement the functionality as a class method and it avoids having to introduce new classes/structs, specifically for passing parameters. On the other hand, externally handling the initialization, means that one can more easily extend it, although any changes on the Simulation class will propagate to the function.

What is the preferred design in situations similar to the one described above, and how should I approach similar design decisions?

3

Your problem isn´t having a class with a constructor method, your problem is what you have put into it. Parsing a text file should not be part of the object – its job is to create and manage Simulations. The constructor/init method should take a simple list or map of particles as one of its parameters and use that data when initialising. Reading data from a file and converting it into a simple list or map is the job of some other part of your code.

  • If you change the file format, or completely change how your data is stored (e.g. in a relational database), you should not have to rewrite the Simulation class.
  • You should not need an input file to write tests for this class. Your tests should be able to create simple input data and pass that directly to the object.

Separation of concerns. The file (its location, format etc.) is no concern of your Simulation object.

2

For discussion, I’m going to present a counter-argument to @itsbruce. YMMV.

What is more likely to change, the file format or the object itself? (as in additional or changed fields).

In my experience, if the file format is standard, and accessed through abstract interfaces (e.g. standard XML, JSON libraries), the core code for that doesn’t change. It’s much more likely for the object to change than the file format.

In that case, it does make some sense for the object to be able to read and write itself. When the Foo object changes, only one file, Foo, needs to change.

If you have a separate FooPersister, and the Foo object adds or changes a field, you are likely to have to modify FooPersister. e.g., in Foo, you’d add a .newField, appropriate setters getters and logic. And, in FooPersister.read(Foo) and FooPersister.write(Foo), you have to remember to add that field to both methods. What if a field changes from int to double? Likely some FooPersister code changes there.

Of course, it depends on your persistence framework, if it automagically reads the fields, or it’s as simple as adding an annotation somewhere. And how mature your project is, if your Foo object is already a gazillion lines of code, etc.. As said before, YMMV. (You could farm out the actual persistence code to a friend class, or, in Java-ese, a private internal class, so your code remains somewhat separated, but the public API rests in the Foo class.

One huge advantage of having the object be able to persist itself is that you may be able to eliminate many of your getters and setters. If you have a FooPersister, you pretty much must expose all your non-transient internal fields to it. If you persist yourself, sometimes you can keep a lot more internal and hidden (and final)

Now, I wouldn’t do exactly as OP does, reading/writing to files:

bool init(const char* init_file)

I’d probably have it read/write from/to a Stream, a Node or Element, or something like that, more abstract than a file. Also, even though it “feels weird”, consider having the read/write methods be instance methods (read should returns a new Foo, not overwrite the old one), not a static class function. This opens a lot of interesting doors in your design.

3

[Note: This question already has an accepted answer, and this answer only adds more arguments to it – but it is too long for a comment].

One such problem is deciding if a method should be part of the class or not.

I have found a very good guideline offered by A. Stepanov on this question:

He basically says that objects should be constructed from fully constructed data members. If the construction of an object requires extra processing (for example, reading the data from a file), it should be done by an external factory function. This maximizes flexibility and reusability. (Eventually, you could set the factory function as a friend).

An example:

class Simulation{
public:
    Simulation(std::vector<particle> particles);
        // instance keeps it's own copy,
        // so we might as well construct
        // it in the argument itself and
        // pass it into particles_ using
        // std::move

    void run();
private:
    std::vector<particle> particles_;
};

// factory function only 
Simulation load(std::istream& in) // use an istream instance here
                                  // (see below for reasoning)
{
    std::vector<particle> particles;
    particle p;
    while(in >> particle)
        particles.emplace_back(std::move(p));
    return Simulation{ std::move(particles) };
}

Test code (working with hardcoded particles):

std::vector<particle> p = test::get_particles();
Simulation s{ std::move(p) };

Test code (loading particles function):

std::string serialized_particles = "jdfkdfhaljkdfhlakjdfhalsjkfh";
std::istringstream in{ serialized_particles };
auto s = load( in );

Production code:

std::string path_to_file{ "/tmp/aaaaa" };
std::ifstream in{ path_to_file };
auto s = load( in );

Outside of this example (and your particular case), the guideline I use to determine if a function should be a member or not, is solved by answering these questions:

  • does the functionality apply to all instances? (if yes, it should be a member)
  • does the functionality require an active instance? (if no, it should not be a member)
  • does the functionality apply to an instance in any state? (if no, function should probably not be a member)

4

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật