Encapsulation and Information Hiding
Before you begin: Objects, methods, lists, and properties.
Encapsulation puts data together with the operations that manage it, and lets an object control how callers use that data. Information hiding keeps callers from depending on decisions about the object’s implementation. 1
A private field helps, but the useful question is not “did we type private?” It is who is allowed to make changes, and what can callers safely assume afterward?
A playlist that never accepts a blank title
Suppose a playlist has one rule: every track title must contain something other than whitespace. If callers can edit the playlist’s internal list directly, they can bypass any validation we write in an add method.
Instead, let callers ask the playlist to add a track. When they want to see its contents, return a snapshot rather than the mutable list itself.
public sealed class Playlist
{
private readonly List<string> tracks = new List<string>();
public int Count { get { return tracks.Count; } }
public string[] Tracks
{
get { return tracks.ToArray(); }
}
public void Add(string title)
{
if (string.IsNullOrWhiteSpace(title))
{
throw new ArgumentException("A track needs a title.", nameof(title));
}
tracks.Add(title);
}
}Playlist playlist = new Playlist();
playlist.Add("Harbor Lights");
string[] snapshot = playlist.Tracks;
snapshot[0] = "Changed outside";
Console.WriteLine(playlist.Tracks[0]); // Harbor LightsAdd checks the title before changing tracks. Tracks returns a new array, so modifying that array does not modify the playlist. The readonly field alone would not do this: it prevents replacing the list reference, not adding items to the list.
class Playlist:
def __init__(self):
self._tracks = []
@property
def count(self):
return len(self._tracks)
@property
def tracks(self):
return tuple(self._tracks)
def add(self, title):
if not isinstance(title, str) or not title.strip():
raise ValueError("A track needs a title.")
self._tracks.append(title)playlist = Playlist()
playlist.add("Harbor Lights")
snapshot = list(playlist.tracks)
snapshot[0] = "Changed outside"
print(playlist.tracks[0]) # Harbor Lightsadd checks the title before changing _tracks. The tracks property returns a tuple, an immutable sequence. Converting it to a new list and modifying that copy does not modify the playlist. _tracks remains accessible to Python code; the underscore asks callers to treat it as private rather than enforcing that restriction.
Where the information is hidden
A caller knows how to add a title, ask for the count, and retrieve a snapshot. It does not need to know that the playlist stores its titles in a list.
We could later store extra information with each track while continuing to return the same title snapshots. Callers using the public operations would not need to learn the new storage structure. Callers reaching directly into the old list would.
That freedom to change an internal decision is information hiding. Keeping the title rule inside the object is a benefit of encapsulation. The two ideas reinforce each other, but they emphasize different things.
Know what the example does not promise
A snapshot is not a live view: tracks added later will not appear in a snapshot already returned. Copying also costs time and memory proportional to the number of titles. Neither version adds thread safety; concurrent readers and writers would need an additional design.
Nor does encapsulation mean every number requires a class. A pair of immutable coordinates may be useful as transparent data. Add controlled operations where they protect a real rule or prevent callers from depending on details you expect to change.
A wrapper that exposes the same mutable list under a different property name has changed the spelling, not the protection.
Sources
- Microsoft — Object-oriented programming, including encapsulation and access control.
- Python — Classes, private variables, explaining the convention used for
_tracks.