Encapsulate Collection
Before you begin: Lists, references, methods, and collection mutation.
Encapsulate Collection keeps a collection under its owner’s control. Callers can inspect its contents, but changes go through named operations such as add or remove rather than through a leaked mutable list. 1
Original version: everyone can change the roster
public sealed class Party
{
public List<string> Names { get; } = new List<string>();
}A get-only property prevents assigning a different list through the property. It does not prevent party.Names.Clear() from emptying the existing one.
class Party:
def __init__(self):
self.names = []The list is directly exposed. Callers can append, remove, reorder, or clear it without the party knowing which operation they intended.
Refactored version: inspect a snapshot, request a change
public sealed class Party
{
private readonly List<string> names = new List<string>();
public string[] Names { get { return names.ToArray(); } }
public void Add(string name) { names.Add(name); }
public bool Remove(string name) { return names.Remove(name); }
}var party = new Party();
party.Add("Mira");
string[] snapshot = party.Names;
snapshot[0] = "Sol";
Console.WriteLine(party.Names[0]); // MiraThe returned array is a copy. Editing it does not alter the stored roster. Add and Remove are the explicit mutation paths.
class Party:
def __init__(self):
self._names = []
@property
def names(self):
return tuple(self._names)
def add(self, name):
self._names.append(name)
def remove(self, name):
try:
self._names.remove(name)
return True
except ValueError:
return Falseparty = Party()
party.add("Mira")
snapshot = party.names
party.add("Sol")
print(snapshot) # ('Mira',)
print(party.names) # ('Mira', 'Sol')The tuple is a snapshot, not a live view. Later additions change the party, not the previously returned tuple. The underscored list remains internal by convention.
Specify the collection’s behavior
These examples retain insertion order and allow duplicates. Removing a name removes its first occurrence. They do not silently introduce a set or a uniqueness rule while changing the API.
A read-only live view is another valid choice, but it differs from a snapshot: its contents can change when the owner changes. Pick the behavior callers need and document it. Copies cost memory, and copying a collection of mutable objects does not copy the objects themselves.
Migrate callers that previously mutated the list to the new operations. Then any new capacity rule, event notification, or validation has one place to live. Encapsulation makes those rules possible; it does not mean they have already been implemented.