Not sure I understand what this means. Could you give an example?
Not sure I understand what this means. Could you give an example?
Here's a data structure that represents a bill of materials. It has a list of components, and a total cost, which is the sum of the costs of the components on the list. That's the invariant - that the total cost is the sum of the costs of the components in the list.
If it's just a structure, then someone can add or remove a component, and forget to update the total cost, and the invariant is violated. But if it's an object, if the code is bound to the data (in the way OOP normally calls encapsulation), then only the member functions can modify the structure. So if you want to add a component, you have to use the object's addComponent method, which (in a properly debugged object) isn't going to forget to update the total cost.
Now, you could say that the structure could have a code library to go with it, and that could have an addComponent function. That's true. But the user doesn't have to use that function - they can mess with the list directly, if they think they know what they're doing. Whereas with OOP encapsulation, they have to use the existing function. That function can guarantee that the object's invariants are maintained.
There's a potential performance cost here, but that usually doesn't matter a whole lot.
Yes, but this is not possible in general. For instance, what if you want to endow your List with a "MaxTotalCost", set at creation. You can't ensure that your list respects that invariant, other than by encapsulating it behind some custom interface.
[1]: https://craftinginterpreters.com/representing-code.html#the-...
All this said, this is basically OOP in C, and the code is still bound to the data, only it is bound by naming (point_add()) not by a scope (point.add()).
You can achieve the same kind of hiding in Java by using an existential type. E.g. you could have a library that looked like:
interface Cursor<T> {
T start();
T nextStep(T currentState, int move);
void finish(T);
}
class DataStructure {
Cursor<?> traverse();
}
And then you don't know what the internal state of the cursor is because the T type is hidden from you, but the compiler checks that you always call nextStep() with the same state you got back from start(), and you can't possibly mix up the state from two different cursors.I love opaque structures, but the one downside in embedded is they can’t be truly opaque since you want to avoid dynamic memory allocation - and therefore the place you instantiate the struct needs to know the size, and therefore members of the struct.
But then again, what you want there is modularity, which OOP provides, but is also well-supported in other paradigms.