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

Push Down Method: যে Method শুধু একটা Subclass ব্যবহার করে, সেটা সেখানে নামিয়ে দাও

Push Down Method শিখো একটা স্কুলের office গল্পের মাধ্যমে — superclass-এর সৎ contract, TypeScript আর C#-এ নিরাপদ ধাপে ধাপে move, আর কীভাবে এটা Refused Bequest smell ঠিক করে।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringpush down methodinheritancerefused bequestpolymorphismtypescriptcsharp

🏫 স্কুলের সেই board যেটা সবাইকে বিভ্রান্ত করত

ধরো তুমি একটা ব্যস্ত স্কুলে গেছো। সামনের counter-এ বসে আছেন সালাম কাকা — বিশ বছর ধরে certificate-এ stamp মারছেন, প্রতিটা ছাত্রের মুখ চেনেন। তাঁর পেছনের দেয়ালে ঝুলছে একটা বড় সাদা board — অফিসের সব সেবার তালিকা: বোনাফাইড সার্টিফিকেট, ফি সংগ্রহ, ঠিকানা update, স্পোর্টস সার্টিফিকেট।

শেষেরটা আবার পড়ো। স্পোর্টস সার্টিফিকেট। Board বলছে অফিস এই সেবা সব ছাত্রকে দেয়।

তাহলে কী হয়? রাহেল, ক্লাস সিক্সের একটা ছেলে যে সারাজীবন ফুটবল ছুঁয়ে দেখেনি, বিশ মিনিট লাইনে দাঁড়িয়ে স্পোর্টস সার্টিফিকেট চায়। কারণ তার কাজিন বলেছে "সব certificate অফিস থেকে আসে।" সালাম কাকা দীর্ঘশ্বাস ফেলে বলেন, "বেটা, এটা শুধু sports section-এর ছাত্রদের জন্য। খান স্যারের কাছে যাও।" রাহেল বিভ্রান্ত হয়ে চলে যায়।

পরের দিন আরেকজন। তার পরের দিন আরো দুজন। সালাম কাকা এখন একটা কাগজের টুকরোয় হিসাব রাখছেন — এই মাসে একাই এগারোটা ভুল-counter ভিজিট।

কেন এটা বারবার হচ্ছে? ছাত্রছাত্রীরা বোকা বলে না। board এমন একটা প্রতিশ্রুতি দিচ্ছে যা সে রাখতে পারে না। "পুরো অফিস" স্তরে একটা সেবা list করা হয়েছে, অথচ বাস্তবে শুধু একটা section — sports section, যেটা চালান খান স্যার — সেটা দেয়।

প্রধান শিক্ষিকা মিসেস রহিমা সালাম কাকার হিসাবের কথা শুনে পাঁচ মিনিটে পুরো ব্যাপারটা ঠিক করেন। তিনি মেইন board থেকে "স্পোর্টস সার্টিফিকেট" সরিয়ে দেন আর sports section desk-এর একটা ছোট board-এ লাগান। মেইন board-এ এখন শুধু সেই সেবাগুলো যেগুলো সত্যিই সবার জন্য। রাহেল আর কখনো ভুল counter-এ লাইন দেয় না।

রাহেলের অভিজ্ঞতা, board ঠিক হওয়ার আগে ও পরে:

Figure 1: মিথ্যা প্রতিশ্রুতি সরানোর আগে ও পরে রাহেলের যাত্রা

এই একই সমাধান code-এ আছে, এর নাম Push Down Method। যখন একটা method superclass-এ বসে থাকে কিন্তু শুধু একটা (বা খুব কম) subclass সেটা আসলে ব্যবহার করে, আমরা সেটা সেই subclass-এ নামিয়ে দিই। Parent-এর "board" — তার public contract — তখন শুধু সেই ক্ষমতাগুলোই দেখায় যেগুলো সত্যিই প্রতিটা child-এর আছে।

🎯 Push Down Method আসলে কী?

Object-oriented code-এ একটা superclass একটা প্রতিশ্রুতি। যখন SchoolSection-এ issueSportsCertificate() method আছে, ভাষাটা প্রতিটা পাঠককে বলছে: প্রতিটা ধরনের section এটা করতে পারে। Science section, arts section, sports section — সবগুলো।

কিন্তু কখনো কখনো সেই প্রতিশ্রুতি মিথ্যা। Method টা অভ্যাসবশত parent-এ রাখা হয়েছিল, বা আগে সত্যিই সবার কাজে লাগত, বা কেউ এমন একটা ভবিষ্যতের পরিকল্পনা করেছিল যেটা আর আসেনি। আজ শুধু SportsSection-এর কাছে এর জন্য বাস্তব কোনো body আছে। বাকি subclass-গুলো হয় এটাকে ignore করে, নয়তো — আরো খারাপ — শুধু "not supported" error throw করতে override করে।

Push Down Method হলো সেই refactoring যেটা এটা ঠিক করে:

  1. যে subclass (বা subclass-গুলো) আসলে method টা ব্যবহার করে সেগুলো খোঁজো।
  2. ঠিক সেই subclass-গুলোতে method টা copy করো।
  3. Superclass থেকে method টা মুছে দাও, সাথে যেকোনো "not supported" override-ও।
  4. যে caller parent type দিয়ে method টা call করছিল সেগুলো ঠিক করো।

Move-এর পরে, parent class আবার সৎ হয়। SchoolSection reference দিয়ে যা কিছু call করা যায় সেটা প্রতিটি section-এর জন্য কাজ করবে বলে গ্যারান্টি থাকে। এটাই polymorphism-কে বিশ্বাসযোগ্য করে — তুমি একটা parent-typed variable ধরে রেখে ভয় ছাড়াই তার method call করতে পারো।

Martin Fowler তাঁর classic catalog-এ এই refactoring-এর কথা লিখেছেন, আর বলেছেন এটা Pull Up Method-এর ঠিক উল্টো। দুটো class hierarchy-তে একজোড়া elevator — শেয়ার করা জিনিস উপরে তোলো, বিশেষ জিনিস নিচে নামাও।

পুরো সিদ্ধান্তটা একটা diagram-এ এঁটে যায়:

Figure 2: push-down সিদ্ধান্ত — এই method টা সৎভাবে কোথায় থাকা উচিত?
💡

এক লাইনে সারকথা: Push Down Method superclass থেকে একটা method শুধু সেই subclass-এ নামিয়ে দেয় যেটা সত্যিই সেটা ব্যবহার করে, যাতে parent-এর contract শুধু সেটুকুই প্রতিশ্রুতি দেয় যা সে রাখতে পারে।

কলেজ কর্নার: মিথ্যা board আনুষ্ঠানিকভাবে যা লঙ্ঘন করে তার নাম Liskov Substitution Principle (LSP) — SOLID-এর "L"। LSP বলে একটা subclass-এর যেকোনো object superclass যেখানে expect করা হয় সেখানে caller-কে চমকে না দিয়েই ব্যবহারযোগ্য হতে হবে। একটা ScienceSection যেটা inherited method থেকে NotSupportedException throw করে — সেটা LSP লঙ্ঘনের পাঠ্যবইয়ের উদাহরণ। Compile হয় substitute হিসেবে, কিন্তু বিস্ফোরিত হয় সেভাবে। Push Down Method child-কে ভালো আচরণ করিয়ে LSP পুনরুদ্ধার করে না — parent-এর প্রতিশ্রুতিকে ছোট করে যতক্ষণ প্রতিটা child সেটা রাখতে পারে। পরীক্ষার ভাষায়: parent যা require করে তা কঠোর করবে না, parent যা প্রতিশ্রুতি দেয় তা দুর্বল করবে না।

📞 ভুল call, একটা কথোপকথন হিসেবে

দেখো fix-এর আগে কী হয়। Caller parent-এর board-কে বিশ্বাস করে, আর সেই বিশ্বাসের পুরস্কার পায় একটা exception দিয়ে:

Figure 3: fix-এর আগে parent type call নেয় আর child বিস্ফোরিত হয়; পরে call সরাসরি একমাত্র বাস্তব provider-এর কাছে যায়

মাঝের note-টা পুরো রোগটাকে এক লাইনে বলে দেয়: একটা compiled প্রতিশ্রুতি থেকে runtime চমক। Compiler call approve করেছিল কারণ parent method declare করেছিল; program crash করল কারণ child সেটা অস্বীকার করল। Push Down Method-এর পরে, ভুল call crash করে না — এটা compile হয় না, যেটা অনেক বেশি ভালো।

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

তোমার codebase-এ এই সংকেতগুলো খোঁজো:

  • একটা "not supported" override। একটা subclass শুধু error throw করতে বা dummy value return করতে parent method override করে। এটা Refused Bequest smell-এর সবচেয়ে জোরে বাজা alarm — child parent-এর জোর করে দেওয়া inheritance অস্বীকার করছে।
  • একটা method যেটা শুধু একটা child call করে। Usages খোঁজো। যদি issueSportsCertificate() শুধুমাত্র SportsSection object-এ call হয়, তাহলে এর বাড়ি ভুল।
  • একটা স্ফীত parent। যখন superclass "হয়তো কেউ একদিন এটার দরকার হবে" method সংগ্রহ করতে থাকে, এটা ধীরে ধীরে Large Class-এ পরিণত হয়। বিশেষ method নিচে নামানো এটা slim করার একটা উপায়।
  • পরিকল্পিত generality যা কখনো আসেনি। কেউ method টা parent-এ রেখেছিল ভেবে "সব section-এর একদিন certificate দরকার হবে।" বছর পেরিয়ে গেছে। শুধু sports-ই করেছে। সেই speculative design এখন undo করো।

সালাম কাকার কাগজের হিসাবের software সমতুল্য আছে — মাপো parent-এর সেবাগুলো আসলে কীভাবে ব্যবহার হচ্ছে। আমাদের স্কুল অফিসে ছবিটা এরকম ছিল:

Figure 4: ছাত্রছাত্রীরা মেইন counter-এ আসলে কী চেয়েছিল — sports certificate-এর অংশটা শুধু একটা desk-এর জন্য

সেই ১০% অংশটা ছোট কারণ সেবাটা গুরুত্বহীন নয় — ছোট কারণ শুধু একটা section-এর ছাত্রদেরই এটা দরকার। একটা গ্রুপের সেবা সবার-board-এ থাকা উচিত না।

একটু ভাবো এই tool নেওয়ার আগে। যদি অনেক subclass method ব্যবহার করে, নিচে নামানো মানে প্রত্যেকটায় copy-paste করা, আর এটা Duplicate Code smell তৈরি করে — একটা সমস্যার বদলে আরো খারাপ একটা নিচ্ছো। সেক্ষেত্রে, হয় method টা parent-এই রাখো, নয়তো child-দের একটা নির্দিষ্ট দলের জন্য Extract Superclass ব্যবহার করে মধ্যবর্তী parent বানাও।

🔍 একনজরে আগে আর পরে

TypeScript-এ স্কুল অফিস, মিথ্যা প্রতিশ্রুতি parent-এ বসে আছে:

// BEFORE: the parent promises a service only one child gives
abstract class SchoolSection {
  constructor(protected sectionName: string) {}
 
  issueBonafide(studentId: string): string {
    return `Bonafide certificate for ${studentId} (${this.sectionName})`;
  }
 
  issueSportsCertificate(studentId: string): string {
    // only meaningful for the sports section!
    return `Sports certificate for ${studentId}`;
  }
}
 
class ScienceSection extends SchoolSection {
  issueSportsCertificate(studentId: string): string {
    throw new Error("Science section does not issue sports certificates");
  }
}
 
class SportsSection extends SchoolSection {
  // uses the inherited issueSportsCertificate()
}

আর এই হলো মিসেস রহিমার fix-এর পরে:

// AFTER: the service lives only where it is real
abstract class SchoolSection {
  constructor(protected sectionName: string) {}
 
  issueBonafide(studentId: string): string {
    return `Bonafide certificate for ${studentId} (${this.sectionName})`;
  }
}
 
class ScienceSection extends SchoolSection {
  // nothing to refuse — the method never reaches here
}
 
class SportsSection extends SchoolSection {
  issueSportsCertificate(studentId: string): string {
    return `Sports certificate for ${studentId}`;
  }
}

Diagram-টা move স্পষ্টভাবে দেখায়:

Figure 5: Push Down Method — issueSportsCertificate() parent ছেড়ে একমাত্র subclass-এ নামে যেটা সেটা ব্যবহার করে

দুটো জয় দেখো। প্রথমত, ScienceSection আর throwing override বহন করছে না — পুরো method টাই চলে গেছে। দ্বিতীয়ত, type system এখন ভুল call আটকায়: scienceSection.issueSportsCertificate() এখন একটা compile error, runtime চমক নয়।

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

এই refactoring কখনো একটা বড় লাফে করো না। ছোট ধাপে এগাও, প্রতিটা ধাপের পরে compile করো আর test চালাও। পুরো যাত্রা স্পষ্ট states-এর মধ্য দিয়ে যায়, আর প্রতিটাতে compile করতে পারা উচিত:

Figure 6: মিথ্যা প্রতিশ্রুতি থেকে সৎ contract পর্যন্ত নিরাপদ পথ — প্রতিটা state compile করে
  1. আসল user-দের নিশ্চিত করো। Method-এ তোমার IDE-র "Find Usages" ব্যবহার করো। কোন subclass-গুলো আসলে এটার দরকার সেটার list করো। উত্তর যদি হয় "তাদের অনেকগুলো", থামো — method টা parent-এই থাকা উচিত, নিচে নামানো উচিত না।

  2. Target subclass-এ method টা copy করো। এখনো কিছু delete করো না। এক মুহূর্তের জন্য method টা দুই জায়গায় থাকে — ঠিক আছে, নিরাপদ।

// INTERMEDIATE STEP: method temporarily lives in BOTH places
abstract class SchoolSection {
  issueSportsCertificate(studentId: string): string {
    return `Sports certificate for ${studentId}`;
  }
}
 
class SportsSection extends SchoolSection {
  // copied down — overrides the parent version for now
  issueSportsCertificate(studentId: string): string {
    return `Sports certificate for ${studentId}`;
  }
}
  1. Method কী touch করে চেক করো। যদি method body একটা field পড়ে যেটা শুধু এই subclass-এর দরকার, সেই field-ও নিচে নামানোর পরিকল্পনা করো (companion refactoring Push Down Field)। Parent-এ এমন data রাখা উচিত না যার একমাত্র customer চলে গেছে।

  2. Parent-এর copy মুছে দাও। এখন superclass থেকে method সরিয়ে দাও, আর অন্য subclass থেকে প্রতিটা "not supported" override সরিয়ে দাও।

  3. Compiler-কে ভাঙা caller খুঁজতে দাও। যে code parent-typed variable-এ method call করেছিল সেটা এখন compile হতে ব্যর্থ হবে। প্রতিটা error visit করো। সাধারণত fix সহজ — সেই caller যাই হোক সবসময় হাতে একটা SportsSection ছিল, তাই variable-এর declared type পরিবর্তন করো। যদি কোনো caller সত্যিই section-এর মিশ্র ভান্ডার ধরে রাখে, আগে type চেক করো:

function printCertificates(section: SchoolSection, studentId: string) {
  console.log(section.issueBonafide(studentId));
  if (section instanceof SportsSection) {
    console.log(section.issueSportsCertificate(studentId));
  }
}
  1. Compile করো আর সব test চালাও। সবুজ test মানে প্রতিশ্রুতির board ঠিক হয়েছে আর কারো আচরণ পরিবর্তন হয়নি।
⚠️

Dynamically typed ভাষায় আর reflection-এর সাথে বিশেষ সতর্ক থাকো। TypeScript-এ compiler প্রতিটা ভাঙা caller ধরে ফেলে, কিন্তু plain JavaScript বা Python-এ parent-typed variable দিয়ে call শুধু runtime-এ fail করে। Parent copy delete করার আগে পুরো codebase-এ method-এর নাম খোঁজো — string-based call-সহ।

📉 জয় পরিমাপ করা

Refactoring অস্পষ্ট মনে হয় যতক্ষণ না কিছু count করো। দুটো সংখ্যা তীব্রভাবে নড়ে যখন method নিচে নামাও: "not supported" throwing override-এর count (সরাসরি শূন্যে) আর call-এর আগে ছড়িয়ে থাকা defensive instanceof check-এর count (সাধারণত এক-দুটো সৎ জায়গায় নেমে আসে)।

Figure 7: hierarchy জুড়ে throwing override, push down-এর আগে ও পরে

আমাদের স্কুলের বাস্তব payroll codebase-এ (এখনই আসছে), চারটা class allowance method-এর জন্য throwing override বহন করছিল যেগুলো তারা কখনো পায়নি। Refactoring-এর পরে সেই count শূন্য — কেউ মনে করে delete করেছে বলে নয়, বরং কারণ refuse করার মতো আর কিছু অবশিষ্ট নেই।

আর এই হলো quadrant আকারে সিদ্ধান্ত। কিছু touch করার আগে এই মানচিত্রে তোমার method রাখো:

Figure 8: এই মানচিত্রে method রাখো — নিচে-বামে মানে আজই নামিয়ে দাও

উপরে-ডানে হলো একটা parent method-এর মিষ্টি বাড়ি। নিচে-বামে — একটা child ব্যবহার করে, বাকিরা অস্বীকার করে — সেটা হলো sports certificate, নিচে নামানোর জন্য চিৎকার করছে। অদ্ভুত নিচে-ডানের কোণ (সবাই call করে তবুও কেউ কেউ অস্বীকার করে) মানে caller-রা ইতিমধ্যে runtime error নিয়ে বাঁচছে — এটা refactoring সমস্যা নয়, design জরুরি অবস্থা।

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

গল্পটা বড় করি। ধরো স্কুল management portal-এ staff payroll model করা হয়েছে। অনেক আগে কেউ calculateMatchAllowance() — ছাত্রদের tournament-এ নিয়ে যাওয়ার জন্য অতিরিক্ত pay — base StaffMember class-এ রেখেছিল। শুধু coach-রাই এটা পায়। Teacher আর clerk-রা পেয়েছে throwing override।

// BEFORE
abstract class StaffMember {
  constructor(protected name: string, protected basicPay: number) {}
 
  abstract monthlySalary(): number;
 
  calculateMatchAllowance(matchDays: number): number {
    return matchDays * 500;
  }
}
 
class Teacher extends StaffMember {
  monthlySalary(): number { return this.basicPay + 4000; }
  calculateMatchAllowance(): number {
    throw new Error("Teachers do not get match allowance"); // Refused Bequest!
  }
}
 
class Clerk extends StaffMember {
  monthlySalary(): number { return this.basicPay + 2000; }
  calculateMatchAllowance(): number {
    throw new Error("Clerks do not get match allowance");   // Refused Bequest!
  }
}
 
class Coach extends StaffMember {
  monthlySalary(): number {
    return this.basicPay + this.calculateMatchAllowance(6);
  }
}

তিনটা class, তার মধ্যে দুটো refusal চিৎকার করছে। Push Down Method-এর পরে:

// AFTER
abstract class StaffMember {
  constructor(protected name: string, protected basicPay: number) {}
  abstract monthlySalary(): number;
}
 
class Teacher extends StaffMember {
  monthlySalary(): number { return this.basicPay + 4000; }
}
 
class Clerk extends StaffMember {
  monthlySalary(): number { return this.basicPay + 2000; }
}
 
class Coach extends StaffMember {
  private matchDaysThisMonth = 6;
 
  monthlySalary(): number {
    return this.basicPay + this.calculateMatchAllowance(this.matchDaysThisMonth);
  }
 
  calculateMatchAllowance(matchDays: number): number {
    return matchDays * 500;
  }
}

Parent এখন pure shared সত্যের তিন লাইন। Teacher আর Clerk code হারিয়েছে — সবসময় আনন্দের মুহূর্ত — আর Coach একটা ছোট method আর matchDaysThisMonth field পেয়েছে যেটা সাথে নেমে এসেছে।

কলেজ কর্নার: লক্ষ্য করো StaffMember এখানে একটা abstract class, interface নয়। এটা তার জায়গা অর্জন করে কারণ এটা বাস্তব shared state (name, basicPay) ধরে রাখে আর tax deduction-এর মতো সত্যিকারের universal logic-এর natural home হবে। C# আর Java-তে, একটা abstract class child-এর single inheritance slot ব্যয় করে — class Coach : StaffMember একমাত্র extends ব্যবহার করে যা একটা class কখনো পাবে। তাই এতে যা রাখো প্রতিটা সেই slot-এর মূল্য হওয়া উচিত। ভুলভাবে base-এ যে method রাখো সেটা ভাষার সবচেয়ে দামি বাড়িতে ভাড়া দিচ্ছে। Push Down Method হলো সেই ভাড়া নষ্ট করা বন্ধ করার উপায়।

💼 C#-এ একই refactoring

C# smell আরো সহজে spot করায়, কারণ refusing subclass সাধারণত NotSupportedException throw করে। ধরো এই bank account hierarchy যেখানে overdraft fee ভুলভাবে base-এ রাখা হয়েছিল:

// BEFORE
public abstract class Account
{
    protected decimal Balance;
 
    public virtual decimal CalculateOverdraftFee(decimal overdrawn)
        => overdrawn * 0.05m;            // only current accounts can overdraw!
}
 
public class SavingsAccount : Account
{
    public override decimal CalculateOverdraftFee(decimal overdrawn)
        => throw new NotSupportedException("Savings accounts cannot overdraw");
}
 
public class CurrentAccount : Account
{
    // silently inherits CalculateOverdraftFee
}

Push Down Method-এর পরে, method শুধু সেখানেই exist করে যেখানে overdraw legal:

// AFTER
public abstract class Account
{
    protected decimal Balance;
}
 
public class SavingsAccount : Account
{
    // nothing to refuse
}
 
public class CurrentAccount : Account
{
    public decimal CalculateOverdraftFee(decimal overdrawn)
        => overdrawn * 0.05m;
}

একটা C# বিস্তারিত মনে রেখো: যখন method নিচে নামে আর আর কিছু override করে না, virtual/override keyword ফেলে দাও। এটা এখন CurrentAccount-এর একটা সাধারণ method। যদি কোনো caller একটা Account variable ধরে fee method call করে, compiler সেটা flag করবে — সেই caller-কে CurrentAccount ব্যবহার করতে পরিবর্তন করো, অথবা আগে pattern-match করো: if (account is CurrentAccount current) { ... }

🐍 Python থেকে একটা সতর্কতা

Python তোমাকে base class-এ মিথ্যা প্রতিশ্রুতি রাখা থেকে থামাবে না — কোনো compiler পাহারা দিচ্ছে না। এটা Push Down Method-এর discipline সেখানে আরো গুরুত্বপূর্ণ করে তোলে, আর test-ই তোমার একমাত্র safety net:

# BEFORE: the base class promises what only Coach delivers
class StaffMember:
    def __init__(self, name: str, basic_pay: int) -> None:
        self.name = name
        self.basic_pay = basic_pay
 
    def calculate_match_allowance(self, match_days: int) -> int:
        return match_days * 500          # false promise for most staff
 
 
# AFTER: pushed down — only Coach carries the allowance logic
class Coach(StaffMember):
    def __init__(self, name: str, basic_pay: int, match_days: int) -> None:
        super().__init__(name, basic_pay)
        self.match_days = match_days
 
    def calculate_match_allowance(self) -> int:
        return self.match_days * 500
 
    def monthly_salary(self) -> int:
        return self.basic_pay + self.calculate_match_allowance()

Python-এ move-এর পরে, পুরো test suite চালাও আর method-এর নামের জন্য codebase grep করো — duck typing মানে একটা ভুলে যাওয়া caller তখনই fail করবে যখন সেই exact line চলে, সম্ভবত production-এ রোববার বিকেলে... দুঃখিত, ভুল গল্প। সেটা Extract Subclass পোস্টে আছে।

কলেজ কর্নার: C#-এ, override আর method hiding-এর মধ্যে পার্থক্য দেখো। যদি CurrentAccount public new decimal CalculateOverdraftFee(...) declare করত যখন base-এ এখনো তার copy থাকত, Account variable দিয়ে call base version চালাত আর CurrentAccount variable দিয়ে call child version চালাত — একটা object-এর জন্য দুটো আচরণ, variable-এর declared type দ্বারা নির্ধারিত। Java-র instance method-এর জন্য এরকম hiding নেই (শুধু static আর field-এর জন্য), আর TypeScript runtime-এ prototype chain follow করে। এটাই ঠিক কারণ step 4 — আসলে parent copy মুছে দেওয়া — গুরুত্বপূর্ণ: C#-এ একটা অর্ধেক-শেষ push down নীরবে আচরণ fork করতে পারে।

🛠️ IDE support

তোমাকে এই move হাতে করতে হবে না — major IDE এটা automate করে।

  • IntelliJ IDEA / Android Studio (Java, Kotlin): method select করো, তারপর Refactor → Push Members Down। একটা dialog class member-এর list checkbox সহ দেখায়; method (আর যেকোনো field যেটা সাথে যাওয়া উচিত) tick করো, আর IDE এটা প্রতিটা direct subclass-এ copy করে parent থেকে সরিয়ে দেয়। JetBrains এটা "Pull members up, push members down" এর অধীনে document করে — দুটো dialog mirror twins।
  • JetBrains Rider / ReSharper (C#): একই Push Members Down refactoring exist করে, আর এটা সতর্ক করে যখন তুমি push করছো এমন member এখনো base type দিয়ে ব্যবহার হচ্ছে।
  • Visual Studio (C#): one-click push down নেই, কিন্তু Find All References আর Move helper-গুলো manual ধাপগুলো দ্রুত করে। (এর বিখ্যাত cousin Extract Interface Quick Actions menu-তে আছে — কাজের যখন ভালো fix push down-এর বদলে একটা সংকীর্ণ contract।)
  • TypeScript editor (VS Code, WebStorm): কোনো dedicated push-down command নেই, কিন্তু compiler হলো তোমার safety net — parent copy মুছো আর red squiggle follow করো।

IDE dialog-এর একটা গুরুত্বপূর্ণ আচরণ: যদি একাধিক subclass exist করে, Push Members Down member টা প্রতিটা direct subclass-এ push করে, duplicate তৈরি করে। যদি শুধু একটা child এটা পাওয়া উচিত, পরে অতিরিক্ত copy মুছে দাও — অথবা ভাবো যে একটা intermediate superclass ভালো design কিনা।

⚖️ Push down বনাম pull up: elevator pair

ছাত্রছাত্রীরা এগুলো ক্রমাগত গুলিয়ে ফেলে, তাই পাশাপাশি দেখো:

প্রশ্নPush Down MethodPull Up Method
দিকParent → childChildren → parent
triggerশুধু একটা (বা কম) child method ব্যবহার করেঅনেক child একই method duplicate করে
যে smell ঠিক করেRefused Bequest, Speculative Generalityভাইবোনদের মধ্যে Duplicate Code
parent-এ প্রভাবslim হয়; contract সৎ হয়বাড়ে; contract সমৃদ্ধ হয় কিন্তু এখনো universal
polymorphism-এ প্রভাবpushed method আর base type-এ call করা যায় না — by designpulled method base type-এ callable হয়
compiler-এর ভূমিকাভুলভাবে parent type ব্যবহার করা প্রতিটা caller flag করেপ্রয়োজনীয় override ভুলে যাওয়া child flag করে

সিদ্ধান্তের প্রশ্ন সবসময় একই: এই behavior সত্যিকারের কে ব্যবহার করে? সবাই — উপরে রাখো বা তোলো। একটা বিশেষ child — নিচে নামাও। একটা নির্দিষ্ট subset — একটা middle superclass extract করো আর ঠিক এক স্তর নামাও।

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

দিকসুবিধাঝুঁকি / খরচ
সৎ contractparent শুধু সেটাই প্রতিশ্রুতি দেয় যা প্রতিটা child করতে পারেparent type ব্যবহার করা caller-রা access হারায় আর fix করতে হবে
Refused Bequestসব "not supported" override মুছে যায়কিছু না — এটা pure win
Class sizeparent slim হয়; Large Class সাহায্য করেreceiving subclass সামান্য বাড়ে
Duplicationকিছু না, যদি শুধু একটা subclass-এর method দরকার হয়অনেক subclass-এ push করলে Duplicate Code তৈরি হয়
Polymorphismbase type দিয়ে call সম্পূর্ণ বিশ্বাসযোগ্য হয়বিশেষ method আর polymorphically call করা যায় না — by design
ভবিষ্যতের পরিবর্তননতুন subclass অপ্রাসঙ্গিক baggage inherit করে নাmethod পরে আবার common হলে, আবার pull up করতে হবে

মধ্যবর্তী trick আবার বলার যোগ্য: যখন subclass-এর একটা subset (যেমন SportsSection আর NccSection) উভয়ের method দরকার, উভয়ে duplicate করো না। Extract Superclass দিয়ে একটা intermediate parent — ActivitySection — তৈরি করো, method টা ঠিক এক স্তর নিচে সেখানে নামাও, আর উভয় activity section সেটা extend করুক।

🧪 কোন smell ঠিক করে?

SmellPush Down Method কীভাবে সাহায্য করে
Refused Bequestsubclass যে inherited member অস্বীকার করছিল সেটা সরিয়ে দেয়; refusal override সম্পূর্ণ অদৃশ্য হয়
Large Classশুধু বিশেষ child ব্যবহার করা member সরিয়ে অতিভারাক্রান্ত superclass ছোট করে
Speculative Generality"একদিন সবার এটা দরকার হবে" design undo করে যেগুলো কখনো সত্য হয়নি
Duplicate Codeপরোক্ষভাবে — তোমাকে না নামাতে সতর্ক করে যখন অনেক child method শেয়ার করে, আর একটা shared home-এ রাখতে বলে

🧠 একটা মানচিত্রে পুরো ধারণা

Figure 9: Push Down Method revision মানচিত্র — smell, move, সতর্কতা, আর বিপরীত

📦 দ্রুত revision box

+--------------------------------------------------------------+
|                    PUSH DOWN METHOD                          |
+--------------------------------------------------------------+
| Problem : Parent class has a method only ONE child uses.     |
|           Other children refuse it ("not supported").        |
| Story   : School office board listed "sports certificate"    |
|           for ALL students; only sports desk gives it.       |
| Fix     : 1. Copy method into the child that uses it         |
|           2. Push down any field it depends on               |
|           3. Delete method (+ refusals) from the parent      |
|           4. Fix callers that used the parent type           |
| Cures   : Refused Bequest, bloated parent (Large Class)      |
| Beware  : Many children need it? DON'T push down —           |
|           keep it up, or extract a middle superclass.        |
| Inverse : Pull Up Method                                     |
+--------------------------------------------------------------+

✍️ অনুশীলন

কোনো সমাধান দেখার আগে নিজে চেষ্টা করো।

ধরো একটা delivery app-এ এই hierarchy আছে:

abstract class DeliveryVehicle {
  abstract maxSpeedKmph(): number;
 
  attachThermalBox(): string {
    return "Thermal box attached for food orders";
  }
}
 
class Bike extends DeliveryVehicle {
  maxSpeedKmph(): number { return 60; }
}
 
class BicycleCart extends DeliveryVehicle {
  maxSpeedKmph(): number { return 15; }
  attachThermalBox(): string {
    throw new Error("Bicycle carts carry parcels only, no food");
  }
}
 
class Van extends DeliveryVehicle {
  maxSpeedKmph(): number { return 80; }
  attachThermalBox(): string {
    throw new Error("Vans carry furniture, no food");
  }
}

তোমার কাজ:

  1. কোন subclass সত্যিকারের attachThermalBox() ব্যবহার করে আর কোনগুলো অস্বীকার করে সেটা চিহ্নিত করো। Figure 8-এর quadrant sketch করো আর method টা সেখানে রাখো।
  2. নিরাপদ ধাপে Push Down Method প্রয়োগ করো: নিচে copy করো, parent থেকে মুছে দাও, throwing override সরাও। Figure 6-এর প্রতিটা state-এ compile (বা test চালাও)।
  3. Codebase-এ একটা caller আছে: function prepareForFood(v: DeliveryVehicle) { v.attachThermalBox(); }। এর parameter type ঠিক করো যাতে আবার compile হয়।
  4. Bonus: ধরো আগামীকাল একটা নতুন Scooter class-এরও thermal box দরকার। এখন দুটো child method শেয়ার করে। সেই intermediate superclass (FoodCapableVehicle?) sketch করো যেটা তুমি extract করবে যাতে method এখনো একটাই জায়গায় থাকে।
  5. কলেজ-স্তরের Bonus: দুটো বাক্যে explain করো কেন original BicycleCart.attachThermalBox() override Liskov Substitution Principle লঙ্ঘন করেছিল, আর কেন after-version তাত্ত্বিকভাবেও এটা লঙ্ঘন করতে পারে না।

যদি তোমার final parent class শুধু maxSpeedKmph() ধারণ করে, আর কোথাও কোনো class "not supported" throw না করে, তুমি সঠিকভাবে করেছো — মিসেস রহিমা সৎ board-এ তোমার নাম রাখতেন।

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

Push Down Method আর Pull Up Method-এর পার্থক্য কী?
দুটো একদম উল্টো। Pull Up Method মেথড সাবক্লাস থেকে parent-এ তোলে, কারণ অনেক চাইল্ড সেটা শেয়ার করে। Push Down Method মেথড parent থেকে subclass-এ নামায়, কারণ শুধু একটা চাইল্ড সেটা আসলে ব্যবহার করে। behavior সবার জন্য হলে উপরে তোলো, বিশেষ একজনের হলে নিচে নামাও।
যদি দুই-তিনটা subclass মেথডটা চায়, কিন্তু সবাই না?
প্রত্যেকটায় copy-paste করলে Duplicate Code smell তৈরি হয়। ভালো সমাধান হলো সেই গ্রুপের জন্য একটা মাঝখানের superclass বানানো — যেমন SportsSection আর NccSection দুটোই একটা ActivitySection থেকে extend করতে পারে যেখানে shared method থাকবে। মেথডটা মাত্র এক স্তর নিচে নামাও, সেই মধ্যবর্তী parent-এ।
নিচে নামানোর পরে কি parent type দিয়ে call করা caller-রা ভেঙে যাবে?
হ্যাঁ, যে code parent-typed variable দিয়ে method call করেছে সেটা compile হওয়া বন্ধ করবে। এটা আসলে কাজের — compiler দেখিয়ে দেবে কোন caller ভুলভাবে ধরে নিয়েছিল যে সব চাইল্ডের এই ক্ষমতা আছে। প্রতিটা ঠিক করো subclass type ব্যবহার করে, অথবা call করার আগে আসল type চেক করে।
subclass-এ 'not supported' throw করা কি আসলেই এত খারাপ?
এটা Refused Bequest smell-এর ক্লাসিক লক্ষণ। parent একটা ক্ষমতার প্রতিশ্রুতি দেয়, child সেটা অস্বীকার করে, আর ব্যর্থতা ধরা পড়ে runtime-এ যখন কেউ এটা call করে। method নিচে নামিয়ে দিলে সেই runtime চমক compile-time গ্যারান্টিতে পরিণত হয় — method টা সহজভাবে সেখানে exist করে না যেখানে এর কোনো মানে নেই।
Push Down Method কি field-এও প্রযোজ্য?
Push Down Field নামে একটা companion refactoring আছে যেটা data-র জন্য একই কাজ করে। অনেক সময় দুটো একসাথে করতে হয় — method নিচে নামালে, যে field শুধু সেই method ব্যবহার করত সেটাও নিচে নামিয়ে দাও। parent-এ orphan state থাকা উচিত না।

আরো দেখো

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

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

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

আরও পড়ুন

Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে

Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।

আরও পড়ুন

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

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

আরও পড়ুন

Extract Subclass: বিশেষ কেসটাকে তার নিজের class দাও

Extract Subclass শেখো একটা দর্জির দোকানের জরুরি অর্ডারের গল্পের মধ্যে দিয়ে — TypeScript আর C#-এ flag সরানোর পদ্ধতি, নিরাপদ ধাপে ধাপে কাজ, আর কখন subclassing ভুল সিদ্ধান্ত সেটাও বুঝবে।

আরও পড়ুন