0

I have two classes that I want to define, Position and TangentVector, partially given as follows:

class Position
{
public:
    Position(double x, double y, double z);
    
    // getters
    double x(){ return m_x };
    double y(){ return m_x };
    double z(){ return m_x };

    void translate(const TangentVector& tangent_vector);
private:
    double m_x;
    double m_y;
    double m_z;
}
class TangentVector
{
public:
    Tangent(double x, double y, double z, Position position);

    // getters
    double x(){ return m_x };
    double y(){ return m_x };
    double z(){ return m_x };
private:
    double m_x;
    double m_y;
    double m_z;
    Position m_position;
}

The key thing to note with the classes is that TangentVector has a member of type Position (TangentVector depends on Position) while Position has a method that takes in an argument of type const TangentVector& (Position depends on TangentVector?).

For context's sake, Position is intended to represent a point on the unit sphere, and TangentVector describes a vector tangent to the sphere, with the origin of the vector specified by a Position. Since the definition of a VectorTangent requires a Position to be specified, it seems reasonable to say that VectorTangent depends on Position. However, now I want to define a function that takes a Position on the sphere, and "translates" it along the sphere by a direction and distance given by TangentVector. I would really like this translate method to live in the Position class, since it a function that modifies the state of Position. This would lend itself to the following usage, which I feel is fairly natural:

Position position{ 1.0, 0.0, 0.0 };
TangentVector tangent_vector{ 0.0, PI/2, 0.0, position };
position.translate(tangent_vector);                        // Now { 0.0, 1.0, 0.0 };

However, I fear that this results in some circular dependency. So...

  • Is this case an example of circular dependency? Is this case bad practice?
  • If so, how can this circular dependency be avoided? How can this code be modified such that it is in-line with good OOP practices?

(I considered making the Position m_position member a raw pointer instead. In this case, however, I intend m_position to be fully owned by TangentVector, rather than allow the possibility of it being altered external to the class. In the example usage code, I do not want the translate method to modify tangent_vector, which would happen if tangent_vector's constructor took in position as a pointer and stored it as a member.)

Involute
  • 111
  • 4
  • 1
    As this question got closed in the meantime, to comment on your questions and not about forward declarations (that are the technical solution): Q1> Is this case an example of circular dependency? Is this case bad practice? A1> It's a circular dependency that comes from a model, so you just model the world as it is. One entity needs the other to operate. – Adam Kotwasinski Jun 27 '22 at 22:18
  • 1
    Q2> If so, how can this circular dependency be avoided? How can this code be modified such that it is in-line with good OOP practices? A2> The code currently appears OK. Theoretically you could extract a helper static method that does the translation, but then your `Position` object would be very anemic (it only stores data). The best idea might be to follow SOLID, and try to keep functionality in a single class. Right now, it feels like `translate` has its place in a `Position`. – Adam Kotwasinski Jun 27 '22 at 22:18
  • 1
    SOLID and actually https://en.wikipedia.org/wiki/Single-responsibility_principle – Adam Kotwasinski Jun 27 '22 at 22:18
  • 1
    In the end it is unexact science, in this simple example there is nothing wrong, but in future you might want to avoid situations when a single class needs N other classes to perform its function. – Adam Kotwasinski Jun 27 '22 at 22:24
  • 1
    And just to be more wordy ;), some possible solutions are: * use TV as Position factory (but then instead it needs to know how to create positions and possibly access Position internal data); * instead of passing TV pass only what's needed to perform the computation (== 3 doubles + position) this way you avoid spelling out TV (you can see it's an ugly idea here). – Adam Kotwasinski Jun 27 '22 at 22:26

1 Answers1

2

class Position takes only a reference to class TangentVector. Therefore you might pre-declare TangentVector as class TangentVector; before the declaration of class Position:

class TangentVector;

class Position
{
public:
    Position(double x, double y, double z);
    
    // getters
    double x(){ return m_x };
    double y(){ return m_x };
    double z(){ return m_x };

    void translate(const TangentVector& tangent_vector);
private:
    double m_x;
    double m_y;
    double m_z;
};
lorro
  • 10,687
  • 23
  • 36