Good Design in C: Encapsulation

A

Adrian Hawryluk

William said:
I was thinking of the case where the modification was to something
that used A, but the modification needed information from C.

E.g. a piece of state in C is needed
by somthing using A, but the need for this piece of state
was not anticipated. If there is no encapsulation I can write
something like

needed_state = a_pointer->b_pointer->c_pointer->low_level_state


With encapsulation I need to write something like

needed_state = a_pointer_get_needed_state(a_pointer)
which needs
needed_state = b_pointer_get_needed_state(b_pointer)
which needs
needed_state = c_pointer_get_needed_state(c_pointer)


and I have to modify a bunch of files.

It may be more of a hassle, but I still think that it is cleaner to
modify the files since, the system may change and the state is no longer
in C but in D. In which case, your 'needed_state =
a_pointer->b_pointer->c_pointer->low_level_state' either will not
compile or will work incorrectly (the worse of the two) and then you
have a bug that may not be trivial to track down.

I'm a bit proponent of encapsulation. It keeps things neat and tidy
(for the most part) and keeps others hands out of the cookie jar, where
they might find an unexpected bug biting at their fingers (unexpected
consequences). Also, if a bug is found in the encapsulated code, you
fix it in one place instead of all over the system (been there, done
that, don't want to go there again).

For all but the simplest of structs would I consider exposing it, and
even then, I would have to think about it at length to make sure it is
something that will not change over the course of development.


Adrian

--
==========================================================
Adrian Hawryluk BSc. Computer Science
----------------------------------------------------------
Specialising in: OOD Methodologies in UML
OOP Methodologies in C, C++ and more
RT Embedded Programming
__--------------------------------------------------__
----- [blog: http://adrians-musings.blogspot.com/] -----
'--------------------------------------------------------'
My newsgroup writings are licensed under the Creative
Commons Attribution-Noncommercial-Share Alike 3.0 License
http://creativecommons.org/licenses/by-nc-sa/3.0/
==========================================================
 
A

Adrian Hawryluk

David said:
Good question!

I default to opaque types and function based interfaces. I've found
over the years that this has advantages that outweigh everything
else, primarily because I create a lot of reusable library code. I
can fix bugs, augment functionality and change implementation without
"recompiling the world" due to a change to a struct defined in a
header. I *know* no module knows the structure's definition when
it's defined in the .c file!

But every rule has it's exceptions, often for the reasons you gave.
I have one module which is fundamental to many of my projects, an
abstract type Buffer. It's used in ISRs in device drivers, among
other places, so function call overhead for accessing variables is
out of the question, and the code must manipulate the Buffer
internals anyway. In that case, I swallow hard, export the struct
definition and provide function macros which do exactly what the
functions do (and implement the functions using the macros to make
sure). So buf_Count(b) (the function) is implemented as:

return buf_COUNT(b);

and buf_COUNT(b) is implemented (in buffer.h) as:

#define buf_COUNT(b) ((b)->end - (b)->start)

(Most of the compilers I use don't support inline functions.) I use
the function where I can and the macro where I must.

But really, cases like this where I've needed to export module
internals are pretty rare, and for me, the reduced coupling provided
by opaque types overwhelms almost every other consideration.

What you describe is not bad, you are still using encapsulation, just
not a black box type. As long as users of the library are honest and
don't touch the struct (get out the ruler if you have to ;)) then things
will continue to go smoothly. Even if a bug is encountered, those
source files that depend on the macros are recompiled and you have a
clean, globally repaired system.

When writing code, you have to take what comes at you and do what is best.


Adrian

--
==========================================================
Adrian Hawryluk BSc. Computer Science
----------------------------------------------------------
Specialising in: OOD Methodologies in UML
OOP Methodologies in C, C++ and more
RT Embedded Programming
__--------------------------------------------------__
----- [blog: http://adrians-musings.blogspot.com/] -----
'--------------------------------------------------------'
My newsgroup writings are licensed under the Creative
Commons Attribution-Noncommercial-Share Alike 3.0 License
http://creativecommons.org/licenses/by-nc-sa/3.0/
==========================================================
 
M

Malcolm McLean

bluejack said:
I'm curious as to how C experts feel about this design concept;
reviewing some old (very old) threads about encapsulation, I see that
many dismiss it as though it were a tenet of some foolish philosophy;
and yet, encapsulation has all sorts of benefits to the author of
libraries in terms of the ability to extend, adapt, & refactor code;
it has also helped me in my own designs by illuminating design
mistakes before they get very far.
Are you sure those old threads are not attacking "object-orientation"?
Object-oriented code is encapsulated, but not all encapsulated code is
object-oriented.

Personally I never use opaque pointers because, if you make a mistake in the
interface, it is then impossible to fix without rewriting the opaque code.
That might cause other problems. However I keep the pointers opaque in that
calling code shouldn't need to access them in everyday use.
 
I

Ian Collins

Malcolm said:
Are you sure those old threads are not attacking "object-orientation"?
Object-oriented code is encapsulated, but not all encapsulated code is
object-oriented.

Personally I never use opaque pointers because, if you make a mistake in
the interface, it is then impossible to fix without rewriting the opaque
code.
So how's that different from changing the the code if you change a struct?
That might cause other problems.

Such as?
However I keep the pointers
opaque in that calling code shouldn't need to access them in everyday use.
You either expose the type, or you don't. You can't have it both ways.
Once a type is exposed, it will get used.
 
W

William Hughes

It may be more of a hassle,


Which was my point. A different question is whether the hassle is
worth it. My answer would depend on how likely the above scenario
is thought to be (a judgment call, and they call this computer
*science*). If it is thought to be likely, then encapsulation
may not be appropriate. In my experience you add about an order
of magnitude to the amount of time needed to make a simple change.
On the other hand, encapsulation goes a long way to insuring
that your "simple change" does not have unintended consequences,
and has other advantages (some of which you note)
thus I do support encapsulation as a default.

- William Hughes
 
M

Malcolm McLean

Ian Collins said:
So how's that different from changing the the code if you change a struct?


Such as?
Let's say the opaque code is a bitstream. Accepts bits, and stores them in
an array of bytes. A fairly general-purpose routine.
It is called by a Huffman compressor, and it is also called by the code I am
working on, which is an encryption algorithm.

I haven't provided an "ungetbit" and for some reason my encryption routine
needs it. My choices are to hack into the bitstream struct in the encryption
module, or rewrite the bitstream. Choice two is the better one, except

The Huffman routine is very serious and used in scientific work. The
encryption routine is just a bit of messing about which I might use for
something that ships but might not. So if I rewrite the code, and break the
Huffman routine, people are going to get really annoyed. Fork development?
The last thing you want is two similar bitstream files swilling around.
Retest the whole thing so it impossible that the Huffman routine will break?
Yes, that would be best, but it's a big job.

Because each bitstream is allocated privately with its own call, however
much I mess about with the internals, the encryption routine won't break
anything else. So a quick hack to implement an ungetbit() is the best
answer, or at least a defensible answer.
You either expose the type, or you don't. You can't have it both ways.
Once a type is exposed, it will get used.
You don't guarantee to maintain anything except the interface. So caller
uses struct members directly at his own risk.
 
I

Ian Collins

Malcolm said:
Let's say the opaque code is a bitstream. Accepts bits, and stores them
in an array of bytes. A fairly general-purpose routine.
It is called by a Huffman compressor, and it is also called by the code
I am working on, which is an encryption algorithm.

I haven't provided an "ungetbit" and for some reason my encryption
routine needs it. My choices are to hack into the bitstream struct in
the encryption module, or rewrite the bitstream. Choice two is the
better one, except

The Huffman routine is very serious and used in scientific work. The
encryption routine is just a bit of messing about which I might use for
something that ships but might not. So if I rewrite the code, and break
the Huffman routine, people are going to get really annoyed. Fork
development? The last thing you want is two similar bitstream files
swilling around. Retest the whole thing so it impossible that the
Huffman routine will break? Yes, that would be best, but it's a big job.
So long as the bitstream has been implemented with proper unit tests,
just add the new ungetbit() interface, make sure all the tests pass and
move on.
 
M

Malcolm McLean

Ian Collins said:
So long as the bitstream has been implemented with proper unit tests,
just add the new ungetbit() interface, make sure all the tests pass and
move on.
But if it hasn't?
Bitstreams can be unit tested quite easily and it is arguably a good idea to
keep a suite handy. In fact the last time I broke a bitstream it was to
honour the MPEG / JPEG rule about consecutive runs of ones or zeroes. So
that would have entailed a complete rewrite of the unit tests. In fact I
didn't write any.

Not everything can be unit tested as easily as that. Higher level modules
often depend quite intimately on other modules in the system and cannot be
tested in isolation from them without effectively rewriting the whole
program. This isn't ideal, and good programming often involves avoiding such
a situation, but it is often inevitable.
 
I

Ian Collins

Malcolm said:
But if it hasn't?
Bitstreams can be unit tested quite easily and it is arguably a good
idea to keep a suite handy. In fact the last time I broke a bitstream it
was to honour the MPEG / JPEG rule about consecutive runs of ones or
zeroes. So that would have entailed a complete rewrite of the unit
tests. In fact I didn't write any.
...and you broke it!
Not everything can be unit tested as easily as that. Higher level
modules often depend quite intimately on other modules in the system and
cannot be tested in isolation from them without effectively rewriting
the whole program. This isn't ideal, and good programming often involves
avoiding such a situation, but it is often inevitable.

Normal practice is to mock the lower layers. I've yet to come across a
problem that can't be tackled test first.
 
M

Mark McIntyre

Irish Rider Training Association?

"I read that as"

--
Mark McIntyre

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it."
--Brian Kernighan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,602
Members
45,184
Latest member
ZNOChrista

Latest Threads

Top