Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Why is private encapsulation "good" in (C) programming?
I have read several times that it is implied that it is "good" programming style to have private encapsulation. But I'm curious to know what exactly makes it good.
For example, the code below does not use private encapsulation because the user of the ring-buffer (main.c) has access to all the ring-buffer's members. When we think about it, the user should really only be able to put and get items.
/* ring_buffer.h */
#define RINGBUFFER_SZ 32
typedef struct
{
uint8_t buf[RINGBUFFER_SZ];
volatile size_t head;
volatile size_t tail;
} ringbuffer_t;
void rb_init(ringbuffer_t* rb);
void rb_put(ringbuffer_t* rb, uint8_t item);
uint8_t rb_get(ringbuffer_t* rb);
/* main.c */
#include "ring_buffer.h"
static ringbuffer_t rb;
int main(void)
{
rb_init(&rb);
rb.head = 0u; //User can do this if he wanted to - for some reason
}
If the user of the ring-buffer wanted to, he could set all the members to arbitrary values. I see why this is bad but why would we ever expect the user to want to do that? Private encapsulation via forward declaring the ring-buffer struct and defining the struct in ring_buffer.c, or by simply defining the ring-buffer struct statically in ring_buffer.c as a variable at file scope would prevent this problem.
I'm seeking a clear explanation of why private encapsulation is good programming style in general.
1 answer
Mistakes
Human programmers make mistakes. The more the compiler can disallow actions that are unambiguously mistakes, the better. Strong type checking and encapsulation are probably the two most common means a compiler can "wall off" the human programmer from things they shouldn't be allowed to do.
In your ring buffer (FIFO) example, an application that uses the FIFO should only be allowed to put and get data into and out of the FIFO. There is no need to an application to alter internal FIFO state. Any attempt to do so would be a mistake. Not making that state available to the application eliminates the possibility of such a mistake.
Levels of abstraction
Another somewhat orthogonal argument is that each level of the software should only know and care about what is appropriate for that level. The application should not care how a FIFO is implemented, only be able to access it via a well-defined set of actions that are appropriate for applications to perform.
This concept allows for easier maintenance and ongoing modification of the code. If a better way of implementing a FIFO comes along, the FIFO routines can be re-written in isolation as long as the interface to the application remains the same. Therefore, the smaller and more limited that interface is, the more likely a new FIFO architecture can be implemented transparently to the rest of the software.
Note that some modifications of underlying functions, like FIFOs, might not be because something better comes along. The modification might be to keep more statistics, to aid in debugging or trapping specific conditions, etc.
Another way to look this is that the application interface is a promise of what will always be kept constant. Once you make a public promise, you have to assume someone somewhere out there is relying on it. The less you promise, the more you retain future flexibility to change things underneath the publicly-visible interface. Therefore promise only what the application actually needs.

1 comment thread