Encapsulate Variable
Before you begin: Variables, assignment, functions, and shared state.
Encapsulate Variable puts reads and writes of a variable behind named operations. Callers ask for the value or request an update instead of accessing the storage directly. That creates a place to control access and later change how the value is represented. 1
Original version: any caller can assign the shared setting
A small application stores its volume as a shared integer. The examples deliberately keep that shared lifetime so we can distinguish encapsulation from removing global state.
public static class Preferences
{
public static int Volume = 50;
}Preferences.Volume = 30;
Console.WriteLine(Preferences.Volume);There is no preference-owned operation between the caller and the assignment. Finding every write requires finding every use of the field.
volume = 50volume = 30
print(volume)In a module, other code can assign the exported variable directly. The representation and its access points are the same thing.
Refactored version: give access an explicit API
public static class Preferences
{
private static int volume = 50;
public static int GetVolume() { return volume; }
public static void SetVolume(int value) { volume = value; }
}Preferences.SetVolume(30);
Console.WriteLine(Preferences.GetVolume());The private field is now reached through two operations. A property could offer similar access control while retaining assignment syntax; the explicit methods make the access paths easy to see.
_volume = 50
def get_volume():
return _volume
def set_volume(value):
global _volume
_volume = valueset_volume(30)
print(get_volume())global makes the setter update the module variable rather than create a local one. The underscore marks internal storage by convention; Python still permits a caller to bypass that convention deliberately.
What has—and has not—improved
The value still begins at fifty, and setting it to thirty still returns thirty on the next read. There is no new range validation. Rejecting values outside zero through one hundred could be useful, but that would change the contract and deserves a separate decision.
The setting is also still shared. Accessor functions do not remove hidden dependencies, make concurrent updates safe, or provide per-user settings. An injected preferences object may be the right next step when those concerns matter.
If a variable holds a mutable collection, returning the collection directly may expose the same uncontrolled mutation through another path. Encapsulate Collection addresses that additional problem.
Use this refactoring when controlling access or preparing a representation change is valuable. Two forwarding functions are an opportunity to establish a boundary, not proof that a good boundary already exists.