Edit: I didn't see the last 3 posts while I was doing mine
Could I summarise by mentioning these points (not in any particular order of importance):
- When get / set functions are mentioned, people seem to immediately think of the trivial & naive versions - which really are bad.
- A trivial get function is much more benign than a trivial set function. But one should decide whether it is even necessary. That is only provide that interface if it is actually required externally to the class, as per the draw tangent line 2 circles problem - the values of centre point & radius are required at some point.
- Decide what data members should have some exposure outside the class. For example in an Arc class, we can calculate all the data members of the arc given 3 points on the arc. To do this we need to calc 2 line segments and their perpendicular bisectors intersect at the centre point.This is all done with a bunch of private functions & data. All that info shouldn't be exposed, but things like the centre point, start point, mid point, end point, radius, arc length, start angle, end angle should be. These things are Properties, but in C++ we provide that interface by using functions to retrieve them.
- Obviously prefer constructors to set values initially.
- Use a member function that gets input, rather than a trivial set function.
- Provide functions that get input and set several values, rather than a function for each one.
- If you have any kind of update function, then make sure you do checking & validation, so as not to invalidate the other data members. Think of move & rotate functions. For the Arc class we are quite clearly not going to have functions that independently alter any data member without checking or recalculating the other members if the operation is possible.
- Don't use get functions to perform output - provide a member output function instead.
- Part of what encapsulation means is to put all the functions to do with an object in the class. It seems to me that is what "The Law of Demeter" is about. For example you wouldn't have a BankAccount class with the functions Withdraw & Deposit external to any class (in main() say). They would probably be part of a Transaction class, which interacts with BankAccount classes.
- The "Tell, don't ask " idea seems to be the "USES A" relationship:
void Line::CalcTangentLine(const CCircle& Circle1, const CCircle& Circle2 ){}
I have used a void return type, but a return type that allowed some status error would be better. I am not sure whether that is better / easier than exceptions. There is a need to have a way of deciding which tangent line to draw, but the example was to show the "USES A" relationship. The circle objects still have getRadius & getCentrePoint as part of their interface.
To me, sending a value as a parameter is the exact same thing as a get function - only the name has changed:
inline double CCircle::sendRadius()const {return m_Radius;}
inline double CCircle::getRadius()const {return m_Radius;}
- Prefer to use Design Patterns to reduce coupling between classes.
- Upgrade data members to classes if that helps with future changing requirements, but don't get carried away with it. If future changing requirements aren't mentioned in the specification then don't do it.
- Upgrade data members to classes if that helps with making interfaces easy to use correctly & hard to use incorrectly. Scott Meyers has an example (Item 18) where he recommends having Day , Month & Year as classes in a Date object. His Month class has function like this:
1 2 3 4 5 6 7 8 9 10
|
class Month {
public:
static Month Jan() {return Month(1);}
//.....
private:
explicit Month (int m);
};
Date d( Month::Mar(), Day(30), Year(1995) );
|
- Use templates to help future proof change in data type. For example if Circle uses templates to allow any integral or FP or boost multi-precision type in it's underlying data, then presumably one might do the same for anything that is a Property, as in:
Distance MyDistance = pMyCircle->getRadius;
That is, the Distance class uses templates in the same way as Circle does.
To me, this seems to be the solution to the problem (code breaks because type changes) posed in the article
ne555 linked:
The problem of a data member being extended in the future (Identity: Name, IDNum, Image) is solved by upgrading to a class.
Also, the solution proposed in the article was a get function with checking & validation.
So, is that a fair summary of the situation?