Remove Setting Method: Some Things Are Written in Ink, Not Pencil
Remove Setting Method explained simply — why a field that should never change after construction must not have a setter, and how read-only fields, init-only properties, and records turn 'please don't change this' into a compiler guarantee.
🖋️ The Birth Certificate With No Pen Attached
When Ananya was born, her parents — Lakshmi and Raghav — went to the municipal office in Chennai. Mr. Kannan, the registrar, was a careful man. He checked the hospital slip twice, wrote Ananya's name and her date of birth into the big register, printed the certificate, pressed the brass stamp on it, and handed it over with both hands, like it was something precious. Because it was.
Now notice something important about that certificate. It does not come with a pen. There is no little box saying "to change the date of birth, write the new date here." The date was written once, at the moment of the event, by the one person authorised to write it — and the whole system is designed so it can never be casually changed afterwards. Why? Because a date of birth is not an opinion that updates — it is a fact, fixed at one moment in time. School admission, passport, voting age, Ananya's first cricket trials — so many things will be built on top of that one date that allowing anyone with a pen to "update" it would break trust in everything above.
Years later, Ananya's school sports coach actually tried. Ananya was three months too old for the under-12 team, and the coach said, half-joking, "Just get the certificate adjusted, no?" Raghav laughed and asked him to imagine the opposite world: a certificate where any coach, any clerk, any uncle with a ballpoint pen could change the date. Within a year, nobody — no school, no passport office — would trust any certificate at all. The whole value of the document comes from the missing pen.
Compare that with the classroom attendance register lying open on the table, where any child passing by can flip a P to an A. Within a week, nobody trusts that register either.
Software has its own birth-certificate facts: an account's ID, an order number, a ticket's PNR, the timestamp when a record was created. These values are set once, at the object's "birth" — its constructor. Yet very often, out of pure habit, we give every field a public setter — we staple a pen to the certificate. The refactoring that removes the pen is called Remove Setting Method.
🧠 What is Remove Setting Method?
Remove Setting Method means deleting the setter for a field whose value should be decided once, at construction, and never changed again. The value goes in through the constructor; after that, the field is read-only.
Before — the certificate with a pen stapled to it:
class Account {
private _id: string;
constructor(id: string) {
this._id = id;
}
get id(): string { return this._id; }
set id(value: string) { // why does this even exist?
this._id = value;
}
}
// ...much later, in a faraway file:
account.id = "TEMP-FIX-42"; // identity silently corruptedAfter — written in ink:
class Account {
constructor(readonly id: string) {} // set at birth, fixed forever
}
// account.id = "TEMP-FIX-42"; // compile error — there is no penThe deep idea here is the difference between a convention and a guarantee. A code comment saying // please never change id is a convention — it works until one tired developer at 7 pm changes it anyway. Removing the setter turns the rule into a guarantee enforced by the compiler. Nobody can break the rule, because the language itself refuses. Mr. Kannan does not have to trust every coach in Chennai to behave; the certificate simply has no place to write.
Fowler's catalog gives this refactoring a one-line summary that says it all: if you do not want a field to change once the object is created, do not provide a setting method. The Refactoring Guru entry adds the practical trigger: the value is meant to be set only at creation, yet the setter keeps inviting "updates" from anywhere in the program.
Here is the conversation between code and object, before and after — notice where the guarantee comes from:
A quick sorting question for every field you design: is this value written in ink or in pencil? Ink: identity, creation time, the defining facts of the object — constructor only, no setter. Pencil: things that genuinely change during life — but even then, prefer an honest method like markDelivered() over a raw set status. If you cannot decide, start with ink: it is far easier to add a setter later than to remove one the whole codebase has started using.
🔍 When do we need it?
Look for these signs:
- A setter that is called only once, right after
new. Search for usages. If every caller doesconst t = new Ticket(); t.pnr = "8642097531";, the value clearly belongs in the constructor, and the setter is just a slower, riskier constructor. - Identity and birth facts with setters.
id,orderNumber,pnr,createdAt,admissionNo— values that define the object. A changeable identity is a contradiction, like a changeable date of birth. - "Who changed this?" debugging sessions. A bug report says the order's customer ID differs from what was saved. With a public setter, every line in the program is a suspect. Remove the setter, and the question cannot even arise.
- Auto-generated setters from habit. Many classes carry a get/set pair for every field only because an IDE template produced them. Each unnecessary setter widens the class's mutable surface for no benefit — and the construction ritual it encourages (
newfollowed by six setter calls in the right order) is really a Long Parameter List problem wearing a disguise: the constructor's job has been smeared across six fragile steps. - Freshly created parameter objects. When you cure Data Clumps by introducing a parameter object such as
AddressorDateRange, that object gets shared between methods and callers. If it has setters, one method can mutate it while another still holds a reference — a classic aliasing bug. Parameter objects should be born without pens.
When is it not needed? When the value genuinely changes during the object's life. A student's marks this term, a delivery's current location, a game's score — these are pencil values. For them, controlled mutation is correct; just make it an intention-revealing method rather than a naked setter. Remove Setting Method is for ink values only.
If you audit a typical business class honestly, the split usually surprises people — most fields never legitimately change at all:
More than half the fields were ink wearing pencil costumes, and a fifth should not have been stored fields at all — they were derived values (totalWithTax) pretending to be facts. Only a quarter genuinely needed any mutation path.
A quick chart to sort any field you are unsure about — how often it truly changes versus how much of the system depends on it:
Reading it: orderId sits deep in "Remove setter now" — never changes, everything depends on it. status changes often and everything depends on it — it keeps a mutation path, but a guarded, named one. A draft note that only one screen reads? A plain setter will not hurt anyone.
| Field | Ink or pencil? | Correct door |
|---|---|---|
admissionNo, pnr, orderId | Ink | Constructor only |
dateOfBirth, createdAt, admittedOn | Ink | Constructor only |
status, currentClass, seat | Pencil | Named method with rules (promoteToNextClass()) |
score, feePaid | Pencil | Named method (addPoints(), recordFeePayment()) |
totalWithTax, age | Neither — derived | Compute in a getter; store nothing |
🪄 Before and after at a glance
Before — every field stays editable forever:
// BEFORE: a railway ticket where everything is changeable, always
class TrainTicket {
pnr = "";
passengerName = "";
trainNo = "";
journeyDate = "";
seat = "";
}
const ticket = new TrainTicket();
ticket.pnr = "8642097531"; // four-step ritual just to be born
ticket.passengerName = "Meera";
ticket.trainNo = "12626";
ticket.journeyDate = "2026-07-15";
// ...weeks later, in some helper:
ticket.pnr = "0000000000"; // the ticket's identity, gone
ticket.journeyDate = "2026-07-99"; // nonsense, silently acceptedAfter — ink fields fixed at construction; only the genuinely changing value keeps a controlled path:
// AFTER: ink for identity, a guarded pencil only where life requires it
class TrainTicket {
private _seat: string;
constructor(
readonly pnr: string, // ink
readonly passengerName: string, // ink
readonly trainNo: string, // ink
readonly journeyDate: string, // ink
seat: string,
) {
this._seat = seat; // pencil — but guarded below
}
get seat(): string { return this._seat; }
reallocateSeat(newSeat: string): void { // honest name, one entry point
if (!/^\d{1,2}[A-F]$/.test(newSeat)) throw new Error("Invalid seat");
this._seat = newSeat;
}
}
const ticket = new TrainTicket("8642097531", "Meera", "12626", "2026-07-15", "32B");
// ticket.pnr = "0000000000"; // compile error: read-only
ticket.reallocateSeat("41C"); // allowed, validated, searchableTwo improvements at once. Construction became one honest step instead of a ritual of setter calls — the object is complete and valid from its first breath. And the only changing value, the seat, now changes through one named, validated door.
The object's whole life, drawn as states — there is exactly one moment when ink can be written, and it is the birth moment:
🪜 Step-by-step, the safe way
Removing a setter sounds like one keystroke, but doing it safely on a living codebase needs care. Here is the slow, sure recipe — the registrar's procedure, in code.
Step 1: Confirm the field is truly ink. Read the class, think about the domain. Is this value really fixed after creation, or does some legitimate business flow change it? If a real flow changes it, stop — this refactoring is not for that field.
Step 2: Find every caller of the setter. Use Find Usages. Sort the results into two piles: calls that happen at creation time (right after new), and calls that happen later in life. The first pile will move into the constructor. Each item in the second pile is either a bug you have just discovered, or proof that the field is pencil after all.
Step 3: Make sure the constructor can receive the value. If it does not yet take this value, add a parameter for it. During the transition, both roads exist:
// Intermediate state: constructor takes the value, setter still alive
class Account {
private _id: string;
constructor(id: string) {
this._id = id;
}
get id(): string { return this._id; }
set id(value: string) { this._id = value; } // still here — for one more step
}Step 4: Migrate creation-time setter calls into constructor arguments, one call site at a time.
// before:
const a = new Account("");
a.id = "ACC-1009";
// after:
const a = new Account("ACC-1009");Compile and test after each migration. Boring, safe, fast.
Step 5: Delete the setter and make the field read-only.
class Account {
constructor(readonly id: string) {}
}Now compile. This is the beautiful moment: the compiler becomes your detective. Every remaining assignment anywhere in the codebase lights up as a red error — a complete, guaranteed list of every place that was still scribbling on the certificate. Route each one through construction instead.
Step 6: Run all tests — especially anything touching frameworks. Serializers, ORMs, and model binders sometimes fill objects through setters behind the scenes; the next callout explains the escape routes.
The payoff curve is real. Here is what one team measured across a quarter as they removed setters from their core entities — the metric is hours per month spent on "who changed this value?" debugging hunts:
The last sliver never quite reaches zero — pencil fields still exist — but every locked field permanently retires a whole category of bug. A value that cannot change cannot have been changed wrongly.
Frameworks are the one genuine trap. Some ORMs, JSON deserializers, and form binders expect a parameterless constructor plus settable properties. Before deleting setters on a persisted or serialized class, check your framework's options: EF Core and System.Text.Json both support constructor binding, and C# init accessors let deserialization set values during creation while blocking everything afterwards. The right answer is almost never "keep the public setter"; it is usually "use constructor binding or an init accessor." Also remember: on a published library, removing a public setter is a breaking API change — schedule it with a proper version bump.
🏗️ A bigger real-life example
A school management system in Pune tracks student records. The original class is all pencil — and the bugs show it:
// BEFORE: everything editable, forever, by anyone
class StudentRecord {
admissionNo = "";
dateOfBirth = "";
admittedOn = "";
currentClass = 6;
feePaidPaise = 0;
}
// Found scattered across the codebase:
record.dateOfBirth = "2014-03-30"; // "small correction" before sports trials
record.admissionNo = "PNE-NEW-77"; // duplicate-fix script rewrote identities
record.admittedOn = "2026-04-01"; // backdated to dodge a late-admission fee
record.currentClass = 8; // skipped class 7 entirely — typo? fraud?Every one of these lines compiled happily. The first one is Ananya's sports coach with a ballpoint pen — except in this school's software, the pen actually worked. The system had rules in people's heads — "date of birth never changes", "admission number is permanent" — but the code enforced none of them. Now we remove the pens:
// AFTER: ink facts locked at admission; life changes go through named doors
class StudentRecord {
private _currentClass: number;
private _feePaidPaise = 0;
constructor(
readonly admissionNo: string, // identity — ink
readonly dateOfBirth: string, // birth fact — ink
readonly admittedOn: string, // history fact — ink
startingClass: number,
) {
if (!/^PNE-\d{4}$/.test(admissionNo)) throw new Error("Bad admission number");
this._currentClass = startingClass;
}
get currentClass(): number { return this._currentClass; }
get feePaidPaise(): number { return this._feePaidPaise; }
promoteToNextClass(): void { // the ONLY way class changes
if (this._currentClass >= 12) throw new Error("Already in final class");
this._currentClass += 1; // always exactly +1 — no skipping
}
recordFeePayment(paise: number): void {
if (paise <= 0) throw new Error("Payment must be positive");
this._feePaidPaise += paise;
}
}Walk through what each removal bought us:
dateOfBirth,admissionNo,admittedOnare now facts, not variables. The sports-trials "correction" and the identity-rewriting script are compile errors today. If a certificate was genuinely entered wrong, the school issues a new corrected record — a deliberate, visible act by an authorised person, exactly like Mr. Kannan's office reissuing a certificate — instead of silently overwriting history.currentClassis pencil, but the pencil is guarded:promoteToNextClass()moves exactly one class up. The "skipped class 7" bug is unrepresentable.- Money flows only through
recordFeePayment(), which refuses nonsense.
The final shape, as a class diagram — count the doors:
And here is the immutability bonus that beginners often miss: this object is now safe to share. The reports module, the ID-card printer, and the fees module can all hold the same StudentRecord without fear, because none of them can spoil the ink fields for the others. No defensive copying, no "who changed this?" hunts.
College corner — immutability and thread safety: the sharing argument becomes life-or-death the moment threads enter the picture. A mutable object shared between two threads needs locks, memory barriers, and prayer: thread A may read currentClass halfway through thread B writing it, and on modern CPUs each core's cache can hold a stale copy of a mutable field unless you pay for synchronisation. An immutable object needs none of that. Since no field can change after construction, there is no write to race against — every thread forever sees the same values, no locks required. This is why functional languages default to immutability, why String is immutable in Java and C#, and why concurrency guides repeat the mantra: share immutable data freely; share mutable data carefully or not at all. One subtlety worth knowing: the object must be safely published — fully constructed before its reference is shared — which is exactly what "all values in through the constructor" gives you for free. An object built by new plus six setter calls has a window of half-built visibility; a constructor-built immutable object does not.
Here is what daily life looks like for the people using the system, before and after the pens were removed:
⚙️ The same refactoring in C#
C# is wonderfully equipped for this refactoring — it offers a whole ladder of immutability, so you can pick exactly the rung you need.
Rung 1: get-only auto-property. The classic Remove Setting Method. Assignable in the constructor, locked everywhere else:
public class Account
{
public string Id { get; } // no setter at all
public Account(string id) => Id = id;
}
// account.Id = "oops"; // CS0200: read-onlyRung 2: readonly fields. The same guarantee for plain fields — the compiler permits assignment only at declaration or in the constructor:
public class Account
{
private readonly string _id;
public Account(string id) => _id = id;
}Rung 3: init-only properties (C# 9+). Sometimes you want callers to use friendly object-initializer syntax, but still lock the value after creation. Replace set with init:
public class StudentRecord
{
public string AdmissionNo { get; init; }
public string DateOfBirth { get; init; }
}
var s = new StudentRecord
{
AdmissionNo = "PNE-1042", // allowed: we are still at the birth moment
DateOfBirth = "2013-08-09",
};
s.DateOfBirth = "2014-03-30"; // CS8852: init-only, object already createdinit is the precise legal language for "the pen exists only inside the municipal office, on the day of issue." Deserializers and object initializers can write the value during creation; the moment construction finishes, the ink dries.
Rung 4: records — whole-object immutability in one keyword.
public record TrainTicket(string Pnr, string PassengerName,
string TrainNo, string JourneyDate, string Seat);
var ticket = new TrainTicket("8642097531", "Meera", "12626", "2026-07-15", "32B");
// "Changing" the seat = issuing a fresh certificate, old one untouched:
var moved = ticket with { Seat = "41C" };
Console.WriteLine(ticket.Seat); // 32B — original unharmed
Console.WriteLine(moved.Seat); // 41C — new objectPositional record properties are init-only automatically, records compare by value, and the with expression answers the eternal question "but how do I change it then?" — you don't; you create a corrected copy. This non-destructive update style is exactly how real certificates work: corrections produce a freshly issued document, and anyone holding the old one can see it is the old one.
| Rung | Syntax | Writable when? | Best for |
|---|---|---|---|
readonly field | private readonly string _id; | Declaration or constructor | Internal state |
| Get-only property | public string Id { get; } | Constructor only | Public ink facts |
| Init-only property | public string Id { get; init; } | Constructor + object initializer | Deserialization-friendly ink |
| Record | record Ticket(string Pnr, ...) | Construction; copies via with | Whole-object immutability |
Python offers the same ladder in its own dialect — a frozen dataclass locks every field, and a read-only property guards a single one:
# Python: ink via frozen dataclass; guarded pencil via a normal class
from dataclasses import dataclass
@dataclass(frozen=True)
class TrainTicket:
pnr: str
passenger_name: str
train_no: str
journey_date: str
seat: str
def reallocate_seat(self, new_seat: str) -> "TrainTicket":
# no mutation: return a corrected copy, old ticket untouched
from dataclasses import replace
return replace(self, seat=new_seat)
ticket = TrainTicket("8642097531", "Meera", "12626", "2026-07-15", "32B")
moved = ticket.reallocate_seat("41C")
# ticket.pnr = "000" # FrozenInstanceError — there is no pendataclasses.replace is Python's with expression: a freshly issued ticket, the old one unharmed.
🧰 IDE support
The tooling does a lot of the watching for you:
- Visual Studio: the built-in code-style rule IDE0044 ("Add readonly modifier") flags private fields that are only ever assigned in the constructor — the IDE itself tells you which fields are secretly ink. Quick Actions (
Ctrl+.) apply the fix. The compiler errors CS0200 (read-only property) and CS8852 (init-only after creation) then guard the rule forever. - JetBrains Rider / ReSharper: inspections suggest making fields
readonlyand converting properties to get-only or init-only when no later writes exist; since the 2020.3 releases both tools understand records andinitaccessors fully, including quick-fixes to convert classes to records. Find Usages on a setter (Shift+F12/Alt+F7) gives you the two piles from Step 2 in seconds. - IntelliJ IDEA (Java): the inspection "field may be
final" plays the same role for Java, and the Refactor menu can introduce constructor parameters when you remove a setter. - VS Code (TypeScript): no single automated action, but the method is simple: add
readonlyto the field, and the TypeScript compiler's error list becomes your complete checklist of every illegal write. Find All References on the setter shows you which callers to migrate first.
As always, the IDE handles the mechanics. Deciding which fields are ink — that judgement about your domain is the human part.
⚖️ Benefits and risks
| Benefits | Risks / Costs |
|---|---|
| "Never changes" becomes a compile-time guarantee, not a polite request — wrong code cannot even build | Frameworks needing parameterless constructors + setters may break; check constructor binding / init support first |
| Debugging shrinks: a value set once can never have been "changed by someone, somewhere" | If the value legitimately changes during life, removing the setter is simply wrong — sort ink from pencil first |
| Safe sharing: many modules, threads, or caches can hold the object with zero fear of it changing underneath them | Removing a public setter from a published library is a breaking change for external callers |
Construction becomes one honest, complete step instead of a fragile new-plus-setters ritual | Multi-step initialization (builders, wizards) may need an init accessor or builder pattern instead of full removal |
| The missing setter documents intent better than any comment — readers instantly know the field is fixed | Updating immutable objects means creating copies; in extremely hot loops this can cost allocations (measure before worrying) |
🩺 Which smells does it cure?
| Smell | How Remove Setting Method helps |
|---|---|
| Mutable data everywhere | Shrinks the mutable surface: fewer fields can change, so fewer states the object can be in and fewer ways for it to go wrong |
| Large Class | Every deleted setter trims the class's public surface; what remains is the real contract |
| Long Parameter List (in disguise) | Replaces the new-then-six-setters construction ritual with one complete constructor — initialization stops being smeared across callers |
| Data Clumps | Freshly introduced parameter objects stay trustworthy: born valid, shared safely, immune to aliasing surprises |
| Data Class | Cutting reflexive get/set pairs forces the question "what does this class actually do?" — behaviour starts replacing raw access |
The whole locking-down toolkit, on one map — Remove Setting Method is one branch of a bigger family that also includes Hide Method and Encapsulate Collection:
📝 Quick revision box
+=================================================================+
| REMOVE SETTING METHOD — REVISION CARD |
+=================================================================+
| SMELL SIGN : setter on a field that should be fixed at birth |
| (id, orderNo, pnr, createdAt, dateOfBirth) |
| PICTURE : birth certificate — written ONCE, no pen attached |
+-----------------------------------------------------------------+
| THE MOVE : 1. Confirm the field is INK, not pencil |
| 2. Find Usages -> two piles: at-birth vs later |
| 3. Constructor receives the value |
| 4. Migrate at-birth setter calls into constructor |
| 5. DELETE setter; make field readonly |
| -> compiler lists every illegal write for you |
| 6. Test, incl. serializers / ORMs |
+-----------------------------------------------------------------+
| C# LADDER : readonly field -> { get; } -> { get; init; } |
| -> record (+ with-expressions for changed copies) |
| REMEMBER : "change" an immutable object = issue a NEW one; |
| pencil values get named methods, never raw setters |
+=================================================================+🏋️ Practice exercise
A food delivery app in Kolkata has this class:
class Order {
orderId = "";
customerId = "";
placedAt = "";
restaurantId = "";
status = "PLACED";
deliveryBoyId = "";
totalPaise = 0;
}
// Found around the codebase:
order.orderId = "ORD-" + Math.random(); // set after new Order()
order.placedAt = new Date().toISOString(); // also set after construction
order.status = "DELIVERED"; // set directly from 5 places
order.totalPaise = order.totalPaise - 500; // "discount" applied by mutation
order.customerId = "CUST-ADMIN"; // a support tool "fixing" dataYour tasks:
- Sort the seven fields into ink and pencil. For each ink field, say in one line why it must never change after the order is created. (Use the Figure 4 quadrant chart to place each field.)
- Apply Remove Setting Method step by step for the ink fields: route values through the constructor, migrate the creation-time assignments, then mark the fields
readonly. Keep the code compiling at every step. statusis pencil — but raw assignment from five places is dangerous. Replace it with methodsaccept(),pickUp(deliveryBoyId), andmarkDelivered(), each checking that the move is legal (e.g., an order cannot be delivered before pickup). Where doesdeliveryBoyIdget written now? Draw the allowed moves as a state diagram like Figure 5.- The "discount by mutation" line is a bug factory. Redesign
totalPaiseso the total is fixed at construction and a discount produces a new value through a method likewithDiscount(paise)returning a newOrder. What happens to code still holding the old object — and why is that a good thing? - Bonus (C#): write
Orderas a record with the ink fields positional andStatushandled through methods or non-destructivewithupdates. Show onewithexpression in action. Bonus (Python): write the same as a frozen dataclass usingdataclasses.replace. - College-corner question: two background threads share the same
Order— one prints invoices, one sends SMS updates. Explain in three sentences why your refactored design needs no locks for the ink fields, and what would still need care forstatus. - Reflection question: your analytics team says they need to "fix" old orders'
placedAtfor a timezone migration. Should you re-add the setter? What is the honest alternative — and what would Mr. Kannan's office do?
Frequently asked questions
- How do I 'change' a value if the object is immutable and has no setters?
- You do not edit the old object — you create a new one with the new value. A corrected birth certificate is a freshly issued certificate, not the old paper with scratching. In TypeScript, write a method like withSeat(newSeat) that returns a new object; in C#, records give you this for free with the with-expression: ticket with { Seat = "42A" }. The old object stays untouched, so anyone still holding it sees consistent data.
- My ORM or JSON serializer needs setters to fill the object. Do I have to keep them public?
- Usually not. Modern frameworks support better options: Entity Framework Core and System.Text.Json can both use constructor binding, and C# init accessors let deserializers set a property during object creation while still blocking later changes. Check your framework's documentation before surrendering — a private setter or an init accessor almost always satisfies the framework without opening the field to the whole world.
- Should I remove every setter from every class?
- No. Remove the setter only for fields that genuinely should not change after construction — identities, creation timestamps, defining values. A field that legitimately changes during the object's life, like a player's score or an order's status, deserves controlled mutation, ideally through an intention-revealing method like addPoints() rather than a raw setter. The skill is telling 'written in ink' fields apart from 'written in pencil' fields.
- What is the difference between readonly, get-only, init, and a record in C#?
- Four levels of the same idea. A readonly field can only be assigned in the constructor. A get-only auto-property ({ get; }) is the property version of that. An init accessor ({ get; init; }) additionally allows setting in an object initializer during creation, then locks. A record applies init-only to all positional properties at once and adds value equality and with-expressions — full-object immutability in one keyword.
- Why exactly are immutable objects easier and safer?
- Three plain reasons. First, no surprises: a value checked once stays checked — nobody changed it behind your back, so debugging hunts for 'who modified this?' simply disappear. Second, safe sharing: you can hand the same object to ten modules, ten threads, or a cache with zero fear, because nobody can spoil it for the others. Third, honest code: the absence of a setter tells every future reader 'this is fixed' more reliably than any comment could.
Further reading
Related Lessons
Long Parameter List: The Chai Order That Took Ten Instructions
Long Parameter List code smell made simple — why methods with too many arguments cause bugs, and how parameter objects make calls short, clear, and safe.
Data Clumps: The Friends Who Always Travel Together
Data Clumps code smell for beginners — learn to spot groups of values that always travel together and bundle them into one class, like a student ID card.
Encapsulate Field: Let the Object Guard Its Own Data
Encapsulate Field explained simply — why public fields let any code corrupt an object's data, and how private fields with getters and setters put the object back in charge.
Hide Method: The Secret Masala Stays Inside the Kitchen
Hide Method explained simply — why a method that only the class itself uses should not sit on the public menu, and how lowering visibility to private or internal shrinks the API, protects internals, and frees you to change code without fear.