0

I'm a computational physicist trying to learn how to code properly. I've written several program by now, but the following canonical example keeps coming back, and I'm unsure as to how to handle it. Let's say that I have a composition of two objects such as

class node
{
    int position;
};

class lattice
{
    vector <node*> nodes;
    double distance (node*,node*);
};

Now, this will not work, because position is a private member of node. I know of two ways to solve this: either you create an accessor such as getpos(){return position}, or make lattice a friend of node.

The second of these solutions seems a lot easier to me. However, I am under the impression that it is considered slightly bad practice, and that one generally ought to stick to accessors and avoid friend. My question is this: When should I use accessors, and when should I use friendship for compositions such as these?

Also, a bonus question that has been bugging me for some time: Why are compositions preferred to subclasses in the first place? To my understanding the HAS-A mnemonic argues this, but, it seems more intuitive to me to imagine a lattice as an object that has an object called node. That would then be an object inside of an object, e.i. a subclass?

storluffarn
  • 121
  • 2
  • 11
  • 5
    First things first. Why is `node::position` private? – juanchopanza May 06 '18 at 19:43
  • 3
    Here's a third way: Make `node` a *member* (class) of `lattice`. – Kerrek SB May 06 '18 at 19:44
  • 2
    Using `friend` introduces tight coupling between classes. Sometimes that is wanted or needed, but it's usually preferable to avoid. – Jesper Juhl May 06 '18 at 19:46
  • 1
    "Why are compositions preferred to subclasses" - It depends on what you want to model. Think of public inheritance as a "*is a*" relationship, private inheritance as a "*implemented in terms of*" relationship and composition as a "*has a*" relationship. – Jesper Juhl May 06 '18 at 19:52
  • 1
    The bonus question has been asked here: https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance. I think it first appeared in the GoF Design Patterns book, but there may be earlier references. – Jens May 06 '18 at 20:21
  • Tanks for the replies, some answers: 1. Right, but I guess it's generally bad to make a variable *more* accessible than needed? I only need node to be accessed by lattice, not other parts of the program. 2. That's how I usually do it actually, but while reading up before posting this, I got the impression that one usually defined objects separately, even when meant to be used together. Regardless, if it's a member class, then I'll still need the friendship to access private members. 3. Thanks for the links and ideas, I'll reflect on this! – storluffarn May 07 '18 at 05:50

4 Answers4

2

Friend is better suited if you give access rights to only specific classes, rather than to all. If you define getpos(){return position}, position information will be publicly accessible via that getter method. If you use friend keyword, on the other hand, only the lattice class will be able to access position info. Therefore, it is purely dependent on your design decisions, whether you wanna make the information publicly accessible or not.

erol yeniaras
  • 3,701
  • 2
  • 22
  • 40
2

You made a "quasi class", this a textbook example of how not to do OOP because changing position doesn't change anything else in node. Even if changing position would change something in node, I would rethink the structure to avoid complexity and improve the compiler's ability to optimize your code.

I’ve witnessed C++ and Java programmers routinely churning out such classes according to a sort of mental template. When I ask them to explain their design, they often insist that this is some sort of “canonical form” that all elementary and composite item (i.e. non-container) classes are supposed to take, but they’re at a loss to explain what it accomplishes. They sometimes claim that we need the get and set functions because the member data are private, and, of course, the member data have to be private so that they can be changed without affecting other programs!

Should read:

struct node
{
    int position;
};
Mikhail
  • 7,749
  • 11
  • 62
  • 136
  • I did entertain the idea of making node a struct for some time, but as I added function members to modify the data members, I made it into a class instead. Thanks, I'll read the link! – storluffarn May 07 '18 at 05:57
  • @storluffarn Modifying the data members isn't enough to justify getters/setters. You need your functions to modify the object in some non-trivial way and use the getter/setter to avoid code duplication. For example, `node.setPosition(position xyz){position=xyz};` isn't good enough. – Mikhail May 07 '18 at 06:54
1

Not all classes have to have private data members at all. If your intention is to create a new data type, then it may be perfectly reasonable for position to just be a public member. For instance, if you were creating a type of "3D Vectors", that is essentially nothing but a 3-tuple of numeric data types. It doesn't benefit from hiding its data members since its constructor and accessor methods have no fewer degrees of freedom than its internal state does, and there is no internal state that can be considered invalid.

template<class T>
struct Vector3 {
    T x;
    T y;
    T z;
};

Writing that would be perfectly acceptable - plus overloads for various operators and other functions for normalizing, taking the magnitude, and so on.

If a node has no illegal position value, but no two nodes in a lattice cannot have the same position or some other constraint, then it might make sense for node to have public member position, while lattice has private member nodes.

Generally, when you are constructing "algebraic data types" like the Vector3<T> example, you use struct (or class with public) when you are creating product types, i.e. logical ANDs between other existent types, and you use std::variant when you are creating sum types, i.e. logical ORs between existent types. (And for completeness' sake, function types then take the place of logical implications.)

Compositions are preferred over inheritance when, like you say, the relationship is a "has-a" relationship. Inheritance is best used when you are trying to extend or link with some legacy code, I believe. It was previously also used as a poor approximation of sum types, before std::variant existed, because the union keyword really doesn't work very well. However, you are almost always better off using composition.

jcarpenter2
  • 5,312
  • 4
  • 22
  • 49
1

Concerning your example code, I am not sure that this poses a composition. In a composition, the child object does not exist as an independent entity. As a rule of thumb, it's life time is coupled with the container. Since you are using a vector<node*> nodes, I assume that the nodes are created somewhere else and lattice only has a pointer to these objects. An example for a composition would be

class lattice {
    node n1; // a single object
    std::vector<node> manyNodes;
};

Now, addressing the questions:

  1. "When should I use accessors, and when should I use friendship for compositions such as these?"

If you use plenty of accessors in your code, your are creating structs and not classes in an OO sense. In general, I would argue that besides certain prominent exceptions such as container classes one rarely needs setters at all. The same can be argued for simple getters for plain members, except when the returning the property is a real part of the class interface, e.g. the number of elements in a container. Your interface should provide meaningful services that manipulate the internal data of your object. If you frequently get some internal data with a getter, then compute something and set it with an accessor you should put this computation in a method.

One of the main reasons why to avoid ´friend´ is because it introduces a very strong coupling between two components. The guideline here is "low coupling, high cohesion". Strong coupling is considered a problem because it makes code hard to change, and most time on software projects is spent in maintenance or evoluation. Friend is especially problematic because it allows unrelated code to be based on internal properties of your class, which can break encapsulation. There are valid use-cases for ´friend´ when the classes form a strongly related cluster (aka high cohesion).

  1. "Why are compositions preferred to subclasses in the first place?" In general, you should prefer plain composition over inheritance and friend classes since it reduces coupling. In a composition, the container class can only access the public interface of the contained class and has no knowledge about the internal data.

From a pure OOP point of view, your design has some weaknesses and is probably not very OO. One of the basic principles of OOP is encapsulation which means to couple related data and behavior into objects. The node class e.g. does not have any meaning other than storing a position, so it does not have any behavior. It seems that you modeled the data of your code but not the behavior. This can be a very appropriate design and lead to good code, but it not really object-oriented.

  1. "To my understanding the HAS-A mnemonic argues this, but, it seems more intuitive to me to imagine a lattice as an object that has an object called node. That would then be an object inside of an object, e.i. a subclass?"

I think you got this wrong. Public inheritance models an is-a-relationship.

class A: public B {};

It basically says that objects of class A are a special kind of B, fulfilling all the assumptions that you can make about objects of type B. This is known as the Liskov substitution principle. It basically says that everywhere in your code where you use a B you should be able to also use an A. Considering this, class lattice: public node would mean that every lattice is a node. On the other hand,

class lattice {
    int x;
    node n;
    int y;
};

means that an object of type lattice contains another object of type node, in C++ physically placed together with x and y. This is a has-a-relationship.

Jens
  • 9,058
  • 2
  • 26
  • 43