মূল বিষয়বস্তুতে যান
Clean Code Mastery

Self Encapsulate Field: একজন দারোয়ান তোমার ডেটা পাহারা দিক

Self Encapsulate Field সহজভাবে বোঝানো — একটা class কেন তার নিজের field পড়া ও লেখার জন্য getter এবং setter ব্যবহার করে, নিরাপদ ধাপ, TypeScript ও C# উদাহরণসহ।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringself encapsulate fieldencapsulationorganizing datagetters and settersinheritance

আম্মার রান্নাঘরের নিয়ম

ধরো ফাতেমাদের বাড়ির রান্নাঘরের তাকে সব কিছু থাকে — চিনি, ঘি, বিস্কুট, আচারের বোয়াম, চালের টিনের পেছনে লুকিয়ে রাখা ঈদের বিশেষ মিষ্টি। আগে সবাই সরাসরি ঢুকে যা খুশি নিয়ে নিত। ফাতেমার ছোট ভাই রুবেল রাতের খাবারের আগে ছয়টা বিস্কুট নিয়ে নিত, তারপর ভাত খেতে চাইত না। দাদু কফিতে বাড়তি চিনি নিতেন, যদিও ডাক্তার একেবারে নিষেধ করেছেন। কতটুকু ঘি আছে কেউ জানত না — যতক্ষণ না হঠাৎ ঈদের সকালে শেষ হয়ে যায়, বারোজন মেহমান আসছে আর নতুন কিনতে যাওয়ার সময় নেই।

সেই ঈদের বিপদ শেষ সীমা ছাড়িয়ে গেল। আম্মা একটাই নিয়ম বানালেন, রাতের খাবার টেবিলে আদালতের আদেশের মতো গম্ভীরভাবে ঘোষণা করলেন: তাক থেকে কেউ সরাসরি কিছু নেবে না। সব অনুরোধ আম্মার মাধ্যমে যাবে।

রুবেল বিড়বিড় করল। দাদু না শোনার ভান করলেন। কিন্তু এক সপ্তাহের মধ্যে সবাই দেখল এই নিয়ম কী করতে পারে। রুবেল বিস্কুট চাইলে আম্মা ঘড়ি দেখেন — রাতের খাবারের এক ঘণ্টা আগে বিস্কুট নেই, কোনো ব্যতিক্রম নেই। দাদু চিনি চাইলে আম্মা চুপচাপ সেই sugar-free প্যাকেট দেন যেটা দেখতে প্রায় একই। যে কেউ ঘি নিলে, আম্মা তার ছোট ডায়েরিতে টুকে রাখেন — ঠিক কখন টিন শেষ হবে জানেন আর তিন দিন আগেই নতুন কিনে রাখেন। ঈদের বিপদ আর কখনো হতে পারে না।

আর এখানেই আসল চালাক কথাটা, যেটা দুইবার পড়ার মতো: পরিবারের সদস্যরা একটুও বদলায়নি। রুবেল এখনো "বিস্কুট" চায়। দাদু এখনো "চিনি" চান। নতুন শব্দ বা নতুন অভ্যাস কাউকে শিখতে হয়নি। শুধু পথটা বদলেছে — প্রতিটা অনুরোধ এখন একজন দারোয়ানের মধ্য দিয়ে যায় যিনি সব নিয়ম জানেন।

একদিন আম্মা আত্মীয়ের বিয়েতে যান, আর চাচি রান্নাঘরের দায়িত্ব নেন। চাচির নিজের ধরন আছে — তিনি প্রতিটা নাস্তার সাথে একটুকরো গুড় দেন, কারণ এটা তার নিজের মায়ের ঐতিহ্য ছিল। পরিবারকে কি আবার শেখাতে হলো? না। তারা একইভাবে নাস্তা চাইল, আর উত্তরগুলো শুধু একটু মিষ্টি হয়ে এলো। চাচি কিভাবে অনুরোধের উত্তর দেওয়া হয় সেটা override করলেন, কোনো পরিবারের সদস্যকে না বদলে।

code-এ Self Encapsulate Field ঠিক এটাই করে। একটা class-এর প্রতিটা method সরাসরি field থেকে ডেটা না নিয়ে (তাক), প্রতিটা read এবং write একটা getter এবং একটা setter-এর মধ্য দিয়ে যায় (আম্মা)। ডেটার একজন দারোয়ান হয়। আর পরে, একটা subclass (চাচি) অন্য কাউকে না ছুঁয়ে উত্তর বদলাতে পারে।

Figure 1: রান্নাঘরের যাত্রা — এলোমেলো বিশৃঙ্খলা থেকে একজন শান্ত দারোয়ানের কাছে।

Self Encapsulate Field কী?

Self Encapsulate Field হলো Martin Fowler-এর Refactoring বই থেকে একটা refactoring। নির্দেশটা সংক্ষিপ্ত: একটা field-এর জন্য getter এবং setter তৈরি করো, আর class-কে নিজের internal access-এর জন্যও সেগুলো ব্যবহার করাও। class-এর ভেতরে কোনো method আর raw field সরাসরি ছোঁয় না — শুধু getter এবং setter ছোঁয়।

self শব্দটা লক্ষ্য করো। সাধারণ "Encapsulate Field" refactoring বাইরের class থেকে field রক্ষা করে — field কে private করে এবং public-কে getter দেয়। Self Encapsulate Field আরেক স্তর গভীরে যায়। এটা বলে: আমার নিজের method-গুলোও আমার field সরাসরি ছোঁবে না। রুবেল বাড়িতে থাকে, কিন্তু রুবেলও আম্মাকে জিজ্ঞেস করে।

বইটার ২য় সংস্করণে Fowler এই ধারণাটাকে Encapsulate Variable নামের একটা বড় refactoring-এ ভাঁজ করেছেন, কিন্তু self-encapsulation-এর ধারণাটা তার bliki-তে নিজস্ব দরকারি অভ্যাস হিসেবে আলোচনা হয়।

একটা class নিজের ডেটার সাথে এত আনুষ্ঠানিক কেন হবে? কারণ সরাসরি field read হলো একটা শক্ত তার। এটা raw value আনা ছাড়া আর কিছুই করতে পারে না। অন্যদিকে getter হলো একটা নরম তার — একটা seam। সেই seam-এর মধ্য দিয়ে পরে যোগ করা যায়:

  • Validation — setter খারাপ মান প্রত্যাখ্যান করে, প্রতিবার, যে method-ই assign করুক।
  • Lazy loading — getter প্রথম ব্যবহারে মান হিসাব করে বা fetch করে।
  • Logging এবং change tracking — setter প্রতিটা পরিবর্তন নোট করে, আম্মার ডায়েরির মতো।
  • Subclass overriding — একটা child class getter কে redefine করে ভদ্রভাবে আচরণ বদলায়, যেভাবে চাচি গুড় যোগ করেছিলেন।
Figure 2: সরাসরি access-এ কোনো checkpoint নেই; দারোয়ানের পথ প্রতিবার নিয়ম প্রয়োগ করে।
💡

মনে রাখার সহজ উপায়: raw field হলো তাক, accessor হলো আম্মা। যা একজন দারোয়ানের মধ্য দিয়ে যায় তা পরে চেক করা, গণনা করা, দেরি করানো বা বদলে দেওয়া যায় — বাকি পরিবারকে না জানিয়েই। তাক থেকে সরাসরি নেওয়া জিনিস পারে না।

চালিয়ে যাওয়ার আগে একটা সৎ কথা: এটা একটা প্রস্তুতিমূলক refactoring। নিজে থেকে, এটা একটুও আচরণ বদলায় না। আম্মার নিয়মের প্রথম দিনে বিস্কুটের স্বাদ একই থাকে। এর মূল্য আসে এটা পরে কী সম্ভব করে তা থেকে। অনেক বড় data refactoring — যেমন Replace Data Value with Object — অনেক সহজ হয়ে যায় যখন সব access ইতিমধ্যে একটা method-এর মধ্য দিয়ে যাচ্ছে।

কলেজ কর্নার: compiler এবং design-এর দৃষ্টিকোণ থেকে, আমরা এখানে একটা indirection layer তৈরি করছি। সরাসরি field access একটা নির্দিষ্ট offset-এ memory load হিসেবে compile হয় — দ্রুত, কিন্তু জমাট। একটা virtual accessor object-এর method table-এর মধ্য দিয়ে dispatch হয়, যার মানে object-এর runtime type সিদ্ধান্ত নেয় কোন code চলবে। সেই একটা indirection স্তরই ডেটায় polymorphism সক্ষম করে। বিখ্যাত কথাটা "computer science-এর সব সমস্যা আরেকটা indirection স্তর দিয়ে সমাধান করা যায়" আক্ষরিক অর্থেই প্রযোজ্য। আধুনিক JIT compiler সাধারণত তুচ্ছ getter-গুলো inline করে, তাই ব্যবহারিক performance খরচ প্রায় শূন্য — design flexibility-ই আসলে তুমি কিনছ।

কখন এটা দরকার?

প্রতিটা class-এর প্রতিটা field self-encapsulate করো না — সেটা নিজের জন্য আনুষ্ঠানিকতা হবে, যেমন এক গ্লাস পানির জন্য আম্মার কাছে লিখিত আবেদন করা। এই সংকেতগুলো দেখলে এই refactoring করো:

  1. একটা subclass একটা মান বদলাতে চাইছে। একটা PromotionalLoan rate ছাড় দিতে চায়; একটা TestClock জমাট সময় ফেরত দিতে চায়। সরাসরি field read subclass-কে কোনো hook দেয় না। একটা virtual getter দেয়।
  2. setter-এর নিয়ম আছে, কিন্তু internal code সেগুলো এড়িয়ে যায়। setRate() validate করে rate negative না, কিন্তু তিনটা internal method সরাসরি this.rate = x লেখে — তাহলে validation হলো ছিদ্রযুক্ত বেড়া। রুবেল পেছনের দরজা দিয়ে বিস্কুট চুরি করছে। setter-এর মধ্য দিয়ে প্রতিটা write পাঠালে ছিদ্র বন্ধ হয়।
  3. Access-এ আচরণ যোগ করতে চাইছ — caching, lazy initialization, logging, "dirty" flag — এবং ঠিক এক জায়গায় যোগ করতে চাইছ।
  4. একটা বড় refactoring-এর প্রস্তুতি নিচ্ছ। ধরো একটা plain string field-এ Primitive Obsession সমস্যা আছে আর তুমি এটাকে proper class-এ মোড়াতে চাও। পঞ্চাশটা line সরাসরি field ধরলে পঞ্চাশটা line এডিট করতে হবে। সব কিছু একটা getter-এর মধ্য দিয়ে গেলে একটা method এডিট করো। একইভাবে Data Clumps bundle করতে চাইলেও self-encapsulation সহজ করে দেয়।
  5. একটা field computed হতে যাচ্ছে। আজ totalMarks সংরক্ষিত; কাল এটা বিষয়ভিত্তিক নম্বর থেকে হিসাব হওয়া উচিত। access ইতিমধ্যে getTotalMarks()-এর মধ্য দিয়ে গেলে, "সংরক্ষিত" থেকে "computed"-এ যাওয়া প্রতিটা caller-এর কাছে অদৃশ্য থাকে।

আর কখন করবে না? যখন class ছোট, final, value-এর মতো, আর field-এর কোনো নিয়ম নেই। একটা সাধারণ Point যার x আর y আছে তার দারোয়ান দরকার নেই। Fowler নিজেই স্বীকার করেন সরাসরি access পছন্দ করেন যতক্ষণ না এটা অস্বস্তিকর হয় — তারপর refactor করেন। সেই শিথিল মনোভাবটাই সঠিক।

Figure 3: একটা সাধারণ codebase-এ field-access bug আসলে কোথা থেকে আসে — বেশিরভাগই নিয়ম এড়িয়ে যাওয়া write।

এখানে একটা ছোট decision table যা পকেটে রাখতে পারো:

পরিস্থিতিসরাসরি field accessSelf encapsulate
ছোট value-এর মতো class, কোনো নিয়ম নেইসহজ রাখো, ঠিক আছেঅপ্রয়োজনীয় আনুষ্ঠানিকতা
Setter-এ validation নিয়ম আছেবেড়ায় ছিদ্রএকটা বন্ধ গেট
Subclass মান বদলাতে চায়কোনো hook নেইgetter override করো
Field পরে computed হতে পারেপ্রতিটা caller ভাঙবেCaller কিছু টের পাবে না
বড় data refactoring-এর প্রস্তুতিপরে পঞ্চাশটা editপরে একটা edit

এক নজরে আগে এবং পরে

ধরো TypeScript-এ একটা Loan class। refactoring-এর আগে, method গুলো সরাসরি field থেকে this.rate এবং this.principal পড়ে।

// BEFORE — methods grab fields directly from the shelf
class Loan {
  private principal: number;
  private rate: number; // yearly rate, e.g. 0.12
 
  constructor(principal: number, rate: number) {
    this.principal = principal;
    this.rate = rate;
  }
 
  monthlyInterest(): number {
    return (this.principal * this.rate) / 12; // direct read
  }
 
  totalInterest(months: number): number {
    return ((this.principal * this.rate) / 12) * months; // direct read again
  }
}

refactoring-এর পরে, প্রতিটা read এবং write accessor-এর মধ্য দিয়ে যায়। অঙ্কটা অপরিবর্তিত — শুধু পথ বদলেছে, ঠিক যেমন পরিবার এখনো "চিনি" চায় কিন্তু আম্মার মাধ্যমে।

// AFTER — every request goes through the gatekeeper
class Loan {
  private principal: number;
  private rate: number;
 
  constructor(principal: number, rate: number) {
    this.principal = principal;
    this.setRate(rate); // writes get validation
  }
 
  protected getPrincipal(): number {
    return this.principal;
  }
 
  protected getRate(): number {
    return this.rate; // the seam — subclasses may override
  }
 
  protected setRate(value: number): void {
    if (value < 0) throw new Error("Rate cannot be negative");
    this.rate = value;
  }
 
  monthlyInterest(): number {
    return (this.getPrincipal() * this.getRate()) / 12;
  }
 
  totalInterest(months: number): number {
    return ((this.getPrincipal() * this.getRate()) / 12) * months;
  }
}
 
// Chithi takes charge — the subclass changes the answer, not the family
class PromotionalLoan extends Loan {
  private monthsElapsed = 0;
 
  protected override getRate(): number {
    return this.monthsElapsed < 3 ? super.getRate() / 2 : super.getRate();
  }
}

promotional discount সম্পূর্ণভাবে PromotionalLoan-এর ভেতরে থাকে। monthlyInterest() এবং totalInterest() একটা অক্ষরও বদলায়নি, তবুও এগুলো এখন promotional loan-এর জন্য ছাড়ের সুদ হিসাব করে। এটাই seam-এর শক্তি।

Figure 4: rate চাওয়া একটা method field কখনো ধরে না; getter উত্তর দেয়, আর subclass ভিন্নভাবে উত্তর দিতে পারে।

কলেজ কর্নার: লক্ষ্য করো subclass-এ getRate() সরাসরি field না পড়ে super.getRate() call করে। এটা গুরুত্বপূর্ণ। override যদি সরাসরি this.rate পড়ত, এটা এক স্তর উপরে শক্ত তার পুনরায় তৈরি করত আর যেকোনো আরো নিচের subclass-এর জন্য chain ভেঙে যেত। যে override super-এ delegate করে সেগুলো পরিষ্কারভাবে compose হয় — প্রতিটা স্তর নিচের স্তরকে সাজায়, যেটা Decorator pattern-এর মতোই আকৃতি, শুধু inheritance-এর মাধ্যমে প্রকাশ।

ধাপে ধাপে, নিরাপদ উপায়ে

Refactoring হলো ব্যস্ত রাস্তা পার হওয়ার মতো — ছোট ধাপ, দুইদিক দেখো, কখনো দৌড়িও না। এখানে নিরাপদ ক্রম। প্রতিটা ধাপের পরে test সবুজ রাখো।

  1. Getter তৈরি করো (এবং field লেখা হলে setter-ও)। অন্য কোনো code এখনো বদলিও না। class compile হয়; নতুন method গুলো কেউ ব্যবহার করে না। এই ধাপ একাই কিছু ভাঙতে পারে না।
class Loan {
  private rate: number;
  // ... existing code untouched ...
 
  protected getRate(): number {
    return this.rate;
  }
 
  protected setRate(value: number): void {
    this.rate = value; // no validation yet — behaviour must not change
  }
}
  1. Field-এর প্রতিটা internal read খুঁজে বের করো। Field-এ editor-এর "find usages" ব্যবহার করো। একটা একটা করে read বদলাও: this.rate হয়ে যায় this.getRate()। প্রতিটা replacement বা ছোট batch-এর পরে compile করো আর test চালাও।

  2. প্রতিটা internal write setter দিয়ে বদলাও: this.rate = x হয়ে যায় this.setRate(x)

  3. Constructor নিয়ে সিদ্ধান্ত নাও। এটা যত্নের দাবি রাখে। setter validate করলে, constructor থেকে call করলে বিনামূল্যে validation পাওয়া যায়। কিন্তু getter বা setter override যোগ্য হওয়ার কথা থাকলে, constructor subclass শেষ হওয়ার আগেই subclass code call করত — একটা ক্লাসিক ফাঁদ। সাধারণ মধ্যপন্থা: constructor-এ সরাসরি field assign করো, আর validation একটা ছোট private function-এ সরাও যা constructor এবং setter উভয়ই call করে।

  4. কেবল এখন নতুন আচরণ যোগ করো — validation, logging, laziness, বা subclass hook-এর জন্য virtual keyword। যান্ত্রিক rewiring-এর পরে আচরণ যোগ করলে প্রতিটা ধাপ ছোট আর পরীক্ষাযোগ্য থাকে।

protected setRate(value: number): void {
  if (value < 0) throw new Error("Rate cannot be negative"); // added last
  this.rate = value;
}
  1. Access কঠোর করো। নিশ্চিত করো field নিজে private যাতে কিছুই — ভেতর থেকে বা বাইরে থেকে — দারোয়ানকে এড়িয়ে যেতে না পারে। রান্নাঘরের পেছনের দরজা লক করো।
Figure 5: একটা field-এর জীবন যখন সে বিশ্বাস অর্জন করে — প্রতিটা transition হলো একটা ছোট, পরীক্ষাযোগ্য ধাপ।
⚠️

ধাপ মেশাবে না। সবচেয়ে সাধারণ ভুল হলো rewiring-এর একই commit-এ validation যোগ করা। তারপর test fail হলে বলতে পারবে না — rewiring কিছু ভাঙল নাকি নতুন validation একটা মান প্রত্যাখ্যান করল যেটা পুরোনো code আগে allow করত? প্রথমে শূন্য আচরণ পরিবর্তনে পথ বদলাও। তারপর আচরণ বদলাও। দুটো ছোট নিরাপদ ধাপ একটা বড় ঝুঁকিপূর্ণ ধাপের চেয়ে ভালো।

একটা বড় বাস্তব উদাহরণ

ধরো ঢাকার একটা school library-র software আছে, যেটা তারিক নামের একজন part-time developer রক্ষণাবেক্ষণ করে। একটা LibraryMember track করে একজন ছাত্র কতটা বই ধার নিয়েছে আর তাদের ধার নেওয়ার সীমা কত। তারিকের প্রথম সংস্করণ সর্বত্র field সরাসরি পড়ে:

// BEFORE
class LibraryMember {
  private borrowedCount = 0;
  private limit = 3;
 
  canBorrow(): boolean {
    return this.borrowedCount < this.limit;
  }
 
  borrow(): void {
    if (this.borrowedCount >= this.limit) {
      throw new Error("Limit reached");
    }
    this.borrowedCount = this.borrowedCount + 1;
  }
 
  returnBook(): void {
    this.borrowedCount = this.borrowedCount - 1; // oops — can go below zero!
  }
}

একই সপ্তাহে দুটো requirement আসে। প্রথম: পরীক্ষার মৌসুমে দশম শ্রেণীর ছাত্রছাত্রীরা ৫টা বইয়ের বেশি সীমা পাবে, প্রধান শিক্ষক বলেন। দ্বিতীয়: লাইব্রেরিয়ান সুমাইয়া ম্যাডাম, borrowedCount পরিবর্তন হলেই log চান, কারণ count গুলো ভুল যাচ্ছে আর তিনি একটা bug সন্দেহ করছেন। দেখো returnBook — এটা negative হয়ে যেতে পারে যদি কোনো ছাত্র এমন বই ফেরত দেয় যেটা কখনো ধার হিসেবে রেকর্ড হয়নি।

সরাসরি field access রেখে তারিককে প্রতিটা method এডিট করতে হতো, আর সম্ভবত একটা মিস হতো। self-encapsulation দিয়ে দুটো requirement এক জায়গায় ল্যান্ড করে:

// AFTER
class LibraryMember {
  private borrowedCount = 0;
  private limit = 3;
 
  protected getLimit(): number {
    return this.limit; // seam for subclasses
  }
 
  protected getBorrowedCount(): number {
    return this.borrowedCount;
  }
 
  protected setBorrowedCount(value: number): void {
    if (value < 0) throw new Error("Count cannot go below zero"); // bug, caught!
    console.log(`borrowedCount: ${this.borrowedCount} -> ${value}`); // the log
    this.borrowedCount = value;
  }
 
  canBorrow(): boolean {
    return this.getBorrowedCount() < this.getLimit();
  }
 
  borrow(): void {
    if (!this.canBorrow()) throw new Error("Limit reached");
    this.setBorrowedCount(this.getBorrowedCount() + 1);
  }
 
  returnBook(): void {
    this.setBorrowedCount(this.getBorrowedCount() - 1);
  }
}
 
// Exam season rule lives in its own small class
class ExamSeasonMember extends LibraryMember {
  protected override getLimit(): number {
    return 5;
  }
}

তারিক কী পেল সেটা ভালো করে দেখো:

  • negative count bug এখন অসম্ভব। যেকোনো method থেকে যেকোনো write — আগামী বছর যে এই article পড়েনি তার লেখা method-সহ — guard-এর মধ্য দিয়ে যায়।
  • logging প্রতিটা পরিবর্তনে স্বয়ংক্রিয়ভাবে দেখায়। সুমাইয়া ম্যাডামের রহস্য পাঁচটা জায়গায় print statement না যোগ করে একটা log stream পড়ে সমাধান হবে।
  • পরীক্ষার মৌসুমের নিয়ম হলো একটা subclass-এ চারটা line। canBorrow() এবং borrow() বদলায়নি, তবুও এগুলো নতুন সীমা মেনে চলে — ঠিক যেমন পরিবার নতুন করে না শিখেই গুড় পেয়েছিল।
Figure 6: subclass শুধু getter override করে। সমস্ত বিদ্যমান logic অপরিবর্তিতভাবে এর মধ্য দিয়ে যায়।

C#-এ একই refactoring

C# এই refactoring-কে স্বাভাবিক মনে করায়, কারণ C# property হলো accessor যেটা দেখতে field-এর মতো। একটা property হলো field-এর পোশাক পরা একজন দারোয়ান। C#-এ self-encapsulate করতে raw field-কে property-তে পরিণত করো আর internal code-কে property ব্যবহার করাও:

public class Loan
{
    private decimal _rate;
 
    public Loan(decimal principal, decimal rate)
    {
        Principal = principal;
        Rate = rate; // goes through the setter — validation applies
    }
 
    public decimal Principal { get; }
 
    // virtual: the seam for subclasses
    public virtual decimal Rate
    {
        get => _rate;
        protected set
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException(nameof(value),
                    "Rate cannot be negative");
            _rate = value;
        }
    }
 
    public decimal MonthlyInterest() => Principal * Rate / 12m;
 
    public decimal TotalInterest(int months) => MonthlyInterest() * months;
}
 
public class PromotionalLoan : Loan
{
    private readonly int _monthsElapsed;
 
    public PromotionalLoan(decimal principal, decimal rate, int monthsElapsed)
        : base(principal, rate)
        => _monthsElapsed = monthsElapsed;
 
    public override decimal Rate
        => _monthsElapsed < 3 ? base.Rate / 2m : base.Rate;
}

মনে রাখার মতো তিনটা C# নোট:

  • Auto-property ইতিমধ্যে self-encapsulated। public decimal Rate { get; set; } লিখলে, প্রতিটা access — internal এবং external — compiler-generated accessor-এর মধ্য দিয়ে যায়। পরে এটাকে backing field সহ full property দিয়ে বদলানো যায়, আর কোনো caller বদলায় না। এটা language দ্বারা করা refactoring।
  • virtual হলো subclass-এর জন্য দরজা খোলা। non-virtual property হলো বন্ধ গেট; শুধু তখনই virtual চিহ্নিত করো যখন সত্যিই variation আশা করো।
  • Constructor-এ virtual call সাবধান। উপরের Loan constructor Rate setter call করে। subclass যদি setter override করত, সেই override subclass constructor body-র আগেই চলত — একটা পরিচিত C# ফাঁদ। এখানে শুধু getter override হয়েছে, তাই নিরাপদ, কিন্তু প্রতিবার এটা নিয়ে ভাবো।

কলেজ কর্নার: constructor ফাঁদটার সুনির্দিষ্ট ব্যাখ্যা দরকার। C# (আর Java)-তে, base-class construction-এর সময় virtual dispatch সক্রিয়। তাই base constructor চলে → virtual call হয় → subclass override চলে → কিন্তু subclass-এর নিজের field এখনো default মানে, কারণ subclass constructor body এখনো execute হয়নি। C++ ভিন্নভাবে আচরণ করে — base construction-এর সময় object-এর dynamic type হলো base type, তাই base version চলে। তোমার language কোন নিয়ম অনুসরণ করে জানা মানে নিরাপদ constructor আর NullReferenceException-এর মধ্যে পার্থক্য যেটা শুধু subclass-এর জন্য দেখা দেয়।

Python-এ এক ঝলক

Python-এর property decorator কোনো call site না বদলেই একই seam দেয় — attribute access syntax একই থাকে, যা এই refactoring-কে caller-দের কাছে প্রায় অদৃশ্য করে:

class Loan:
    def __init__(self, principal: float, rate: float) -> None:
        self._principal = principal
        self.rate = rate  # goes through the property setter below
 
    @property
    def rate(self) -> float:
        return self._rate  # the seam — override in a subclass if needed
 
    @rate.setter
    def rate(self, value: float) -> None:
        if value < 0:
            raise ValueError("Rate cannot be negative")
        self._rate = value
 
    def monthly_interest(self) -> float:
        return self._principal * self.rate / 12
 
 
class PromotionalLoan(Loan):
    @property
    def rate(self) -> float:
        return super().rate / 2  # first three months promotion

Caller এখনো loan.rate লেখে — তারা বলতেই পারে না raw attribute ধরছে নাকি guarded property। Python মূলত Self Encapsulate Field-কে একটা built-in upgrade path হিসেবে দেয়: plain attribute দিয়ে শুরু করো, আর যেদিন নিয়ম দরকার হবে সেদিন property-তে রূপান্তর করো কোনো caller না ভেঙে।

IDE সাপোর্ট

সুখবর: এই refactoring এত সাধারণ যে বেশিরভাগ IDE যান্ত্রিক অংশটা স্বয়ংক্রিয় করে।

Toolসাপোর্ট
Visual Studio (C#)Encapsulate Field refactoring (Ctrl+R, Ctrl+E)। "use property everywhere" অফার করে — যেটা class-এর নিজস্ব usage আপডেট করে, এক শটে self-encapsulation দেয়।
JetBrains Rider / ReSharperEncapsulate Field class-এর ভেতরে এবং বাইরে usage বদলানোর option সহ; backing field সহ property generate করতে পারে।
IntelliJ IDEA (Java)Refactor → Encapsulate Fields, "use accessors even when field is accessible" checkbox সহ — সেই checkbox-ই এই refactoring-এর self অংশ।
VS Code (TypeScript)কোনো single-click encapsulate refactoring built in নেই। Rename Symbol (F2) ব্যবহার করো field rename করতে (যেটা নিরাপদে সব usage খুঁজে পায়), তারপর accessor যোগ করো আর file-এর মধ্যে find-and-replace দিয়ে usage বদলাও।

IDE বিরক্তিকর rewiring টাইপোমুক্তভাবে করে। তোমার কাজ থাকে চিন্তার অংশ: কোন field-গুলো দারোয়ান পাওয়ার যোগ্য, accessor virtual হওয়া উচিত কিনা, আর constructor কী করবে।

সুবিধা, ঝুঁকি, আর কখন লেনদেন সার্থক

সুবিধাঝুঁকি / খরচ
Subclass একটা মান কিভাবে পড়া হয় তা override করতে পারে — ডেটায় polymorphism।বাড়তি indirection: reader-দের getter খুলে দেখতে হয় এটা তুচ্ছ কিনা।
Validation, logging, lazy loading, change tracking এক জায়গায়।ছোট value-এর মতো class-এ ceremony যেগুলোর কখনো এটার দরকার হবে না।
Class storage form-এর উপর নির্ভর করা বন্ধ করে — সংরক্ষিত field চুপচাপ computed হতে পারে।Constructor থেকে call করা overridable accessor খুব তাড়াতাড়ি subclass code চালাতে পারে।
বড় refactoring-এর ভিত্তি তৈরি করে (primitive মোড়ানো, ডেটা সরানো)।প্রতিটা field-এ অন্ধভাবে করলে class আমলাতান্ত্রিক মনে হয়।
"বেড়ার ছিদ্র" বন্ধ করে যেখানে internal write setter-এর নিয়ম এড়িয়ে যেত।কোনো সুবিধাই আসে না যতক্ষণ না আসলেই seam দরকার — এটা একটা বিনিয়োগ।

সৎ সারসংক্ষেপ: Self Encapsulate Field আজ একটু indirection বিনিময়ে কালকের অনেক flexibility দেয়। যখন কাল সত্যিই আসছে তখন লেনদেন করো।

Figure 7: প্রতিটা field এই মানচিত্রে রাখো — উপরের ডানদিকেরগুলো আগে encapsulate করো, নিচের বামেরগুলো একা ছেড়ে দাও।
Figure 8: library system সব write এক guarded setter-এর মধ্য দিয়ে route করার পর, skipped-validation bug প্রায় অদৃশ্য হয়ে গেল।

কলেজ কর্নার: এই refactoring-এর নিচে একটা গভীর নীতি লুকিয়ে আছে — Bertrand Meyer-এর বলা uniform access principle: একটা client যে notation ব্যবহার করে তা প্রকাশ করা উচিত নয় যে মানটা সংরক্ষিত নাকি computed। C# property এবং Python property এটা স্বাভাবিকভাবে পূরণ করে; Java-র getX() convention এটা সামাজিকভাবে পূরণ করে। Self Encapsulate Field হলো uniform access principle একটা class-এর নিজের সাথে সম্পর্কে প্রয়োগ: এমনকি class-ও যত্ন নেবে না rate সংরক্ষিত সংখ্যা, computed মান, cached fetch, বা subclass-এর overridden উত্তর।

এটা কোন smell সারায়?

Smellএই refactoring কিভাবে সাহায্য করে
Primitive Obsessionএকটা primitive field-এর সব access এক method-এর মধ্য দিয়ে পাঠায়, তাই পরে এটাকে proper type-এ মোড়ানো পঞ্চাশটা নয় এক জায়গা ছোঁয়।
Data Clumpsclumped field-গুলোর প্রতিটায় accessor থাকলে, সেগুলো এক object-এ bundle করা মানে প্রতিটা method না লিখে কয়েকটা getter redirect করা।
Data Classbare field-কে তার প্রথম আচরণ দেয় — একটা guarded accessor — আর একটা জায়গা যেখানে আরো আচরণ জমতে পারে।
Duplicate Codeপ্রতিটা assignment-এর আগে পুনরাবৃত্তি হওয়া validation এক setter-এ ভেঙে পড়ে।
Shotgun Surgeryএকটা মান কিভাবে fetch হয় তার পরিবর্তন (cache করো, log করো!) অনেক method জুড়ে edit-এর বদলে এক edit হয়ে যায়।
Figure 9: এক পাতায় পুরো পাঠ — gatekeeper, seam, আর সতর্কতা।

দ্রুত রিভিশন বাক্স

+--------------------------------------------------------------+
|              SELF ENCAPSULATE FIELD — REVISION               |
+--------------------------------------------------------------+
| Idea     : Class reads/writes its OWN field only through     |
|            a getter and setter (one gatekeeper, like Amma).  |
| Why      : Creates a seam — add validation, logging, lazy    |
|            loading, or subclass overrides in ONE place.      |
| Steps    : 1. Add getter/setter (no behaviour change)        |
|            2. Replace internal reads  -> getter              |
|            3. Replace internal writes -> setter              |
|            4. Decide constructor policy                      |
|            5. THEN add new behaviour                         |
|            6. Keep the field private                         |
| Watch out: virtual accessors + constructors = danger;        |
|            don't ceremonialize tiny value classes.           |
| Cures    : Primitive Obsession prep, Data Clumps prep,       |
|            duplicate validation, shotgun surgery.            |
| C# bonus : auto-properties give you this almost for free.    |
+--------------------------------------------------------------+

অনুশীলন

এই Student class নাও। এতে একটা bug আর দুটো আসন্ন requirement আছে। Self Encapsulate Field দিয়ে এটা refactor করো।

class Student {
  private marks = 0; // out of 100
 
  addMarks(extra: number): void {
    this.marks = this.marks + extra; // can exceed 100!
  }
 
  grade(): string {
    if (this.marks >= 90) return "A";
    if (this.marks >= 75) return "B";
    return "C";
  }
 
  reportLine(): string {
    return `Marks: ${this.marks}, Grade: ${this.grade()}`;
  }
}

তোমার কাজ:

  1. getMarks() আর setMarks(value) যোগ করো আর addMarks, grade, এবং reportLine rewire করো যাতে কোনো method সরাসরি this.marks না ছোঁয়। test চালাও — আচরণ অবশ্যই একই থাকবে।
  2. setter-এ bug ঠিক করো: marks অবশ্যই 0 এবং 100-এর মধ্যে থাকবে। অন্যথায় error throw করো।
  3. নতুন requirement: একটা SportsQuotaStudent subclass পড়ার সময় 5টা grace mark পায় (সংরক্ষিত নয়!)। getter override করো যাতে grade() আর reportLine() স্বয়ংক্রিয়ভাবে grace mark মেনে চলে, এডিট না করেই।
  4. Bonus: C#-এ private backing field আর virtual getter সহ property ব্যবহার করে class rewrite করো। নিশ্চিত করো constructor কোনো overridable member call করে না।
  5. Extra credit: Python-এ @property দিয়ে আরেকবার rewrite করো, আর লক্ষ্য করো student.marks ব্যবহার করা caller একটুও বদলায় না।

ধাপ 3-এ যদি grade() আর reportLine()-এ শূন্য পরিবর্তনের দরকার হয়, তুমি পুরো পাঠ বুঝেছ: seam কাজটা করেছে, কাঁচি নয়। আম্মা বদলেছেন; পরিবার টেরই পায়নি।

সচরাচর জিজ্ঞাসা

Encapsulate Field আর Self Encapsulate Field-এর পার্থক্য কী?
Encapsulate Field বাইরের class-গুলোকে field সরাসরি ধরতে দেয় না। Self Encapsulate Field আরেক ধাপ এগিয়ে যায় — class-এর নিজের method-গুলোও field সরাসরি ধরে না, getter এবং setter-এর মধ্য দিয়ে যায়। class নিজের বাড়িতেও ভদ্র অতিথির মতো থাকে।
একটা সাধারণ field-এর জন্য এত আনুষ্ঠানিকতা কি দরকার?
মাঝে মাঝে না! field যদি সহজ, private, আর কখনো validation বা subclass override দরকার না হয়, তাহলে সরাসরি access ঠিকই আছে। Martin Fowler নিজেই বলেন সরাসরি access ব্যবহার করেন যতক্ষণ না অস্বস্তিকর হয়, তারপর refactor করেন। কারণ থাকলে করো, অন্ধ নিয়ম হিসেবে নয়।
subclass-গুলো এই refactoring নিয়ে কেন মাথা ঘামাবে?
সরাসরি field read কে subclass বদলাতে পারে না। কিন্তু getter override করা যায়। তাই একটা PromotionalLoan তিন মাসের জন্য rate অর্ধেক করতে চাইলে, শুধু getRate() override করলেই হয়। self-encapsulation ছাড়া কোনো hook নেই — field পড়ে এমন প্রতিটা method কপি করে এডিট করতে হতো।
constructor-ও কি setter ব্যবহার করবে?
এটা বিচারের বিষয়। setter ব্যবহার করলে বিনামূল্যে validation পাওয়া যায়। কিন্তু getter বা setter override যোগ্য হলে, construction-এর সময় call করলে subclass সম্পূর্ণ তৈরি হওয়ার আগেই subclass code চলে যেতে পারে। অনেক টিম constructor-এ field সরাসরি assign করে আর validation একটা shared private method-এ রাখে।
C# property কি এটা বিনামূল্যে দিয়ে দেয়?
বেশিরভাগ ক্ষেত্রে হ্যাঁ। C# auto-property আগে থেকেই একটা accessor, তাই property ব্যবহার করা code সংজ্ঞা অনুযায়ী self-encapsulated। refactoring তখনও দরকার যখন পুরোনো code raw field ধরে, বা property কে virtual করতে হয় যাতে subclass override করতে পারে।

আরো দেখো

সম্পর্কিত পাঠ

Replace Data Value with Object: তোমার Data-কে একটা নিজের ঘর দাও

Replace Data Value with Object সহজভাবে বোঝানো — কীভাবে একটা plain string বা number-কে validation আর behaviour সহ একটা ছোট class-এ রূপান্তর করতে হয়। TypeScript আর C# record-এর উদাহরণ দিয়ে।

আরও পড়ুন

Change Value to Reference: বিশটা ফটোকপি না, একটাই অফিস ফাইল

Change Value to Reference সহজ ভাষায় — একই entity-র ডুপ্লিকেট কপি কেন পুরনো হয়ে যায়, আর registry বা repository দিয়ে একটাই shared instance কীভাবে data consistent রাখে।

আরও পড়ুন

Change Reference to Value: যেকোনো ১০ টাকার নোটই সমান

Change Reference to Value সহজভাবে বোঝানো হয়েছে — একটা shared, mutable reference object-কে কীভাবে content-based equality সহ একটা ছোট immutable value object-এ রূপান্তর করতে হয়, TypeScript আর C# record-এর উদাহরণসহ।

আরও পড়ুন

Primitive Obsession: যখন সব কিছুই শুধু একটা string বা number

Primitive Obsession সহজ ভাষায় — কেন plain string আর number bug লুকিয়ে রাখে, আর কীভাবে Money বা Address-এর মতো value object দিয়ে code-কে safe আর পরিষ্কার করা যায়।

আরও পড়ুন