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

Pull Up Constructor Body: একটাই সকালের রুটিন, তারপর নিজের কাজ

Pull Up Constructor Body refactoring শেখো school-এর সকালের রুটিনের গল্প দিয়ে — সব subclass constructor-এর শুরুতে যে duplicate initialization আছে, সেটা superclass constructor-এ তুলে নাও আর super/base দিয়ে call করো।

22 মিনিট আপডেট: June 11, 2026beginner
refactoringpull up constructor bodyconstructorssuperbaseinheritancetypescriptcsharp

প্রতিটা section-এ একই সকালের প্রার্থনা

ধরো, তুমি একটা school-এর code লিখছো। সকাল ৮টায় ঘণ্টা বাজে, আর Class 7-এর তিনটা section শুরু হয়।

7A-তে: class teacher Mrs. Iyer school prayer করান, attendance নেন, board-এ date লেখেন। তারপর 7A-র নিজের first period শুরু — Mathematics

7B-তে: Mr. Bose prayer করান, attendance নেন, date লেখেন। তারপর 7B-র first period — English

7C-তে: Ms. Fernandes prayer, attendance, date। তারপর 7C যায় Swimming-এ (এটা sports section)।

প্রতিটা section-এর সকাল একইভাবে শুরু, কিন্তু আলাদাভাবে শেষ। Prayer, attendance, date — এই opening routine সব section-এরই একই। শুধু first period আলাদা।

এখন বছরের পর বছর ধরে প্রতিটা class teacher নিজের "morning routine card" রাখতেন। আর এই cards সময়ের সাথে আলাদা হয়ে যেতে থাকে। একবার 7B-তে একজন substitute teacher এমন একটা card পেলেন যেখানে attendance-এর step-ই নেই। পুরো এক সপ্তাহ 7B-র কোনো attendance record নেই — কেউ বুঝতেই পারেনি যতক্ষণ না এক অভিভাবক head clerk Mr. Sharma-কে phone করলেন।

Headmistress Dr. Meena KulkarniPull Up Field আর Pull Up Method গল্পে তুমি তাঁকে চেনো — সবসময়ের মতো fix করলেন। একটাই "School Morning Opening" procedure লিখলেন: prayer, attendance, date। প্রতিটা section-এর card মাত্র দুই লাইন হলো: "১. School Morning Opening করো। ২. নিজের section-এর first period শুরু করো।"

এই গল্পটার জন্যই আলাদা post দরকার। Code-এ একটা object-এর "সকালের রুটিন" হলো তার constructor — যে procedure object জন্মের সময় চলে। আর constructor special: তুমি সরাসরি Pull Up Method apply করতে পারবে না, কারণ constructor inherit হয় না — এটা chain হয়। এই special case-এর refactoring হলো Pull Up Constructor Body

চিত্র ১: morning routine cards maintain করা — আগে এবং পরে

Pull Up Constructor Body আসলে কী?

Pull Up Constructor Body হলো Fowler-এর একটা specific সমস্যার সমাধান। তুমি দেখলে sibling subclass-গুলোর constructor body প্রায় একই রকম — সবাই একই fields-এ একই parameters assign করছে, তারপর নিজের subclass-specific কাজ করছে। পরিষ্কার Duplicate Code — কিন্তু সাধারণ cure এখানে কাজ করে না।

কেন না? দুটো কারণ, দুটোই constructor নিয়ে:

  1. Constructor inherit হয় না। Section-এ constructor থাকলে SectionSevenA সেটা automatically পায় না — যেভাবে applyForLeave() পেত। নিজের constructor নিজেকে declare করতেই হয়। তাই "উপরে তুলে দাও, copies delete করো" — মানে plain Pull Up Method — এখানে impossible।
  2. Constructor chain হয়। new SectionSevenA(...) লিখলে language আগে parent-এর constructor চালায় (super(...)/base(...) দিয়ে), তারপর child-এর body। ঠিক school-এর মতো — সব-school opening আগে, তারপর section-এর নিজের first period।

তাই Fowler একটা constructor-specific variant define করেছেন:

যখন subclass constructor-গুলোতে common initialization code থাকে, তখন একটা superclass constructor বানাও যেখানে সেই shared code থাকবে, আর প্রতিটা subclass constructor TypeScript/Java-তে super(...) বা C#-তে : base(...) দিয়ে সেটা call করবে — নিজের কাজ করার আগে।

Shared opening উপরে চলে যাবে, একবার লেখা হবে। প্রতিটা subclass constructor শুধু বলবে: shared opening call করো, তারপর আমার নিজের কাজ। একদম Headmistress-এর দুই লাইনের card।

💡

এক লাইনে মনে রাখো: Pull Up Constructor Body মানে সব sibling constructor-এর শুরুর duplicate অংশ একটা superclass constructor-এ তুলে নেওয়া, আর প্রতিটা subclass সেটা super()/base() দিয়ে call করবে — ঠিক যেমন একই prayer + attendance + date-এর পরে প্রতিটা section নিজের first period শুরু করে।

এটা সাধারণ duplication-এর চেয়ে বেশি গুরুত্বপূর্ণ কারণ constructor হলো সেই জায়গা যেখানে invariant জন্ম নেয়। Invariant মানে object-এর সারাজীবনের promise — "প্রতিটা section-এর সবসময় class teacher থাকবে", "attendance register কখনো missing থাকবে না"। যখন প্রতিটা subclass constructor আলাদাভাবে এই promise দেওয়ার চেষ্টা করে, copies drift করতে পারে। তখন কিছু object সঠিকভাবে জন্মায়, কিছু broken হয়ে — ঠিক 7B-র attendance-বিহীন সপ্তাহের মতো। এই ধরনের bug খোঁজা সবচেয়ে কঠিন কারণ crash হয় জন্মস্থান থেকে অনেক দূরে।

একটু গভীরে যাই: এই refactoring-এর নিচে যে language feature আছে সেটা হলো constructor chaining। Java আর TypeScript-এ super(...) সবার আগে আসতে হয় (TypeScript তো this touch করতেই দেবে না আগে); C#-এ : base(...) signature-এর মধ্যেই থাকে, তাই child-এর কোনো statement আগে যাওয়ার সুযোগই নেই; Python-এ super().__init__(...) explicitly call করতে হয় আর ordering তোমার দায়িত্ব। মূল কথা: object base-first তৈরি হয়, তলা থেকে উপরে। Pull Up Constructor Body এই chaining-কে সঠিকভাবে ব্যবহার করে।

চিত্র ২: constructor body হলো pull-up family-র বিশেষ সদস্য

কখন দরকার?

এই signals দেখলে বুঝবে:

  • Sibling constructor-গুলো একই assignments দিয়ে শুরু হচ্ছে। প্রতিটাতে this.name = name; this.grade = grade; বারবার আছে। এটা Duplicate Code — শুধু সবাই method দেখে, constructor দেখে না।
  • Pull Up Field করা হয়ে গেছে। এটাই স্বাভাবিক সময়। Fields এখন parent-এ আছে (Pull Up Field করার পর), কিন্তু প্রতিটা subclass constructor এখনো নিজে নিজে সেগুলো initialize করছে। Data উপরে গেছে, তার জন্মের ritual যায়নি।
  • নতুন shared field যোগ করলে সব constructor বদলাতে হচ্ছে। admissionDate যোগ করতে গিয়ে তিনটা constructor-এ একই line লিখতে হলো — এই shared opening আগেই উপরে থাকা উচিত ছিল।
  • Common validation copy-paste হয়ে আছে। প্রতিটা constructor-এ if (!name) throw ... — একই invariant N জায়গায়, drift হওয়ার সুযোগ খোলা।

কিছু সাবধানতাও আছে:

  • শুধু genuinely shared অংশই উপরে যাবে। 7C-র swimming period 7C-তেই থাকবে। একটা subclass-এর setup সবার উপরে চাপালে Refused Bequest হয়।
  • Order block করতে পারে। TypeScript, Java, C#-এ parent constructor call সবার আগে আসতেই হবে। Shared logic যদি সত্যিই কিছু subclass setup-এর পরে চালাতে হয়, তাহলে protected init method ব্যবহার করো।
  • Shared body-তে overridable method call করা যাবে না। এটা classic constructor trap — নিচে warning আছে।
চিত্র ৩: shared constructor opening সব subclass ব্যবহার করে কিন্তু প্রত্যেকে নিজে লিখেছে — উপরে তোলার সময়

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

ধরো TypeScript-এ section-গুলো আছে, প্রতিটা constructor-এ একই opening repeat হচ্ছে। (Fields আগেই parent-এ আছে — Pull Up Field আগে করা হয়েছে।)

// BEFORE: every constructor repeats the same opening ritual
abstract class Section {
  protected classTeacher!: string;
  protected attendanceRegister!: string[];
  protected dateOnBoard!: string;
}
 
class SectionSevenA extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super();
    this.classTeacher = teacher;            // shared opening...
    this.attendanceRegister = [];           // ...copied here...
    this.dateOnBoard = today;               // ...line by line
    this.firstPeriod = "Mathematics";       // 7A's own part
  }
}
 
class SectionSevenB extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super();
    this.classTeacher = teacher;            // the same opening again
    this.attendanceRegister = [];
    this.dateOnBoard = today;
    this.firstPeriod = "English";           // 7B's own part
  }
}
 
class SectionSevenC extends Section {
  private firstPeriod: string;
  private poolLane: number;
  constructor(teacher: string, today: string, lane: number) {
    super();
    this.classTeacher = teacher;            // and again
    this.attendanceRegister = [];
    this.dateOnBoard = today;
    this.firstPeriod = "Swimming";          // 7C's own part
    this.poolLane = lane;                   // sports section extra
  }
}

তিনটা constructor, তিনটা জায়গায় তিন লাইনের একই opening — মোট নয়টা duplicate line। Duplication এতটাই সমানভাবে ছড়িয়ে যে কোনো একজন class teacher কখনো ভাবেননি এটা তাঁর সমস্যা:

চিত্র ৪: প্রতিটা section constructor-এ duplicate opening lines-এর একটা করে copy আছে

Pull Up Constructor Body করার পরে:

// AFTER: shared opening defined once, called via super()
abstract class Section {
  protected classTeacher: string;
  protected attendanceRegister: string[];
  protected dateOnBoard: string;
 
  protected constructor(teacher: string, today: string) {
    this.classTeacher = teacher;            // the School Morning Opening,
    this.attendanceRegister = [];           // written exactly once
    this.dateOnBoard = today;
  }
}
 
class SectionSevenA extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super(teacher, today);                  // 1. do the shared opening
    this.firstPeriod = "Mathematics";       // 2. start my own first period
  }
}
 
class SectionSevenB extends Section {
  private firstPeriod: string;
  constructor(teacher: string, today: string) {
    super(teacher, today);
    this.firstPeriod = "English";
  }
}
 
class SectionSevenC extends Section {
  private firstPeriod: string;
  private poolLane: number;
  constructor(teacher: string, today: string, lane: number) {
    super(teacher, today);
    this.firstPeriod = "Swimming";
    this.poolLane = lane;                   // only the sports extras remain
  }
}

প্রতিটা subclass constructor এখন Headmistress-এর দুই লাইনের card: shared opening, তারপর নিজের part। পরের বছর নতুন shared field admissionYear যোগ করতে হলে? শুধু একটা constructor-এ এক লাইন।

চিত্র ৫: refactoring-এর পরে shared opening base constructor-এ আছে, subclass-গুলো সেটাকে delegate করছে

Runtime-এ order কেমন হয় সেটা দেখা দরকার, কারণ chaining-ই constructor-কে special করে। new SectionSevenA(...) চললে messages এভাবে flow করে:

চিত্র ৬: construction parent-first — shared opening আগে শেষ হয়, তারপর section নিজের part যোগ করে

এই sequence-টাই school-এর সকাল: সব-school opening আগে শেষ হয়, তারপর প্রতিটা section নিজের first period শুরু করে। Language নিজেই এই assembly order enforce করে।

কীভাবে করবে? এই ধাপগুলো follow করো

চিত্র ৭: তিনটা constructor-এর shared opening lines একটা base constructor-এ মিলিত হচ্ছে
  1. আগে নিশ্চিত করো shared fields superclass-এ আছে কিনা। classTeacher যদি এখনো প্রতিটা subclass-এ declare থাকে, থামো আর আগে Pull Up Field করো। Field নিচে থাকলে assignment উপরে নিয়ে যাওয়া যাবে না। hierarchy cleanup-এর সঠিক order: fields উপরে, তারপর constructor body উপরে, তারপর methods উপরে।

  2. Shared statements সব constructor-এর উপরে এনে রাখো। যে statements move করতে চাও সেগুলো অবশ্যই একটা prefix হতে হবে — কারণ super() call যেটা সেগুলোকে replace করবে, সেটাই আগে আসতে হবে। কোনো shared line যদি subclass-specific line-এর পরে থাকে, দেখো reorder করা যায় কিনা (সাধারণত যায় যদি আলাদা fields touch করে)। এই intermediate state-এ পৌঁছাও:

    // INTERMEDIATE: shared lines gathered at the top, nothing moved yet
    constructor(teacher: string, today: string, lane: number) {
      super();
      this.classTeacher = teacher;        // shared block,
      this.attendanceRegister = [];       // now contiguous,
      this.dateOnBoard = today;           // at the very top
      this.firstPeriod = "Swimming";      // own part below
      this.poolLane = lane;
    }
  3. Superclass constructor বানাও। Shared block-এর জন্য যা লাগে সেই parameters দাও — এখানে teacher আর today। Parent যদি abstract হয় তাহলে protected করো; কেউ bare Section বানাবে না।

  4. একটা subclass-এ shared block-টা super(...) call দিয়ে replace করো। তিন লাইন delete করো, parameters pass করো: super(teacher, today);। Compile করো, tests চালাও।

  5. বাকি প্রতিটা subclass-এ একে একে একই কাজ করো, প্রতিবার test করো। আগের posts-এর মতো: এক ছোট step, একবার green test, তারপর পরের step।

  6. Order কাজ না করলে fallback ব্যবহার করো। কখনো কখনো shared logic subclass setup-এর পরে চালাতে হয় — ধরো subclass fields থেকে computed registration code। Constructor chaining-এ "child আগে, parent পরে" express করার উপায় নেই। তখন parent-এ একটা protected ordinary method:

    // FALLBACK: when super-first order doesn't fit
    abstract class Section {
      protected completeMorningOpening(teacher: string, today: string): void {
        this.classTeacher = teacher;
        this.attendanceRegister = [];
        this.dateOnBoard = today;
      }
    }
    // subclass constructor: own pre-setup first, then
    // this.completeMorningOpening(teacher, today);

    এতে একটু safety কমে (কোনো কিছু force করে না যে সব subclass এটা call করবে), তাই order allow করলে real constructor chaining prefer করো।

চিত্র ৮: constructor opening-এর যাত্রা — আগে prefix হিসেবে align, তারপর hoist, তারপর verify
⚠️

দুটো trap-এ বিশেষ সাবধান। প্রথমত, প্রতিটা constructor convert করার পরেই tests চালাও — constructor bug জন্মের সময় দেখা দেয়, আর একটা super() call-এ missed parameter সেই subclass-এর প্রতিটা instance-এ একটা field silently wrong করে দিতে পারে। দ্বিতীয়ত, pulled-up constructor-এ কখনো overridable (virtual) method call করো না। Parent constructor চলে subclass-এর fields initialize হওয়ার আগে; সেখানে কোনো method call করলে সেটা অর্ধেক-তৈরি object-এর উপর চলে আর undefined value পড়ে। Constructor body plain রাখো: fields assign করো, parameters validate করো, polymorphic কিছু না।

বাস্তব জীবনের বড় example

পুরো school scenario দেখি validation সহ — কারণ validation-এই duplicate constructor সবচেয়ে বেশি ক্ষতি করে। আগে:

// BEFORE: shared validation + assignment, copied and already drifting
abstract class Section {
  protected classTeacher!: string;
  protected roomNumber!: number;
  protected attendanceRegister!: string[];
}
 
class SectionSevenA extends Section {
  constructor(teacher: string, room: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenB extends Section {
  constructor(teacher: string, room: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    // room validation forgotten here — drift!
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenC extends Section {
  private poolLane: number;
  constructor(teacher: string, room: number, lane: number) {
    super();
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
    if (lane < 1 || lane > 8) throw new Error("Pool lane must be 1-8");
    this.poolLane = lane;
  }
}

দেখো drift কোথায়: 7B room check ভুলে গেছে, তাই new SectionSevenB("Mr. Bose", -3) হাসিমুখে room minus-three-তে একটা section তৈরি করে দেয়। "প্রতিটা section-এ valid room থাকবে" — এই invariant কিছু object রাখছে, কিছু রাখছে না। জন্ম থেকেই broken। Refactoring-এর পরে:

// AFTER: invariants established once, for every object in the hierarchy
abstract class Section {
  protected classTeacher: string;
  protected roomNumber: number;
  protected attendanceRegister: string[];
 
  protected constructor(teacher: string, room: number) {
    if (teacher.trim() === "") throw new Error("Teacher name required");
    if (room < 1) throw new Error("Invalid room");
    this.classTeacher = teacher;
    this.roomNumber = room;
    this.attendanceRegister = [];
  }
}
 
class SectionSevenA extends Section {
  constructor(teacher: string, room: number) {
    super(teacher, room);                   // opening + validation, guaranteed
  }
}
 
class SectionSevenB extends Section {
  constructor(teacher: string, room: number) {
    super(teacher, room);                   // 7B can no longer forget the check
  }
}
 
class SectionSevenC extends Section {
  private poolLane: number;
  constructor(teacher: string, room: number, lane: number) {
    super(teacher, room);
    if (lane < 1 || lane > 8) throw new Error("Pool lane must be 1-8");
    this.poolLane = lane;                   // only sports-section concerns remain
  }
}

এখন কোনো subclass room check ভুলতে পারবে না — language নিজেই সব construction-কে একটাই shared opening-এর মধ্য দিয়ে নিয়ে যায়। এটাই আসল benefit: invariant "প্রতিটা constructor-কে মনে রাখতে হবে" — এই convention থেকে "compiler enforce করবে" — এই rule-এ পরিণত হলো। আর 7C-র constructor দেখো: এখন শুধু sports-section-এর বিষয়গুলো আছে। যা ordinary, সব সরে গেছে।

চিত্র ৯: shared opening-এর copies আগে এবং পরে

একটু গভীরে: আমরা যা বানালাম সেটা hierarchy-তে class invariants guarantee করার textbook পদ্ধতি। Formally, constructor-এর postcondition invariant establish করে; প্রতিটা public method entry-তে সেটা ধরে নেয় আর exit-এ preserve করে। Initialization duplicate থাকলে তোমার আসলে N টা independent postcondition আছে যেগুলো claim করছে একই invariant establish করবে — আর 7B example দেখাল একটা quietly না করলে কী হয়। Base constructor-এ centralize করা invariant-কে single proof সহ একটা theorem-এ পরিণত করে। এটাও মাথায় রাখো যে dependency-injection framework বা ORM যেগুলো constructor bypass করে (deserializer, proxy) — এগুলো code review-এ সন্দেহের চোখে দেখো; এরা এমন object বানাতে পারে যেটা morning assembly-ই skip করেছে।

Python-এ একই কাজ

Python-এ super().__init__(...) explicitly call করতে হয় — language force করে না যে এটা আগে আসবে, তাই এটা তোমার দায়িত্ব:

# AFTER, in Python: shared opening once, chained explicitly
class Section:
    def __init__(self, teacher: str, room: int) -> None:
        if not teacher.strip():
            raise ValueError("Teacher name required")
        if room < 1:
            raise ValueError("Invalid room")
        self.class_teacher = teacher
        self.room_number = room
        self.attendance_register: list[str] = []
 
 
class SectionSevenC(Section):
    def __init__(self, teacher: str, room: int, lane: int) -> None:
        super().__init__(teacher, room)   # shared opening first — by convention
        if not 1 <= lane <= 8:
            raise ValueError("Pool lane must be 1-8")
        self.pool_lane = lane             # sports-section extras only

দুটো Python-specific note। প্রথমত, language তোমাকে super().__init__() এর আগে statements লিখতে stop করবে না, তাই teams সাধারণত Java-style convention follow করে: সবসময় chain আগে। দ্বিতীয়ত, super().__init__(...) call-ই ভুলে গেলে Python কোনো error দেয় না construction-এ; object জন্মায়, শুধু parent-এর fields ছাড়া, আর crash আসে অনেক পরে — একদম 7B-র blank-register week-এর মতো। Linter rule অথবা attrs/dataclasses (যেগুলো সঠিক chaining auto-generate করে) হলো Python codebase-এর Mr. Sharma।

C#-এ একই refactoring

C#-এ chaining একটু আলাদাভাবে লেখা হয় — আর বেশ elegant। Parent call body-র ভেতরে statement না হয়ে constructor signature-এ : base(...) clause হিসেবে থাকে, যা দেখলেই বোঝা যায় "shared opening আগে"। আগে:

// BEFORE: duplicated initialization in every subclass constructor
abstract class Section
{
    protected string _classTeacher;
    protected int _roomNumber;
    protected List<string> _attendanceRegister;
}
 
class SectionSevenA : Section
{
    public SectionSevenA(string teacher, int room)
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        _classTeacher = teacher;           // shared opening, copy 1
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}
 
class SectionSevenB : Section
{
    public SectionSevenB(string teacher, int room)
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        _classTeacher = teacher;           // shared opening, copy 2
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}

পরে — protected base constructor আর : base(...) clause দেখো:

// AFTER: protected base constructor + base(...) chaining
abstract class Section
{
    protected string _classTeacher;
    protected int _roomNumber;
    protected List<string> _attendanceRegister;
 
    protected Section(string teacher, int room)   // shared opening, once
    {
        if (string.IsNullOrWhiteSpace(teacher))
            throw new ArgumentException("Teacher name required");
        if (room < 1) throw new ArgumentException("Invalid room");
        _classTeacher = teacher;
        _roomNumber = room;
        _attendanceRegister = new List<string>();
    }
}
 
class SectionSevenA : Section
{
    public SectionSevenA(string teacher, int room)
        : base(teacher, room) { }                 // nothing extra to do
}
 
class SectionSevenC : Section
{
    private readonly int _poolLane;
 
    public SectionSevenC(string teacher, int room, int lane)
        : base(teacher, room)                     // shared opening first, by syntax
    {
        if (lane is < 1 or > 8)
            throw new ArgumentException("Pool lane must be 1-8");
        _poolLane = lane;                         // sports section's own part
    }
}

C#-এর কিছু বিশেষ বিষয়:

  • protected Section(...) হলো abstract base-এর constructor-এর সঠিক visibility: subclass chain করতে পারবে, বাইরের কেউ পারবে না। public করলে misleading হয় — abstract class-এ directly call করা যায় না এমনিতেও।
  • : base(teacher, room) signature-এ আছে, body-র opening brace-এর আগে। Language নিশ্চিত করে এটা আগে চলবে; তুমি কোনো subclass statement আগে নিতে পারবে না।
  • : base(...) না লিখলে C# চুপচাপ parent-এর parameterless constructor call করে — আর parent-এ যদি সেটা না থাকে (আমাদেরটায় parameters লাগে), তাহলে compile error। সেই error তোমার বন্ধু: shared opening ভুলে যাওয়া impossible করে দেয়।
  • Virtual call warning C#-এ পূর্ণ শক্তিতে প্রযোজ্য: Section-এর constructor থেকে virtual method call করলে subclass-এর constructor body চালু হওয়ার আগেই সেই override dispatch হবে। Analyzer rule CA2214 ("Do not call overridable methods in constructors") ঠিক এই trap-এর জন্যই আছে।

IDE support কেমন?

Tools এখানে ভালো কিন্তু plain members-এর মতো push-button না, কারণ constructor সব language-এই special:

  • IntelliJ IDEA / Rider (JetBrains): Refactor > Pull Members Up dialog fields আর methods সরাসরি handle করে। Constructor body-র জন্য সাধারণ workflow দুই step — shared opening lines-এ Extract Method করো, তারপর সেই method pull up করো, অথবা superclass constructor বানিয়ে IDE-র super() generation আর parameter hints দিয়ে wiring করো। IntelliJ নিজেও suggest করে super(...) call যোগ করতে যখন তুমি এমন parent-এর subclass লিখছো যার constructor-এ parameters লাগে।
  • Visual Studio (C#): Ctrl+. Quick Actions দিয়ে আশপাশের moves করা যায় — fields-এর জন্য "Pull members up to base type", base class-এ "Generate constructor", আর নতুন parameters thread করতে "Add parameter to constructor"। Missing : base(...) call-এর compile error তোমাকে প্রতিটা subclass-এ guide করবে।
  • ReSharper: এখানে ভালো কাজ করে — shared block-এ Extract Method, result-এ Pull Members Up, তারপর call sites inline করো; plus "virtual member call in constructor" inspection warnings।
  • Eclipse (Java): Refactor > Pull Up methods move করতে পারে; constructor body move সাধারণত manual, compiler-এর "must call super first" rules দিয়ে guided।

সব IDE-তে safety net একই: compiler সঠিক chaining force করে, আর tests behaviour confirm করে। দুটোই use করো।

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

সুবিধাঝুঁকি / cost
Shared initialization একবার লেখা — N copies একটাতে নামেsuper()/base() আগে চালাতে হবে; child-first order লাগলে plain chaining হবে না (protected-init fallback ব্যবহার করো)
Language invariant enforce করে, convention না — কোনো subclass validation skip করতে পারবে নাPulled-up constructor-এ virtual call করলে অর্ধেক-তৈরি object-এ subclass code চলতে পারে
Hierarchy-র প্রতিটা object একই সঠিক baseline থেকে জন্মায়Base constructor-এর parameter list বাড়ে; নতুন shared parameter প্রতিটা subclass signature-এ একবার করে touch করতে হয়
Subclass constructor শুধু নিজের honest, subclass-only কাজ রাখেএকটা subclass-এর setup সবার constructor-এ তুললে বাকিদের burden হয় — Refused Bequest, constructor edition
Pull Up Field গল্প complete হয় — data আর তার জন্মের ritual একসাথে থাকেChaining না জানা beginners-এর জন্য একটু বেশি ceremony

তিনটা pull-up refactoring-এর তুলনা, কারণ এই post-এই upward set complete হচ্ছে:

Pull Up FieldPull Up Constructor BodyPull Up Method
কী move হয়Data declarationConstructor-এর shared openingএকটা পুরো behaviour
কেন specialVisibility সাধারণত protected-এ wide হয়Constructor chain হয়, inherit না; super()/base() আগে আসতে হবেMove করার আগে bodies unify করতে হয়
সাধারণ orderপ্রথমদ্বিতীয়তৃতীয়
School pictureOffice board-এর addressFirst period-এর আগে morning openingLeave application sheet

দিকনির্দেশনা নিয়ে বলি: Pull Up Constructor Body Pull Up Field আর Pull Up Method-এর মতো একই upward family-র। আর এর mirror situation handle করে push-down family — parent constructor যদি এমন setup করছে যা শুধু একটা child-এর দরকার (সবার জন্য poolLane fill করছে, ধরো), সেটা নিচে নামা উচিত, ঠিক যেমন Push Down Field misplaced data নামায়। Compass সবসময় একই: সবার জন্য shared উপরে, একবার; কারো কারো জন্য নিচে, যেখানে ব্যবহার। Constructor শুধু একটা extra rule যোগ করে: উপরে যা যাবে তা আগে চালাতে পারতে হবে।

কোন smell ঠিক করে?

SmellPull Up Constructor Body কীভাবে সাহায্য করে
Duplicate Codeসবচেয়ে লুকানো জায়গার cure — sibling constructor-জুড়ে copy হওয়া initialization logic
Shotgun SurgeryShared field-এর setup বদলাতে এখন একটা constructor, প্রতিটা subclass না
Inconsistent state bugsCatalog smell না কিন্তু আসল রোগ: drifted invariant সহ জন্মানো objects (7B-র unvalidated room) impossible হয়
Refused BequestIndirect: শুধু একটা subclass-এর setup shared constructor-এ না রাখলে siblings-এ burden চাপানো হয় না

Quick revision

+--------------------------------------------------------------+
|             PULL UP CONSTRUCTOR BODY — REVISION              |
+--------------------------------------------------------------+
| Story    : Every section opens with prayer + attendance +    |
|            date, then its OWN first period. Drifted cards    |
|            -> 7B lost a week of attendance.                  |
|            Fix: ONE shared "Morning Opening", sections add   |
|            only their own part after it.                     |
|                                                              |
| Move     : Shared opening of sibling constructors            |
|            ---> superclass constructor, called by            |
|            super(...) / base(...) as the FIRST step          |
|                                                              |
| Why special: constructors are NOT inherited, they are        |
|              CHAINED -> plain Pull Up Method cannot work     |
|                                                              |
| Safe steps : Pull Up Field first -> gather shared lines at   |
|              the TOP -> create protected parent ctor ->      |
|              replace block with super(...) one subclass at   |
|              a time, testing each                            |
|                                                              |
| Traps      : super/base must be first; NEVER call virtual    |
|              methods from a constructor (half-built object)  |
| Fallback   : protected init() method when order won't fit    |
| Cures      : Duplicate Code in initialization; drift-born    |
|              invariant bugs                                  |
+--------------------------------------------------------------+

Practice করো

ধরো school-এর transport software-এ তোমার সাহায্য দরকার। তিনটা bus-route class তিনজন আলাদা মানুষ লিখেছে:

abstract class BusRoute {
  protected driverName!: string;
  protected busNumber!: string;
  protected stops!: string[];
}
 
class MorningRoute extends BusRoute {
  private departureTime: string;
  constructor(driver: string, bus: string) {
    super();
    if (driver === "") throw new Error("Driver required");
    this.driverName = driver;
    this.busNumber = bus;
    this.stops = [];
    this.departureTime = "07:00";
  }
}
 
class EveningRoute extends BusRoute {
  private departureTime: string;
  constructor(driver: string, bus: string) {
    super();
    this.driverName = driver;      // validation missing — drift!
    this.busNumber = bus;
    this.stops = [];
    this.departureTime = "15:30";
  }
}
 
class ExcursionRoute extends BusRoute {
  private destination: string;
  constructor(driver: string, bus: string, destination: string) {
    super();
    if (driver === "") throw new Error("Driver required");
    this.driverName = driver;
    this.busNumber = bus;
    this.stops = [];
    this.destination = destination;
  }
}

তোমার কাজ:

  1. প্রতিটা constructor-এ shared opening খুঁজে বের করো। কোন lines তিনটাতেই আছে? কোন line একটা copy থেকে drift করে গেছে, আর সেটা এখন কী bug allow করছে?
  2. Superclass constructor design করো: কী parameters লাগবে, আর visibility কী হবে?
  3. Refactoring করো safe order-এ: parent constructor বানাও, তারপর একটা route class super(driver, bus) দিয়ে convert করো, "tests চালাও", তারপর পরেরটা।
  4. departureTime MorningRoute আর EveningRoute-এ আছে কিন্তু ExcursionRoute-এ নেই। এটা কি shared constructor-এ যাওয়া উচিত? এই post-এর compass দিয়ে উত্তর দাও। (Hint: সব subclass use করে, নাকি কিছু?)
  5. Bonus trap-check: একজন teammate suggest করল base constructor-এ this.announceRoute() call করা হোক — প্রতিটা subclass সেটা override করে নিজের details print করে। Half-built-object trap দিয়ে explain করো কেন প্রতিটা ExcursionRoute announcement-এ destination undefined print হবে।

যখন প্রতিটা bus route একটাই validated opening দিয়ে জন্মাবে, আর প্রতিটা subclass constructor শুধু নিজের personality রাখবে — তখন তুমি upward journey complete করলে: fields, তারপর constructor bodies, তারপর methods। পরের post-এ compass উল্টো দিকে ঘোরাবো আর দেখব কখন members নিচে নামে — শুরু হবে Push Down Field দিয়ে।

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

Constructor-এ কি সরাসরি Pull Up Method apply করা যায় না?
না, কারণ constructor সাধারণ method-এর মতো inherit হয় না — এটা chain হয়। একটা subclass তার parent-এর constructor সরাসরি পেয়ে যায় না, নিজে declare করতেই হয়। তাই পুরো constructor উপরে তুলে দেওয়া সম্ভব না। আমরা শুধু shared অংশটা superclass constructor-এ তুলি, আর super() বা base() দিয়ে call করি।
কেন super() বা base() সবসময় constructor-এর প্রথমেই থাকতে হয়?
Java, C#, TypeScript-এর নিয়ম হলো — child-এর নিজের কাজ শুরুর আগে parent-কে পুরোপুরি তৈরি হতে হবে। TypeScript-এ super() শেষ হওয়ার আগে this touch করলেই error আসে। এই নিয়মটা নিশ্চিত করে যে কোনো object কখনো অর্ধেক-তৈরি parent-এর উপরে দাঁড়াচ্ছে না।
যদি shared code-টা কিছু subclass-specific setup-এর পরে চালাতে হয়?
তাহলে সরাসরি constructor chaining কাজ করবে না, কারণ super()/base() সবসময় আগে আসতে হয়। সেক্ষেত্রে standard সমাধান হলো shared logic-টাকে parent-এ একটা protected method-এ রাখো — যেমন protected init() বা Initialize() — আর প্রতিটা subclass constructor তার নিজের pre-setup-এর পরে ওটা call করুক।
Constructor-এর ভেতরে virtual বা overridable method call করা কেন বিপজ্জনক?
কারণ subclass-এর override টা চালু হতে পারে subclass-এর fields initialize হওয়ার আগেই। Parent constructor আগে চলে, virtual method call করে, আর subclass-এর version চলে অর্ধেক-তৈরি object-এর উপর — undefined বা default value পড়ে। Constructor-এ কখনো overridable method call করো না।
Pull Up Constructor Body করার আগে কোন refactoring করা উচিত?
Pull Up Field। shared initialization-এ যে fields assign হচ্ছে সেগুলো আগে superclass-এ উঠতে হবে। hierarchy cleanup-এর সঠিক order হলো: Pull Up Field, তারপর Pull Up Constructor Body, তারপর Pull Up Method।

আরো দেখো

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

Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা

বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।

আরও পড়ুন

Refused Bequest: যে ছেলে মিষ্টির দোকানের রেসিপি নিতে চায়নি

Refused Bequest কোড স্মেল শেখো একটা পারিবারিক মিষ্টির দোকানের গল্পের মাধ্যমে — TypeScript ও C#-এ Liskov লঙ্ঘন আর delegation দিয়ে সমাধান ধাপে ধাপে।

আরও পড়ুন

Pull Up Field: সবার জন্য একটাই নোটিশ বোর্ড

Pull Up Field refactoring শিখো একটা স্কুলের নোটিশ বোর্ডের গল্প দিয়ে — যে field প্রতিটা subclass-এ বারবার কপি হয়েছে সেটাকে superclass-এ তুলে দাও, TypeScript আর C#-এ নিরাপদ ধাপ, IDE সাপোর্ট, আর pull-up বনাম push-down কোনটা কখন করবে।

আরও পড়ুন

Pull Up Method: পুরো স্কুলের জন্য একটাই নির্দেশিকা

Pull Up Method refactoring শেখো স্কুলের ছুটির আবেদনের গল্পের মাধ্যমে — subclass-এ duplicate হয়ে যাওয়া method-গুলো superclass-এ তুলে আনো, TypeScript আর C#-এ safe steps সহ, IDE dialog আর কখন Form Template Method বেছে নেবে সেটাসহ।

আরও পড়ুন