How does information hiding decouples the modules that compromise a system?
Encapsulation (information hiding) allows you to only expose the absolute minimum to the outside world. This means you can change the non-exposed bits to your heart's content without affecting clients.
A case in point. Say you've implemented a data structure that holds strings in an array. If you expose the array, users of your data structure can just use str[7]
to get the string at position 7.
Now, what happens if you decide to re-implement your data structure as a balanced tree to speed up searches for strings. You do this by moving the strings into a tree and changing your array to be, not strings, but pointers to locations within the tree.
That will break your clients, because they will be expecting strings, not pointers. Every single client would have to change to get the pointers from the array then look those up in the tree.
However, if you had totally hidden the implementation and just provided a function:
String getStringAt (int n);
you would just change the data structure and the function to stay compatible.
The clients don't break because your application programming interface (API) hasn't changed.
One of the most important rules I follow with classes is to aim for maximum coherence, minimum coupling. In other words, a single class should have everything it needs (nothing more) and it should have as little information sharing as possible with the outside world.
That means all that clients can do is call the API. No exposing non-API methods or allowing unfettered access to public variables - everything should be done with setters and getters.
Data Hiding, doesn't so much decouples modules, that it helps limit the coupling between them.
That is because none of the hidden elements can be used by any other module than the one where they are defined, hence limiting the possible tie-ins / dependancies / call-it-what-you-may which "couple" the modules together.
In other words, it limits the inter-module exchanges to these that are specifically defined in the API, and this fact helps a lot when the implementation of a given module is modified, because so long as the API stays the same, the module interop will work. (No need to scout through the code to find if somehow module A, used the variable x from Module B (if B hides x, it is the only one to use it !)