2

I'd like to design a robust version of this class (C++11):

class Oscillator
{
  private:
    std::vector<double> trajectory_;
    // Some numbers that are needed to get the trajectory
    double xinit_;
    double vinit_;
    double omega_;
  public:
    Oscillator(double xinit, double vinit, double omega): 
      xinit_(xinit), vinit_(vinit), omega_(omega) {}

    void Integrate(); // Implementation populates 'trajectory_'
    // Implementation prints the trajectory in JSON-friendly format
    void PrintJSON(std::ostream &);
    double TrajectoryMean();
    double TrajectoryStdv();
};

Here are some thoughts:

  1. I'd like to replace the PrintJSON method with a private variable of class TrajectoryPrinter or something, so that I don't have to change Oscillator::PrintJSON and recompile oscillator.cpp everytime there's a problem
  2. The same goes for the TrajectoryMean member. I'd rather have a class TrajectoryStats that does this for me.

The only problem with the above points is this: both proposed classes require access to a private member of Oscillator: trajectory_.

Is there a design pattern or generic design solution to this problem? Is what I am asking for possible?

Thanks.

4
  • The more I look at this code the more I think "Why isn't Trajectory a class of it's own?" – candied_orange Jul 20 '16 at 19:37
  • 1
    What do you mean with 'robustness'? You only talk about recompiling, but that is trivially solved by moving the code to a different .cpp file. What I would consider 'fragile' about this class is 1) it has more than one responsibility; 2) two-stage initialization makes it easier to use incorrectly; 3) the arguments of the constructor are easily mixed up; 4) the output format (JSON) is tightly coupled to the class. – D Drmmr Jul 21 '16 at 9:29
  • Seems like this question is better suited for Code Review. – D Drmmr Jul 21 '16 at 9:33
  • I suppose robust is a technical term. As you guessed, I meant 'opposite of fragile'. I was trying to solicit ideas to tackle your points 1 & 4. My real working example is somewhat complex. I felt my question was more conceptual (OOP concepts) and hence felt that it belonged here. I found your first comment very useful in this regard. – Salmonstrikes Jul 22 '16 at 1:42
7

If you want a TrajectoryPrinter ask for a TrajectoryPrinter. Right now you're only asking for doubles.

Something will need to build Oscillator. Something will need to build TrajectoryPrinter. I don't recommend that Oscillator build or even find TrajectoryPrinter. Oscillator shouldn't know TrajectoryPrinter as anything except as something it can call a print() function on.

TrajectoryPrinter is a dependency. You need one. Just like you need xinit, vinit, and omega. So let someone hand them all to you.

You can do this in main but do this in main a lot and it becomes crowded. It's ok to create classes whose only job is to build other classes.

This is called simply passing but some people call it dependency injection. The difference is mostly if they're trying to sell you a framework.

3
  • Any comments on the Builder C++ pattern in the wiki? That's what I imagine using to construct Oscillator objects. Also, I was wondering how TrajectoryPrinter would see the Oscillator's trajectory? I can't think of a way where TrajectoryPrinter would be able to do its job without having access to Oscillator's private trajectory_ member. Like, is this okay?: void Oscillator::Print(TrajectoryPrinter const &t) { t.Print(this->trajectory_; }} and void TrajectoryPrinter::Print(std::vector<double> const &)? – Salmonstrikes Jul 20 '16 at 6:21
  • 3
    @Salmonstrikes yes, something like that. This makes your TrajectoryPrinter more generally useful - and it enables you to test it completely independently from your Oscillator – Hulk Jul 20 '16 at 9:53
  • 1
    @Salmonstrikes Whether you want to pass in the dependency during construction or during the call depends on whether you want to be able to print the trajectory in different ways whenever calling Print - if it should always be printed the same, pass it during construction, if it can be a different printer each time, pass it in the call. – Hulk Jul 20 '16 at 10:02
1

If the integrate function is only invoked once per object, what you have is a trajector builder, only it doesn't expose the trajectory as a first-class entity.

So, you might make a first-class notion of the concept of a trajectory, and have your integrate function be a builder to construct the trajectory entity.

Once you have a first-class trajectory entity, you can pass it around to various other methods, such as serializers, and other manipulators.

2
  • Yes, I just might call Integrate() as a part of the constructor. When you say first class objects in the context of C++, do you mean something like these? (related) – Salmonstrikes Jul 20 '16 at 20:51
  • 1
    Not necessarily. Just that the notion of trajectory could be its own class or interface, meaning it is an entity that represents the notion of a trajectory and it can share it's trajectory contents somehow (perhaps more abstractly and indirectly than sharing the vec). Yes, if you call integrate in the constructor, then the Oscillator would almost be that trajectory concept, just add some access to the trajectory (or trajectory contents in a domain specific way that doesn't necessarily expose the vec, unless a vec is domain appropriate). – Erik Eidt Jul 20 '16 at 21:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.