Defending the Data
In modern software architecture, "encapsulation" is often misunderstood as simply making fields private and exposing them via public Properties. However, true encapsulation is about localizing invariants—ensuring that data and the logic that manipulates it reside in the same place.
This article explores three powerful rules for enforcing encapsulation, moving beyond standard practices to create code that is safer, more maintainable, and easier to reason about.
Rule 1: Do Not Use Getters or Setters
The Rule: Do not use setters or getters (including public C# Properties) for non-Boolean fields.
The Explanation
In the C# world, we are taught to use Properties ({ get; set; }) as the standard way to expose data. However, exposing data—even through a getter—breaks encapsulation.
When you expose data, you create a Pull-Based Architecture. External classes fetch data from your object to perform calculations elsewhere. This turns your objects into "dumb" data containers and spreads your business logic (invariants) across "Manager" or "Service" classes.
To defend your data, you should aim for a Push-Based Architecture. Instead of asking an object for its data, you pass the necessary arguments to the object and ask it to perform the work. This is often referred to as "Tell, Don't Ask."
C# Example
❌ The "Pull" Approach (Violation)
In this example, the PostLinkGenerator pulls data out of Website and User. If the internal structure of User changes, the generator breaks.
✅ The "Push" Approach (Correct)
Here, we eliminate the getters. We push the requirement into the classes. The Website knows how to format its own link, and the User knows how to utilize the website.
Refactoring Pattern: Eliminate Getter or Setter
- Make the getter/property
private. - Fix the resulting compiler errors by moving the logic into the class (Push Code Into Classes).
- Delete the unused private getter.
Rule 2: Never Have Common Affixes
The Rule: Your code should not have methods or variables with common prefixes or suffixes.
The Explanation
If you find variables like depositAmount, depositCurrency, and depositDate floating in a method or class, it indicates that these elements belong together but haven't been unified. Common affixes are a cry for help from your code, signaling a missing abstraction (Class).
By grouping these into a dedicated class, you satisfy the Single Responsibility Principle. You can also enforce invariants on that specific group of data (e.g., ensuring Amount is never negative) in one place, rather than repeating checks globally.
C# Example
❌ The Violation
Here, the player prefix is used on multiple variables in the global scope or a large game loop class.
✅ The Refactoring (Encapsulate Data)
We extract these variables into a dedicated Player class.
Refactoring Pattern: Encapsulate Data
- Create a new class to hold the related variables.
- Move variables into the class (initially keeping getters/setters if necessary).
- Update call sites to use the new class instance.
- Apply Rule 1 (Eliminate Getters/Setters) to move behavior into the new class.
Rule 3: Enforce Sequence
The Rule: Eliminate "Sequence Invariants" by using the constructor.
The Explanation
A "Sequence Invariant" occurs when the code requires method A to be called before method B, but the compiler does not enforce it. For example, needing to call Connect() before Query(). If a developer forgets the order, the application crashes at runtime.
We can eliminate this risk by doing the required work in the Constructor. In Object-Oriented Programming, the constructor is guaranteed to run before any instance method. If the initialization happens in the constructor, it creates a guarantee: If you have an instance of this object, it is in a valid state.
C# Example
❌ The Sequence Invariant
Here, the developer must remember to call Capitalize() before Print(). This is brittle.
✅ The Enforced Sequence
By moving the capitalization logic into the constructor (or a method called by the constructor), we remove the invariant. It is now impossible to have an un-capitalized CapitalizedString.
Summary
Defending your data is about more than just private keywords. By adhering to these rules:
- Avoid Getters/Setters to force behavior into the classes that hold the data (Push vs. Pull).
- Eliminate Common Affixes to discover hidden abstractions and improve cohesion.
- Enforce Sequence via constructors to rely on the compiler rather than human memory.