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

Introduce Parameter Object: পাঁচটা আলাদা উত্তর না দিয়ে একটা ঠিকানা কার্ড দাও

Introduce Parameter Object সহজ ভাষায় — একই parameter গ্রুপ বারবার বিভিন্ন method-এ ঘুরে বেড়ানো আসলে একটা লুকানো concept-এর চিহ্ন। সেগুলো একটা named object-এ bundle করলে signature ছোট হয়, order ভুল বন্ধ হয়, আর behaviour এসে জুটে যায়।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringintroduce parameter objectparameter objectlong parameter listdata clumpsimmutabilitytypescriptcsharp

কুরিয়ার আর ঠিকানা কার্ডের গল্প

ধরো রহিম কুরিয়ার কোম্পানিতে কাজ করে। প্রতি শুক্রবার সে একই বাসা থেকে একই পার্সেল নিতে যায় — তোমার দাদি তোমার মামাতো ভাই করিমকে চট্টগ্রাম থেকে আমের আচারের কৌটো পাঠান।

আগের দিনে বুকিং ছিল একটা ক্লান্তিকর ব্যাপার। সালাম ভাই কাউন্টারে বসে ফর্ম নিয়ে একে একে জিজ্ঞেস করতেন। "বাড়ি নম্বর?" — "১২-বি।" "রাস্তা?" — "আজিজ লেন ৪।" "এলাকা?" — "পোস্ট অফিসের পাশে।" "শহর?" — "চট্টগ্রাম।" "পিন কোড?" — "৪০০০।" পাঁচটা প্রশ্ন, পাঁচটা উত্তর, প্রতি শুক্রবার।

আর কাউন্টারেই শেষ না। ডিপোতে সর্টিং লোকও একই পাঁচটা প্রশ্ন করত। ডেলিভারি বয় ঝাপসা লেবেল থেকে একই পাঁচটা উত্তর পড়ত। এক বৃষ্টির শুক্রবারে সালাম ভাই রাস্তার নামটা শহরের বাক্সে লিখলেন — দাদির আচার পনেরো দিন ধরে ভুল জেলায় ঘুরে বেড়াল। করিম সেই মাসে সাদা ভাত খেল আর এক ঘণ্টা ফোনে বকাবকি করল।

তারপর দাদি একটা সহজ কিন্তু দারুণ কাজ করলেন। লোকাল প্রিন্ট শপে গিয়ে একটা ছোট কার্ড ছাপালেন: বাড়ি নম্বর, রাস্তা, এলাকা, শহর, পিন — সব একটা ঝকঝকে কার্ডে "করিম, চট্টগ্রাম" শিরোনামসহ। এখন রহিম এলে দাদি একটাই কার্ড দেন। কেউ পাঁচটা প্রশ্ন করে না। কেউ order গুলিয়ে ফেলে না, কারণ কার্ডে labelled বাক্স আছে। প্রিন্ট শপ এমনকি প্রথম draft ছাপাতে রাজি হয়নি কারণ পিনে সংখ্যা কম ছিল — একটা পার্সেলও নড়ার আগেই ভুলটা ধরা পড়ে গেল।

করিম পরের বছর নতুন ফ্ল্যাটে উঠলে? দাদি একটাই নতুন কার্ড ছাপালেন। সালাম ভাই, সর্টিং লোক, ডেলিভারি বয় — সবাইকে আলাদা করে পাঁচটা নতুন উত্তর শেখালেন না।

সহজ কথায়: কোডে সেই পাঁচটা আলাদা উত্তর হলো পাঁচটা আলাদা parameter, method-এর পর method-এ বারবার। আর ছাপানো কার্ড হলো parameter object — একটা named object যেটা পুরো গ্রুপটা বহন করে। যে refactoring কার্ডটা ছাপায় তার নাম Introduce Parameter Object

চিত্র ১: পাঁচটা loose উত্তর থেকে একটা ছাপানো কার্ড যা সবাই reuse করে

Introduce Parameter Object আসলে কী?

Introduce Parameter Object মানে হলো এমন parameter গ্রুপ খোঁজা যেগুলো সবসময় একসাথে চলে — একই cluster method-এর পর method-এ দেখা যায় — আর সেগুলোকে একটাই object দিয়ে replace করা।

আগে, প্রতিটা method signature-এ পুরো গ্রুপটা বানান করতে হয়:

function bookParcel(houseNo: string, street: string, area: string,
                    city: string, pinCode: string): Booking { /* ... */ }

পরে, প্রতিটা method একটাই সুনামযুক্ত object নেয়:

function bookParcel(to: Address): Booking { /* ... */ }

এটা এত বড় ব্যাপার কেন? কারণ গ্রুপটা কখনোই আসলে পাঁচটা আলাদা জিনিস ছিল না। বাড়ি নম্বর, রাস্তা, এলাকা, শহর, পিন কোড — এগুলো একটাই ধারণা: একটা ঠিকানা। Code ধারণাটা বহন করছিল, কিন্তু কখনো নাম দেয়নি। Martin Fowler-এর Refactoring বইয়ে এটা catalog-এ আছে। তাঁর কথা হলো: parameter-এর বারবার ফেরা একটা গ্রুপ আসলে নাম পেতে অপেক্ষা করা একটা concept। সেটাকে class দেওয়া মানে concept-টাকে বাস্তব করে তোলা।

একবার concept বাস্তব হলে তিনটা ভালো জিনিস ঘটে:

  1. Signature ছোট হয়। পাঁচটা parameter হয় একটা। Method পড়তে আর call করতে সহজ হয়।
  2. Order ভুল গায়েব হয়। আর street আর area এলোমেলো করা যাবে না, কারণ তুমি loose string না পাঠিয়ে labelled বাক্সসহ একটা কার্ড পাঠাচ্ছো।
  3. Object behaviour আকর্ষণ করে। এটাই magic bonus। Validation ("পিন অবশ্যই ছয় সংখ্যার"), formatting ("পুরো label print করো"), comparisons ("এটা কি একই শহর?") — এই সব logic যেটা এখন caller-দের কাছে copy-paste হয়ে ছড়িয়ে আছে, সেটা নতুন class-এ একটা স্বাভাবিক ঘর পায়। Refactoring Guru-ও এটা বলে: একবার data একসাথে থাকলে, সেই data-র উপর operations-গুলোও সেখানে চলে আসতে পারে।

কলেজ কর্নার: দাদি যা ছাপিয়েছেন সেটাকে domain-driven design-এ value object বলে। Value object-এর নিজস্ব কোনো পরিচয় নেই — একই পাঁচটা মান সহ দুটো address card একই ঠিকানা, ঠিক যেমন দুটো একশো টাকার নোট একই টাকা। Value object গুলো contents দিয়ে তুলনা হয় (value equality, reference equality নয়), সাধারণত immutable থাকে, আর নিজের ছোট নিয়মগুলো বহন করে: একটা Address জানে valid পিন কোড কেমন, একটা DateRange জানে শুরু শেষের পরে আসতে পারে না, একটা Money object জানে টাকায় ডলার যোগ হয় না। Introduce Parameter Object প্রায়ই "একগুচ্ছ primitive" থেকে proper value object-এর পথে প্রথম পদক্ষেপ।

💡

সহজ মনে রাখার কৌশল: একই তিন, চার, বা পাঁচটা parameter স্কুলের লাইনের মতো অনেক method-এর মধ্য দিয়ে একসাথে হাঁটছে — তারা অপরিচিত নয়, তারা একটা পরিবার। পরিবারটাকে একটা পদবি দাও। Address, DateRange, FlightQuery, ExamSlot — যে মুহূর্তে একটা ভালো নাম মাথায় আসে, class-টা থাকার যোগ্য।

পার্সেল পাঠানোর অভিজ্ঞতা সবার জন্য কীভাবে বদলে যায় দেখো — রহিমের দিনও দাদির মতোই ভালো হয়:

চিত্র ২: পার্সেল পাঠানোর যাত্রা, কার্ডের আগে এবং পরে

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

তোমার code-এ এই লক্ষণগুলো দেখো:

  1. Long Parameter List smell। একটা method পাঁচ, ছয়, সাতটা parameter নেয়। Caller-রা signature-এ তাকায়, কমা গোনে, তারপরেও ভুল order-এ জিনিস পাঠায়। Related parameter bundle করা এই smell-এর এক নম্বর সমাধান।
  2. Data Clumps smell। একই ছোট গ্রুপ — city, area, pinCode বা startDate, endDate — অনেক ভিন্ন method signature-এ একসাথে দেখা যাচ্ছে। সেই পুনরাবৃত্তি মানে clump একটা নামের জন্য চিৎকার করছে।
  3. Order-sensitive bug। একই type-এর দুটো parameter পাশাপাশি আছে, যেমন origin: string, destination: string — একদিন কেউ এলোমেলো করে দিল। Compiler চুপ থাকে কারণ দুটোই string। Named field সহ parameter object এই ভুল অসম্ভব করে দেয়।
  4. বেদনাদায়ক field যোগ। Business আরও এক টুকরো data চায় — ধরো ডেলিভারি বয়ের জন্য একটা landmark। আজ মানে chain-এর প্রতিটা signature আর প্রতিটা caller edit করা। Parameter object থাকলে একটা class-এ একটা field যোগ করো — শুধু যে code আসলে landmark ব্যবহার করে সেটাই বদলায়।
  5. ছড়ানো twin logic। একই validation ("পিন অবশ্যই ৬ সংখ্যার") বা একই formatting (houseNo + ", " + street + ...) বেশ কয়েকটা caller-এর কাছে duplicate হয়ে আছে। Logic-এর কোনো ঘর নেই কারণ data-র কোনো ঘর নেই।

কখন দরকার নেই? দুটো parameter মাত্র একটাই method-এ একসাথে ব্যবহার হয়, অন্য কোথাও না — তাহলে নতুন class অনুষ্ঠান মাত্র, লাভ নেই। কার্ড ছাপানো মূল্যবান যখন অনেকে বারবার একই পাঁচটা প্রশ্ন করে, একবারের কথাবার্তার জন্য না।

একটু ভাবো: রহিমের কুরিয়ার কোডের পুরনো signature-এ আসলে কতটুকু কী ছিল:

চিত্র ৩: পুরনো signature-এর বেশিরভাগটাই গোপনে এক বা দুটো লুকানো concept ছিল

অর্ধেক ছিল address টুকরো, প্রায় এক তৃতীয়াংশ contact টুকরো — মাত্র একটা ছোট অংশ ছিল weight-এর মতো সত্যিকারের স্বাধীন option। দুটো লুকানো concept একটা bloated signature-এর ভেতরে লুকিয়ে ছিল।

কোনো parameter গ্রুপ object হওয়া উচিত কিনা সেটার জন্য একটা decision chart:

চিত্র ৪: এই parameter গ্রুপটা কি object হওয়া উচিত?

Chart পড়া: address fields "Make the object now"-এ আছে — অনেকগুলো, সবসময় একসাথে। startDate আর endDate মাত্র দুটো, কিন্তু এত ঘন ঘন একসাথে চলে যে DateRange এখনও worth it। Weight আর fragile flag কমই একজোড়া হয় — তাদের loose রাখো।

পরিস্থিতিLoose parameter ঠিক আছে?Parameter object?
২টা param, মাত্র একটা method-এ একসাথেহ্যাঁনা — অপ্রয়োজনীয় জটিলতা
২টা param সর্বত্র বারবার (startDate, endDate)ঝুঁকিপূর্ণহ্যাঁ — DateRange, ছোট কিন্তু শক্তিশালী
৪–৬টা param একটাই ধারণা তৈরি করে (Address)নাহ্যাঁ — মূল case
৭+ সম্পর্কহীন config optionনাহ্যাঁ, কিন্তু হয়তো দুটো object আলাদা করে
Param গুলো ইতিমধ্যে existing object-এ আছেনাPreserve Whole Object ব্যবহার করো

এক ঝলকে আগে এবং পরে

আগে — পাঁচটা loose উত্তর, সর্বত্র বারবার:

// BEFORE: the same five parameters travel through every method
function bookParcel(houseNo: string, street: string, area: string,
                    city: string, pinCode: string, weightKg: number): Booking {
  // ...
}
 
function quotePrice(houseNo: string, street: string, area: string,
                    city: string, pinCode: string, weightKg: number): number {
  // ...
}
 
function printLabel(houseNo: string, street: string, area: string,
                    city: string, pinCode: string): string {
  return `${houseNo}, ${street}, ${area}, ${city} - ${pinCode}`;
}
 
// A caller, praying the order is right:
bookParcel("12-B", "Hazratganj Lane 4", "Near Post Office", "Pune", "411001", 2.5);

পরে — একটা ছাপানো ঠিকানা কার্ড:

// AFTER: the group has a name, and travels as one object
class Address {
  constructor(
    readonly houseNo: string,
    readonly street: string,
    readonly area: string,
    readonly city: string,
    readonly pinCode: string,
  ) {}
}
 
function bookParcel(to: Address, weightKg: number): Booking { /* ... */ }
function quotePrice(to: Address, weightKg: number): number { /* ... */ }
function printLabel(to: Address): string {
  return `${to.houseNo}, ${to.street}, ${to.area}, ${to.city} - ${to.pinCode}`;
}
 
// The caller hands over one card:
const puneHome = new Address("12-B", "Hazratganj Lane 4",
                             "Near Post Office", "Pune", "411001");
bookParcel(puneHome, 2.5);

প্রতিটা field-এ readonly দেখো। কার্ড ছাপানো হয়েছে, পেন্সিলে লেখা নয় — caller-রা পড়বে, কিন্তু আঁচড় কাটবে না।

আর এখানে প্রিন্ট শপ খারাপ কার্ড প্রত্যাখ্যান করছে — booking form আর নতুন Address class-এর কথোপকথন, যেখানে validation একবারই ঘটে, তৈরির সময়:

চিত্র ৫: BookingForm একবার field দেয়; Address জন্মেই নিজেকে validate করে

ধাপে ধাপে, নিরাপদ পথে

যেকোনো refactoring-এ বিপদ হলো অর্ধেক পথে program ভেঙে ফেলা। তাই ছোট ছোট পদক্ষেপে এগোতে হবে, প্রতিটার পরে code compile হবে। এই mechanics Fowler-এর বই আর Refactoring Guru-র সতর্ক style অনুসরণ করে।

ধাপ ১: নতুন class তৈরি করো। গ্রুপের প্রতিটা parameter-এর জন্য একটা করে field। পারলে immutable বানাও — readonly fields, constructor-এ মান set।

class Address {
  constructor(
    readonly houseNo: string,
    readonly street: string,
    readonly area: string,
    readonly city: string,
    readonly pinCode: string,
  ) {}
}

আর কিছু পরিবর্তন হয়নি। Program এখনও compile হয়। এই পদক্ষেপ সম্পূর্ণ addition — শূন্য ঝুঁকি।

ধাপ ২: একটা method বেছে নাও আর পুরনোগুলোর পাশাপাশি object-টা extra parameter হিসেবে যোগ করো। এখনো কিছু delete করো না।

// Intermediate state: old parameters AND the new object, side by side
function quotePrice(houseNo: string, street: string, area: string,
                    city: string, pinCode: string, weightKg: number,
                    to: Address): number {
  // body still uses the old parameters for now
}

প্রতিটা caller আপডেট করো যাতে সে ইতিমধ্যে থাকা মান থেকে Address বানিয়ে পাঠায়। Program compile হয়, আগের মতোই কাজ করে।

ধাপ ৩: Method-এর ভেতরে পুরনো parameter গুলো object-এর field দিয়ে একটা একটা করে replace করো। প্রথমে pinCode-এর প্রতিটা ব্যবহার to.pinCode দিয়ে replace করো। Compile করো। Test করো। তারপর city থেকে to.city। Compile করো। Test করো। প্রতি পদক্ষেপে একটা parameter।

ধাপ ৪: এখন অব্যবহৃত পুরনো parameter গুলো signature থেকে delete করো। Caller গুলো আপডেট করো সেগুলো পাঠানো বন্ধ করতে। Signature পরিষ্কার চূড়ান্ত রূপে সংকুচিত হয়:

function quotePrice(to: Address, weightKg: number): number { /* ... */ }

ধাপ ৫: clump বহনকারী প্রতিটা method-এর জন্য repeat করো। bookParcel, printLabel, trackParcel — একটা method একটা করে, মাঝে মাঝে test করতে করতে।

ধাপ ৬: গৃহহীন logic খোঁজো আর object-এ নিয়ে যাও। Caller-দের মধ্যে এমন code খোঁজো যেটা শুধু এই মানগুলো নিয়ে কাজ করে — validation, formatting, comparisons — আর সেগুলো Address-এ method হিসেবে নিয়ে যাও। এখানেই refactoring-টা সবচেয়ে বেশি মুনাফা দেয়।

পুরো যাত্রা, code যে states-এর মধ্য দিয়ে যায় সেগুলো দেখো:

চিত্র ৬: একটা data clump-এর জীবন — loose fields থেকে smart object পর্যন্ত

আর দৃশ্যমান পুরস্কার — signature সংকুচিত হচ্ছে যখন safety আসলে বাড়ছে:

চিত্র ৭: refactor জুড়ে quotePrice-এর parameter গণনা

মাঝখানের ছোট bump লক্ষ্য করো: ধাপ ২-তে পুরনো parameter আর নতুন object পাশাপাশি থাকায় signature সাময়িকভাবে বড় হয়। সেই bump হলো নিরাপত্তার মূল্য — আর প্রতিটা method-এ মাত্র কয়েক মিনিট স্থায়ী হয়।

⚠️

দুটো সতর্কতা। প্রথম, সব method-এর জন্য একসাথে ধাপ ২–৪ করার লোভ সামলাও — একটা big-bang edit যেটা পঞ্চাশটা call site ভেঙে দেয় সেটাই সেই ব্যথা যা এই ধীর recipe এড়িয়ে চলে। দ্বিতীয়, সীমানায় সতর্ক থাকো: method যদি published API, web endpoint, বা serialized format-এর অংশ হয়, তাহলে signature পরিবর্তন সেই contract বদলায় যার উপর বাইরের code নির্ভর করে। সেখানে পুরনো signature thin wrapper হিসেবে রাখো যেটা ভেতরে object বানায়, আর caller-দের নিজস্ব সময়ে migrate করতে দাও।

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

ধরো রহিমের কুরিয়ার কোম্পানি সারাদেশে ডেলিভারি করে। তাদের codebase-এ সর্বত্র address clump আছে। আর আরও খারাপ — ঠিকানা সম্পর্কে নিয়মগুলোও সর্বত্র ছড়িয়ে আছে:

// BEFORE: the clump, plus its rules, copy-pasted near every caller
 
function bookParcel(houseNo: string, street: string, area: string,
                    city: string, pinCode: string, weightKg: number): Booking {
  if (!/^[1-9]\d{5}$/.test(pinCode)) {           // PIN rule, copy #1
    throw new Error("Invalid PIN code");
  }
  // ... booking logic
}
 
function quotePrice(houseNo: string, street: string, area: string,
                    city: string, pinCode: string, weightKg: number): number {
  if (!/^[1-9]\d{5}$/.test(pinCode)) {           // PIN rule, copy #2
    throw new Error("Invalid PIN code");
  }
  const localDelivery = pinCode.startsWith("4");  // zone rule, copy #1
  return (localDelivery ? 40 : 90) * weightKg;
}
 
function printLabel(houseNo: string, street: string, area: string,
                    city: string, pinCode: string): string {
  // label format, copy #1 (another slightly different copy exists in SMS code...)
  return `${houseNo}, ${street}, ${area}, ${city} - ${pinCode}`;
}

পিন কোড নিয়ম duplicate হয়ে আছে। Zone logic raw string উঁকি মারছে। Label format দুটো সামান্য ভিন্ন version-এ বেঁচে আছে। কোনো logic-এর ঘর নেই কারণ data-র ঘর নেই।

এখন parameter object introduce করি — আর দেখো এটা কীভাবে behaviour আকর্ষণ করে:

// AFTER: Address carries the data AND the rules about the data
 
class Address {
  constructor(
    readonly houseNo: string,
    readonly street: string,
    readonly area: string,
    readonly city: string,
    readonly pinCode: string,
  ) {
    // Validation moved INTO the object: a bad Address cannot even be created
    if (!/^[1-9]\d{5}$/.test(pinCode)) {
      throw new Error(`Invalid PIN code: ${pinCode}`);
    }
    if (!houseNo.trim() || !city.trim()) {
      throw new Error("House number and city are required");
    }
  }
 
  // Formatting moved in: ONE label format for the whole company
  fullLabel(): string {
    return `${this.houseNo}, ${this.street}, ${this.area}, ${this.city} - ${this.pinCode}`;
  }
 
  // Zone logic moved in: callers ask, they do not inspect raw strings
  isInZone(zonePrefix: string): boolean {
    return this.pinCode.startsWith(zonePrefix);
  }
 
  isSameCity(other: Address): boolean {
    return this.city.toLowerCase() === other.city.toLowerCase();
  }
}
 
function bookParcel(to: Address, weightKg: number): Booking {
  // no validation here — an Address in hand is ALREADY valid
  // ... booking logic
}
 
function quotePrice(to: Address, weightKg: number): number {
  return (to.isInZone("4") ? 40 : 90) * weightKg;
}
 
function printLabel(to: Address): string {
  return to.fullLabel();
}

কী ঘটল সেটার মধ্য দিয়ে হাঁটো — এটাই পুরো refactoring-এর মূল:

  • Validation constructor-এ চলে গেছে। আগে প্রতিটা method পিন re-check করত, আর যে method ভুলে যেত সেটা ছিল অপেক্ষারত bug। এখন খারাপ পিন দিয়ে Address object বানানোই যাবে না। তুমি একটা Address ধরে থাকলে সেটা valid — guaranteed। Check একবারই হয়েছে, তৈরির সময়। ঠিক যেমন প্রিন্ট শপ পাঁচ সংখ্যার পিনের কার্ড প্রত্যাখ্যান করেছিল।
  • Formatting-এর একটাই ঘর। কোম্পানির label format fullLabel()-এ। Marketing যখন label-এ landmark যোগ করতে চায়, একটাই method বদলায়।
  • সিদ্ধান্তগুলো প্রশ্ন হয়ে গেছে। quotePrice আর raw string ঘাঁটে না — ঠিকানাকে একটা প্রশ্ন করে: to.isInZone("4")। Caller একটা বাক্যের মতো পড়া যায়।

এটাই Fowler বোঝান যখন বলেন নতুন structure নিজের দিকে behaviour টানতে শুরু করে। প্রথমদিন Address ছিল একটা বিনয়ী bundle। এক সপ্তাহ পরে এটা validation, formatting, comparisons-এর মালিক — codebase জুড়ে duplicate code এতে গলে গেছে। Clump একটা সত্যিকারের domain class হয়ে উঠল।

Design-এর চূড়ান্ত রূপ দেখো:

চিত্র ৮: Courier code একটাই সমৃদ্ধ Address card-এর উপর নির্ভর করে, পাঁচটা loose string-এ নয়

কলেজ কর্নার: একটু ভাবো — data আর সেই data সম্পর্কে নিয়মগুলো একই ঠিকানায় থাকতে চায়। পাঁচটা string loose থাকলে পিন নিয়মের দাঁড়ানোর জায়গা নেই, তাই প্রতিটা caller-এর কাছে ঘাঁটি গেড়ে inconsistent copy হয়ে ছড়িয়ে পড়ে। এটা software design-এর cohesion argument: একটা class cohesive যখন তার methods সব তার নিজস্ব fields নিয়ে কাজ করে। তাজা parameter object cohesion-এ নিখুঁত স্কোর করে — তাই behaviour এতে এত স্বাভাবিকভাবে আসে। এটাও কারণ refactoring-টা Primitive Obsession ঠিক করার stepping stone — সমৃদ্ধ domain ধারণাগুলোকে bare string আর number হিসেবে represent করার অভ্যাস।

C#-এ একই refactoring

C# এই refactoring প্রায় কষ্টহীন করে তোলে, records ধন্যবাদে — class গুলো ঠিক "একটা named bundle of values"-এর জন্য ডিজাইন করা, default-এ immutable।

আগে:

// BEFORE
public Booking BookParcel(string houseNo, string street, string area,
                          string city, string pinCode, double weightKg) { /* ... */ }
 
public decimal QuotePrice(string houseNo, string street, string area,
                          string city, string pinCode, double weightKg) { /* ... */ }

পরে, একটা positional record দিয়ে:

// AFTER: one record declaration replaces five repeated parameters
public record Address(string HouseNo, string Street, string Area,
                      string City, string PinCode)
{
    // Validation lives in the record itself
    public Address
    {
        if (!System.Text.RegularExpressions.Regex.IsMatch(PinCode, @"^[1-9]\d{5}$"))
            throw new ArgumentException($"Invalid PIN code: {PinCode}");
    }
 
    public string FullLabel() => $"{HouseNo}, {Street}, {Area}, {City} - {PinCode}";
 
    public bool IsInZone(string zonePrefix) => PinCode.StartsWith(zonePrefix);
}
 
public Booking BookParcel(Address to, double weightKg) { /* ... */ }
public decimal QuotePrice(Address to, double weightKg) { /* ... */ }

Records বিনামূল্যে ঠিক সেই properties দেয় যেগুলো parameter object চায়:

Record featureParameter object-কে যা দেয়দাদির কার্ডের সমতুল্য
init-only positional propertiesImmutability — ছাপানোর পরে কেউ edit করে নাকালিতে ছাপা, পেন্সিলে নয়
Value equality (== contents তুলনা করে)দুটো equal address একই ঠিকানাদুটো একই কার্ড একই ফ্ল্যাটে নির্দেশ করে
with expressionsOriginal না ছুঁয়ে পরিবর্তিত copyকরিম ফ্ল্যাট বদলালে fresh কার্ড ছাপাও
Auto ToString()Readable logs আর debuggingকার্ড সুন্দরভাবে পড়া যায়

Caller দের জন্য একটা C# টিপ: named arguments দিয়ে construction সুন্দর পড়ে, order ভুল সম্পূর্ণ অসম্ভব হয়:

var puneHome = new Address(
    HouseNo: "12-B",
    Street:  "Hazratganj Lane 4",
    Area:    "Near Post Office",
    City:    "Pune",
    PinCode: "411001");

আর Python পাঠকদের জন্য — একই ধারণা হলো frozen dataclass, তিন লাইনেই শেষ:

# Python: the address card as a frozen dataclass
from dataclasses import dataclass
import re
 
@dataclass(frozen=True)
class Address:
    house_no: str
    street: str
    area: str
    city: str
    pin_code: str
 
    def __post_init__(self) -> None:
        if not re.fullmatch(r"[1-9]\d{5}", self.pin_code):
            raise ValueError(f"Invalid PIN code: {self.pin_code}")
 
    def full_label(self) -> str:
        return f"{self.house_no}, {self.street}, {self.area}, {self.city} - {self.pin_code}"
 
def book_parcel(to: Address, weight_kg: float) -> str:
    return f"Booked {weight_kg}kg parcel to {to.full_label()}"

frozen=True construction-এর পরে প্রতিটা field read-only করে — Python-এর কালিতে ছাপার উপায়। আর __post_init__ হলো প্রিন্ট শপের quality check।

IDE support

সুখবর হলো IDE গুলো বেশিরভাগ mechanical কাজ তোমার হয়ে করতে পারে।

  • JetBrains Rider / ReSharper: Transform Parameters refactoring (Refactor This-এর অধীনে, Ctrl+Shift+R) ঠিক এর জন্যই তৈরি। JetBrains documentation অনুযায়ী, এটা selected input parameter গুলো corresponding properties সহ একটা নতুন class-এ encapsulate করে আর একটাই operation-এ method আর সব caller আপডেট করে। Change Signature refactoring related add/remove/reorder parameter moves পরিচালনা করে।
  • IntelliJ IDEA (Java): Refactor menu-তে dedicated Introduce Parameter Object refactoring আছে — parameters select করো, class-এর নাম দাও, IDE সেটা বানিয়ে প্রতিটা call site পুনরায় লেখে।
  • Visual Studio: একটাই one-click "introduce parameter object" নেই, কিন্তু টুকরোগুলো আছে। Parameters সরাতে Change Signature ব্যবহার করো, নতুন type generate করতে Quick Actions (Ctrl+.). Compiler-এর error list তারপর call sites-এর মধ্য দিয়ে গাইড করে।
  • VS Code (TypeScript): step-by-step section থেকে manual recipe follow করো। Class বানাও, নতুন parameter যোগ করো, TypeScript compiler-এর errors-কে checklist হিসেবে ব্যবহার করো যখন পুরনো parameter গুলো একটা একটা সরাচ্ছো। Find All References বলে দেয় ঠিক কোন caller গুলো update করতে হবে।

মনে রেখো: IDE mechanical অংশটা করে। চিন্তার অংশ — ভালো নাম বেছে নেওয়া, constructor-এ কোন validation যাবে সিদ্ধান্ত নেওয়া, কোন ছড়ানো logic নতুন class-এ যাওয়া উচিত spot করা — সেটা তোমার। এটাই bundle-কে domain object-এ পরিণত করে।

সুবিধা আর ঝুঁকি

সুবিধাঝুঁকি / খরচ
Signature পাঁচ parameter থেকে একটায় — পড়তে, call করতে, মনে রাখতে সহজমাত্র একটা call site দুটো parameter সহ — নতুন class overkill, লাভ ছাড়া জটিলতা
লুকানো concept নাম পায়; Address বলে যেটা পাঁচটা string কখনো পারেনিকখনো behaviour না গেলে object passive Data Class হয়ে বসে থাকতে পারে
Argument-order bug অসম্ভব — field গুলো named, positional নয়Calls-এর মধ্যে shared mutable parameter object aliasing bug আমন্ত্রণ জানায় — readonly prefer করো
একটা field যোগ মানে একটা class ছোঁয়া, chain-এর প্রতিটা signature নয়Public API বা serialization সীমানায় নতুন type contract বদলায় — wrap বা version করো সতর্কভাবে
Object validation, formatting, comparison logic আকর্ষণ করে — duplicate code এক ঘরে গলে যায়পুরনো আর নতুন signature একসাথে থাকার transition period শেষ করতে discipline লাগে

এটা কোন smell ঠিক করে?

SmellIntroduce Parameter Object কীভাবে সাহায্য করে
Long Parameter Listমূল সমাধান: পাঁচ-parameter signature একটাই named argument-এ সংকুচিত হয়
Data Clumpsবারবার আসা গ্রুপ অবশেষে নিজস্ব class পায়; clump প্রতিটা signature-এ দেখা দেওয়া বন্ধ করে
Primitive Obsessionপাঁচটা loose string একটাই meaningful type হয় নিয়মসহ — Address, পাঁচটা আলাদা string নয়
Duplicate CodeCaller-দের কাছে copy-paste হওয়া validation আর formatting object-এ যায়, একবারই
Shotgun Surgery"একটা landmark field যোগ করো" প্রতিটা signature আর caller-এর বদলে একটা class edit করে

এই একটা refactoring যা ছোঁয়, একটাই map-এ:

চিত্র ৯: Introduce Parameter Object-এর পুরো ধারণা এক ঝলকে

দ্রুত revision box

+=================================================================+
|         INTRODUCE PARAMETER OBJECT — REVISION CARD              |
+=================================================================+
| SMELL SIGN : same 3-5 parameters travel together through        |
|              many method signatures                             |
| PICTURE    : courier boy asks 5 questions every time            |
|              -> hand him ONE printed address card               |
+-----------------------------------------------------------------+
| THE MOVE   : 1. Create the class (immutable, readonly fields)   |
|              2. Add object as EXTRA param in one method         |
|              3. Swap body to object fields, one param at a time |
|              4. Delete old params; update callers               |
|              5. Repeat for every method with the clump          |
|              6. MOVE BEHAVIOUR IN: validation, formatting,      |
|                 comparisons find their home (the magic bonus)   |
+-----------------------------------------------------------------+
| C# GOLD    : record Address(...) — immutable, value equality,   |
|              with-expressions, free ToString()                  |
| REMEMBER   : a valid object cannot be created invalid —         |
|              validate in the constructor, check nowhere else    |
+=================================================================+

অনুশীলনের exercise

ধরো নাসরিন একটা ক্লিনিকের জন্য appointment booking system বানাচ্ছে। এই code-টা দেখো:

function bookAppointment(patientName: string, patientAge: number,
                         doctorId: string, day: string, slot: string): string {
  if (patientAge < 0 || patientAge > 120) throw new Error("Bad age");
  // ... booking
  return `Booked: ${patientName} (${patientAge}) with ${doctorId} on ${day} ${slot}`;
}
 
function checkAvailability(doctorId: string, day: string, slot: string): boolean {
  // ... lookup
  return true;
}
 
function sendReminder(patientName: string, patientAge: number,
                      doctorId: string, day: string, slot: string): void {
  const isSenior = patientAge >= 60;            // seniors get a phone call
  // ... reminder
}
 
function rescheduleAppointment(patientName: string, patientAge: number,
                               doctorId: string, oldDay: string, oldSlot: string,
                               newDay: string, newSlot: string): void {
  // ... seven parameters! two of them nearly identical pairs
}

তোমার কাজ:

  1. Clump গুলো খোঁজো। (Hint: দুটো আছে — একটা patient সম্পর্কে, একটা time slot সম্পর্কে।) তুমি যে দুটো class বানাবে তাদের নাম দাও।
  2. নিরাপদ steps follow করে Patient আর TimeSlot parameter object introduce করো: class বানাও, পুরনো parameter-দের পাশে যোগ করো, body migrate করো, পুরনো parameter delete করো — একটা method একটা করে।
  3. Age validation Patient constructor-এ আর isSenior নিয়ম একটা Patient method-এ নিয়ে যাও। কোন functions সহজ হয়?
  4. তোমার নতুন type ব্যবহার করে rescheduleAppointment পুনরায় লেখো। এখন এটা কতটা parameter নেয়? oldSlot/newSlot গুলিয়ে ফেলা কতটা কঠিন হয়ে গেছে দেখো।
  5. একটা receptionist appointment book করার journey diagram (চিত্র ২-এর মতো) তৈরি করো তোমার refactor-এর আগে আর পরে। কার দিন সবচেয়ে বেশি ভালো হয়?
  6. Bonus (C#): constructor validation সহ positional record হিসেবে Patient আর TimeSlot লেখো, আর named arguments দিয়ে প্রতিটার একটা করে বানাও। Bonus (Python): frozen dataclass দিয়ে একই করো।
  7. Reflection প্রশ্ন: TimeSlot-এর কি overlaps(other: TimeSlot)-এর মতো একটা method থাকা উচিত? একটা বাস্তব ক্লিনিক system-এ সেটা কোন duplicate logic শোষণ করবে? TimeSlot কি এখন কলেজ কর্নার অর্থে একটা value object — আর তুমি কীভাবে check করবে?

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

কতগুলো parameter হলে parameter object বানানো উচিত?
কোনো নির্দিষ্ট সংখ্যা নেই। চারটা বা তার বেশি হলে বেশিরভাগ team অস্বস্তি পায়। কিন্তু আসল প্রশ্ন হলো — এই মানগুলো কি বারবার একসাথে চলে? তিনটা parameter-ও object পাওয়ার যোগ্য যদি সেগুলো পাঁচটা আলাদা method-এ একসাথে দেখা যায়। আর মাত্র একটা method-এ দুটো parameter আসলে এই refactoring লাগবে না — সেখানে খরচ বেশি, লাভ কম।
parameter object কি শুধু সমস্যাটা সরিয়ে দেয়? data তো একই থাকে।
হ্যাঁ, data সরায় — কিন্তু সাথে তিনটা নতুন কাজও হয়। গ্রুপটা একটা নাম পায়, তাই code এখন পাঁচটা anonymous string-এর বদলে Address বলে। একটা field যোগ করতে এখন একটাই জায়গা ছুঁতে হয়, প্রতিটা signature না। আর সবচেয়ে বড় কথা — validation, formatting, comparisons-এর মতো behaviour পরে এসে এখানে ঘর বাঁধে। প্রথমদিন সাধারণ bundle, ত্রিশদিন পর সমৃদ্ধ domain class।
parameter object কি mutable নাকি immutable হওয়া উচিত?
immutable রাখো। parameter object caller আর method-এর মধ্যে shared থাকে, আর প্রায়ই store বা আরও পাঠানো হয়। mutable হলে একটা method চুপচাপ বদলে দিতে পারে যখন আরেকটা method এখনও সেটার reference ধরে আছে — বিভ্রান্তিকর aliasing bug। TypeScript-এ readonly fields ব্যবহার করো, C#-এ record বা init-only properties। মান পরিবর্তন করতে হলে পুরনোটা না ছুঁয়ে নতুন object বানাও।
Introduce Parameter Object আর Preserve Whole Object-এর পার্থক্য কী?
এরা কাছের আত্মীয়। Introduce Parameter Object হয় যখন মানগুলো এখনও কোনো object-এ নেই — loose parameter হিসেবে ঘুরছে, তাই তুমি brand-new class বানাও। Preserve Whole Object হয় যখন মানগুলো ইতিমধ্যে একটা object-এ আছে, কিন্তু caller সেগুলো বের করে টুকরো টুকরো করে পাঠায় — সেখানে সমাধান হলো পুরো object-টাই পাঠানো। আগে দেখো কোনো suitable object আছে কিনা; না থাকলেই নতুন বানাও।
এটা কি আমার public API বা serialized data ভেঙে দেবে?
পারে, তাই সীমানায় সতর্ক থাকো। নিজের codebase-এর ভেতরে IDE সব caller আপডেট করে, কিছু ভাঙে না। কিন্তু method যদি published library, web API, বা JSON-এ serialize হওয়া data-র অংশ হয়, তাহলে পাঁচটা parameter থেকে একটা object-এ যাওয়া মানে সেই contract বদলানো যার উপর বাইরের code নির্ভর করে। সেখানে পুরনো signature-টা thin wrapper হিসেবে রাখো যেটা ভেতরে object বানায়, অথবা API ঠিকমতো version করো।

আরো দেখো

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

Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার

Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।

আরও পড়ুন

Data Clumps: যে বন্ধুরা সবসময় একসাথে ঘোরে

শিক্ষার্থীদের জন্য Data Clumps code smell — শেখো কীভাবে সবসময় একসাথে চলা value-এর গ্রুপ চেনা যায় আর সেগুলোকে একটা class-এ bundle করা যায়, ঠিক যেমন একটা student ID card।

আরও পড়ুন

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

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

আরও পড়ুন

Extract Class: অতিরিক্ত কাজে ডুবে যাওয়া class-কে একটু সাহায্য করো

Extract Class refactoring শেখো একটা মজার school office-এর গল্পের মাধ্যমে। একটা overloaded class-কে দুটো focused class-এ ভাগ করো — প্রতিটার একটাই কাজ।

আরও পড়ুন