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

Form Template Method: একটা রেসিপি কার্ড, অনেক রকম বিরিয়ানি

দুই চাচির বিরিয়ানির গল্প দিয়ে Form Template Method রিফ্যাক্টরিং শেখো — TypeScript আর Python-এ shared algorithm skeleton বের করার ধাপে ধাপে পদ্ধতি, আর কীভাবে এই রিফ্যাক্টরিং Template Method design pattern তৈরি করে।

23 মিনিট আপডেট: June 11, 2026intermediate
refactoringtemplate methodduplicate codeinheritancedesign patternstypescriptpython

🍚 দুই চাচি, এক বিরিয়ানি

ধরো ঢাকার একটা অ্যাপার্টমেন্ট বিল্ডিং-এ দুজন বিখ্যাত রাঁধুনি থাকেন। দ্বিতীয় তলার রাহেলা চাচির চিকেন বিরিয়ানি মানুষ সপ্তাহ ধরে মনে রাখে। চতুর্থ তলার সুমাইয়া চাচি কঠোর নিরামিষভোজী, আর তার ভেজ বিরিয়ানির নিজস্ব ভক্তকুল আছে। দুই চাচি বন্ধুত্বপূর্ণ প্রতিদ্বন্দ্বী — প্রতিটা বিল্ডিং ফাংশনে দুই কিচেনের থালা ঘোরে।

এক রবিবার, বিল্ডিং একটা কমিউনিটি রেসিপি বই ছাপানোর সিদ্ধান্ত নিল। ষষ্ঠ তলার engineering ছাত্রী ফাতিমা রেসিপি টাইপ করার দায়িত্ব নিল। দুই চাচি একে একে রেসিপি বললেন, আর দ্বিতীয়টা মাঝপথে ফাতিমা হাসতে শুরু করল। রাহেলা চাচির রেসিপি:

  1. বাসমতি চাল আধ ঘণ্টা ভিজিয়ে রাখো।
  2. পেঁয়াজ আর গোটা মসলা ঘিয়ে ভাজো।
  3. ম্যারিনেট করা চিকেন দিয়ে নরম না হওয়া পর্যন্ত রান্না করো।
  4. আধসেদ্ধ চালের স্তর মসলার উপর দাও।
  5. পাতিল বন্ধ করে ২০ মিনিট দমে রাখো।

সুমাইয়া চাচির রেসিপি:

  1. বাসমতি চাল আধ ঘণ্টা ভিজিয়ে রাখো।
  2. পেঁয়াজ আর গোটা মসলা ঘিয়ে ভাজো।
  3. পনির আর সবজি দিয়ে নরম না হওয়া পর্যন্ত রান্না করো।
  4. আধসেদ্ধ চালের স্তর মসলার উপর দাও।
  5. পাতিল বন্ধ করে ২০ মিনিট দমে রাখো।

পাঁচটা step। চারটা — হুবহু একই শব্দে — অভিন্ন। শুধু step ৩ আলাদা। ফাতিমা বুদ্ধিমানের কাজ করল। দুটো পূর্ণ রেসিপি ছাপানোর বদলে সে একটাই রেসিপি কার্ড ছাপাল "বিরিয়ানি" শিরোনামে। Step ১, ২, ৪, ৫ একবার লিখল, আর step ৩ রাখল fill-in-the-blank হিসেবে: "মূল উপকরণ দিয়ে রান্না করো — চিকেন (রাহেলা স্টাইল) অথবা পনির আর সবজি (সুমাইয়া স্টাইল)।"

চাচিরা ঠিক এক মিনিট প্রতিবাদ করলেন — "আমার রেসিপি স্পেশাল!" — যতক্ষণ না ফাতিমা কার্ড দেখিয়ে জিজ্ঞেস করল, "চাচি, এই চারটা step-এর মধ্যে কোনটা তোমার কিচেনে আলাদা?" নিরব। স্পেশালনেস পুরোটা step ৩-এ, আর কার্ড সেটা সৎভাবে বলল।

এখন একটু ভাবো সেই একটা কার্ড পুরো বিল্ডিং-এর জন্য কী করে। দমের সময় বিশ মিনিট থেকে পঁচিশ মিনিট হলে — রাহেলা চাচি পরীক্ষা-নিরীক্ষা করছেন — এক জায়গায় ঠিক করলেই বিল্ডিং-এর সব বিরিয়ানি একসাথে উন্নত হয়। তৃতীয় তলার নাসরিন চাচি ডিমের বিরিয়ানি বানাতে চাইলে, পুরো নতুন রেসিপি লিখতে হবে না — শুধু blank পূরণ করতে হবে। আর কারো রেসিপির order চুপচাপ বদলে যেতে পারবে না, কারণ order টা shared কার্ডে থাকে। গত বছর, কার্ডের আগে, সুমাইয়া চাচির ভাগ্নি হাতে রেসিপি কপি করতে গিয়ে step ২ আর ৪ উল্টে ফেলেছিল — সেই বিরিয়ানির কথা বিল্ডিং এখনো বলে। এক কার্ড থাকলে সেই ভুলটা structurally অসম্ভব।

সেই রেসিপি কার্ডটাই আজকের রিফ্যাক্টরিং। যখন দুটো subclass একই sequence of steps perform করে আর শুধু কোনো step-এর details-এ আলাদা, তখন shared sequence টা একটা superclass method-এ তুলে নেওয়া হয় আর আলাদা step গুলো blank হিসেবে রাখা হয় যা subclass গুলো পূরণ করে। এটাই Form Template Method

চিত্র ১: দুটো প্রায়-একই রেসিপি থেকে একটা template কার্ড — মাঝের step পরিবর্তনযোগ্য

Form Template Method কী? 📋

Form Template Method সবচেয়ে চতুর ধরনের duplication-এর বিরুদ্ধে লড়াই করে: shape-এর duplication। দেখো, দুটো subclass method একই procedure follow করে — একই step, একই order-এ — কিন্তু লাইনগুলো detail-এ আলাদা। Line-by-line diff দুটো আলাদা method দেখে। কিন্তু তোমার চোখ দুটো পড়ে বুঝতে পারে — একই algorithm দুবার লেখা।

এই duplication বিপজ্জনক কারণ এটা tool-এর কাছে অদৃশ্য। আর সব duplicated knowledge-এর মতো, এটা drift করে। একটা subclass একটা validation step যোগ করল, অন্যটা পেল না। কেউ এক copy-তে দুটো step reorder করল "কারণ পড়তে ভালো লাগছে।" ছয় মাস পরে, দুটো procedure যেগুলো "একই" ছিল সেগুলো তিনটা subtle জায়গায় disagree করছে, আর কেউ বলতে পারছে না কোন পার্থক্যগুলো intentional।

"আলাদা" method-এর একটা typical জুটির কতটুকু আসলে shared? ফাতিমা তার দুটো রেসিপি measure করল, উত্তর সবসময়ের মতোই:

চিত্র ২: চাচিদের রেসিপি সততার সাথে measure করা — বেশিরভাগ পার্থক্যই আসলে কোনো পার্থক্য নয়

সমাধানের দুটো movement:

  1. Skeleton গুলো identical করো। প্রতিটা subclass-এর ভেতরে, আলাদা অংশগুলো ছোট method-এ extract করো। গুরুত্বপূর্ণ বিষয় হলো উভয় subclass-এ একই name আর signature দিতে হবে। যথেষ্ট extraction-এর পরে, দুটো top-level method textually identical হয়ে যাবে — প্রতিটা লাইন হয় shared logic নাহলে একটা same-named step-এর call।
  2. Skeleton তোলো। এখন identical top-level method টা superclass-এ pull up করো। আলাদা step গুলো subclass-এ রয়ে যায়, parent-এ abstract (বা overridable) operation হিসেবে। তোলা method টাকে template method বলে: এটা order-এর মালিক; subclass গুলো details-এর মালিক।

ব্যাপারটা হলো, ফলাফল একটা বিখ্যাত নামের structure। Form Template Method হলো সেই রিফ্যাক্টরিং যেটা Template Method pattern তৈরি করে — Gang of Four pattern যার definition আমাদের রেসিপি কার্ডের মতোই: একটা operation-এ algorithm-এর skeleton define করো, কিছু step subclass-এ defer করে। Pattern হলো গন্তব্য; এই রিফ্যাক্টরিং হলো সেই রাস্তা যেটা existing duplicated code থেকে শুরু হয়।

💡

এক লাইনে সারমর্ম: যখন দুটো subclass একই step একই order-এ আলাদা details সহ run করে, আলাদা step গুলো same-named method-এ extract করো, shared sequence টা superclass template method-এ pull up করো, আর প্রতিটা subclass শুধু তার নিজের blank পূরণ করুক।

আরেকটা vocabulary word শিখে নাও — hook। প্রতিটা step abstract হতে হবে না। এমন একটা step যেটা শুধু কিছু subclass বদলাতে চায়, সেটা parent-এ একটা default implementation দেওয়া যায় — প্রায়ই empty body। Subclass override করতে পারে কিন্তু বাধ্য নয়। রেসিপি কার্ডে: "Step ৪½ (ঐচ্ছিক): ভালো লাগলে ভাজা কাজু বাদাম দাও।" রাহেলা চাচি এড়িয়ে যান, সুমাইয়া চাচি দেন। Hook গুলো template-কে flexible রাখে, প্রতিটা রাঁধুনিকে প্রতিটা প্রশ্নের উত্তর দিতে বাধ্য না করে।

চিত্র ৩: Form Template Method-এর পুরো idea map

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

যেসব লক্ষণ খেয়াল রাখতে হবে:

  • Sibling subclass-এ parallel method। দুটো (বা বেশি) subclass প্রতিটায় generate(), process(), বা export()-এর মতো method implement করেছে। পাশাপাশি পড়লে মনে হয় কয়েকটা শব্দ swap করা একই paragraph পড়ছো। এটা হলো structure-এর level-এ Duplicate Code — সবচেয়ে খারাপ ধরনের, কারণ diff tool দেখতে পায় না।
  • Bug দুবার fix। History দেখায় একই fix মার্চে HtmlReport.render-এ আর মে মাসে MarkdownReport.render-এ apply হয়েছে, যখন কেউ দ্বিতীয় copy-টা অবশেষে খেয়াল করেছে। দুবার লেখা knowledge মানেই drift করবে।
  • নতুন variant মানে copy-paste। PdfReport যোগ করা শুরু হয় "copy HtmlReport, তিনটা লাইন বদলাও" দিয়ে। প্রতিটা copy গর্ত আরো গভীর করে।
  • পার্থক্যগুলো similarity লুকিয়ে রাখে। Code reviewer দ্রুত বলতে পারে না "এই দুটো subclass-এ আসলে কী আলাদা?" কারণ genuine পার্থক্যগুলো দুইশত shared লাইনের ভেতরে লুকানো।

আর যেসব পরিস্থিতিতে এটা প্রযোজ্য নয়:

  • Order সত্যিই আলাদা। একটা subclass যদি ইচ্ছা করে validate-then-save করে আর অন্যটা save-then-validate করে, তাহলে তোলার মতো কোনো shared skeleton নেই। একটায় জোর করলে দুটো procedure-ই নষ্ট হয়। Step গুলো সত্যিই order share করতে হবে।
  • মিলটা superficial। যে দুটো method "loop করে আর string তৈরি করে" সেগুলো একই algorithm নয়। একটু ভাবো: তুমি কি একটা numbered recipe লিখতে পারবে যেটা সততার সাথে দুটোকে describe করে? না পারলে, আলাদা রাখো।
  • Variation runtime-এ বদলাতে হবে। Template method variation-কে compile time-এ class hierarchy-তে আটকে দেয়। কোনো live object-কে step swap করতে হলে — বা step স্বাধীনভাবে combine করতে হলে — তোমার Strategy object দরকার, Replace Inheritance with Delegation-এর মাধ্যমে।
  • Class গুলো এখনো sibling নয়। Duplicated method গুলো unrelated class-এ থাকলে, আগে Extract Superclass দিয়ে common parent দাও; তারপরই template method থাকতে পারে। আর কোনো subclass parent-এর বেশিরভাগ জিনিস refuse করলে, তুমি Refused Bequest-এর দিকে যাচ্ছ — inheritance এখানে ঠিক আছে কিনা পুনর্বিবেচনা করো।

Apply করব কি না সেটা এক নজরে:

প্রশ্নহ্যাঁ →না →
প্রতিটা variant-এ একই order-এ একই step?এগিয়ে যাওথামো — shared skeleton নেই
একটা সৎ numbered recipe কি সব variant describe করতে পারে?এগিয়ে যাওথামো — মিলটা superficial
Variation কি class-প্রতি fixed (call-প্রতি নয়)?এগিয়ে যাওDelegation দিয়ে Strategy ব্যবহার করো
Class গুলো কি sibling (বা সততার সাথে sibling হতে পারে)?Form Template Method apply করোআগে Extract Superclass, তারপর পুনরায় দেখো

আর template method আর তার নিকটতম প্রতিযোগী Strategy pattern-এর মধ্যে পছন্দ — step order কতটা stable, আর variation runtime-এ বদলাতে হবে কিনা:

চিত্র ৪: Template method নাকি Strategy? Fixed order আর compile-time variation template-এর দিকে নির্দেশ করে

Before আর after এক নজরে

দুই চাচিকে code-এ রাখা যাক। Before — দুটো cook method, চার-পঞ্চমাংশ identical:

// BEFORE: the same procedure written twice; only one step truly differs
class ChickenBiryaniCook {
  cook(): string[] {
    const log: string[] = [];
    log.push("Soak basmati rice 30 min");            // shared
    log.push("Fry onions and whole masala in ghee"); // shared
    log.push("Add marinated chicken, cook till tender"); // DIFFERENT
    log.push("Layer part-cooked rice over masala");  // shared
    log.push("Seal pot, dum for 20 min");            // shared
    return log;
  }
}
 
class VegBiryaniCook {
  cook(): string[] {
    const log: string[] = [];
    log.push("Soak basmati rice 30 min");            // shared (copy #2)
    log.push("Fry onions and whole masala in ghee"); // shared (copy #2)
    log.push("Add paneer and vegetables, cook till soft"); // DIFFERENT
    log.push("Layer part-cooked rice over masala");  // shared (copy #2)
    log.push("Seal pot, dum for 20 min");            // shared (copy #2)
    return log;
  }
}

After — parent-এ একটা রেসিপি কার্ড, প্রতিটা চাচির একটা করে filled-in blank:

// AFTER: the skeleton lives once; subclasses supply only their difference
abstract class BiryaniCook {
  cook(): string[] {                       // the TEMPLATE METHOD
    const log: string[] = [];
    log.push("Soak basmati rice 30 min");
    log.push("Fry onions and whole masala in ghee");
    log.push(this.addMainIngredient());    // the blank to fill in
    log.push("Layer part-cooked rice over masala");
    log.push(this.garnish());              // a HOOK - optional
    log.push("Seal pot, dum for 20 min");
    return log;
  }
 
  protected abstract addMainIngredient(): string;  // every cook must answer
  protected garnish(): string { return "No garnish"; } // default: skip
}
 
class ChickenBiryaniCook extends BiryaniCook {
  protected addMainIngredient(): string {
    return "Add marinated chicken, cook till tender";
  }
}
 
class VegBiryaniCook extends BiryaniCook {
  protected addMainIngredient(): string {
    return "Add paneer and vegetables, cook till soft";
  }
  protected garnish(): string {            // only Meena Aunty overrides the hook
    return "Add fried cashews";
  }
}

এখন প্রতিটা subclass কী ধারণ করে গণনা করো: শুধু তার genuine পার্থক্য। নাসরিন চাচির ডিমের বিরিয়ানি যোগ করা মাত্র তিন লাইনের class। Dum-এর সময় বদলানো একটা লাইন edit, একবার, সবার জন্য। আর step-এর order variant গুলোর মধ্যে physically drift করতে পারবে না, কারণ order-এর একটাই copy আছে।

চিত্র ৫: রিফ্যাক্টরিং-এর পরে, parent algorithm-এর skeleton-এর মালিক; subclass শুধু আলাদা step-এর মালিক

একটা বিরিয়ানি রান্না হওয়ার সময় template আর subclass-এর কথোপকথন দেখো। Parent show চালায়; child শুধু জিজ্ঞেস করলে উত্তর দেয়:

চিত্র ৬: Template algorithm চালায় আর blank-এ subclass-কে call করে — কখনো উল্টো নয়

কলেজ কর্নার: এই উল্টো control flow-এর একটা বিখ্যাত ডাকনাম আছে — Hollywood principle: "আমাদের call করো না, আমরা তোমাকে call করব।" সাধারণ code-এ specific জিনিস general জিনিসকে call করে (তোমার function একটা library call করে)। Template method-এ, general জিনিস specific জিনিসকে call করে: abstract parent drive করে, আর concrete subclass গুলো blank-এ তার দ্বারা call হয়। এই inversion of control একই idea যেটা সর্বত্র framework চালায় — তুমি React বা ASP.NET call করো না; তারা well-defined point-এ তোমার component আর handler call করে। Template method form করলে, তুমি named extension point সহ একটা ছোট framework বানাচ্ছ।

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

রিফ্যাক্টরিং হলো একের পর এক ছোট, individually-safe move-এর sequence। সোনালী নিয়ম: কিছু pull up করার আগে দুটো method অবশ্যই textually identical হতে হবে।

ধাপ ১: দুটো method পাশাপাশি রাখো। প্রতিটা লাইন হয় shared (দুটোতেই identical) নাহলে varying হিসেবে চিহ্নিত করো। এমন চিহ্নিতকরণ তৈরি করতে না পারলে — order disagree করলে — এখানেই থামো; এই রিফ্যাক্টরিং প্রযোজ্য নয়।

ধাপ ২: প্রতিটা varying অংশ method-এ extract করো — উভয় subclass-এ একই নাম দিয়ে। এটাই technique-এর হৃদয়। প্রতিটা subclass-এর ভেতরে Extract Method apply করো যাতে আলাদা লাইনগুলো ছোট method-এ চলে যায়, যাদের name আর signature siblings জুড়ে ঠিক match করে:

// Intermediate state: both cook() methods now read IDENTICALLY,
// but each still lives in its own subclass. Nothing pulled up yet.
class ChickenBiryaniCook {
  cook(): string[] {
    const log: string[] = [];
    log.push("Soak basmati rice 30 min");
    log.push("Fry onions and whole masala in ghee");
    log.push(this.addMainIngredient());   // extracted, same name in both
    log.push("Layer part-cooked rice over masala");
    log.push("Seal pot, dum for 20 min");
    return log;
  }
  private addMainIngredient(): string {
    return "Add marinated chicken, cook till tender";
  }
}
// VegBiryaniCook.cook() is now line-for-line THE SAME — only
// its addMainIngredient() body differs.

প্রতিটা extraction-এর পরে compile আর test করো, সব একসাথে নয়।

ধাপ ৩: Skeleton গুলো match করছে কিনা verify করো। দুটো cook() body diff করো। Character-for-character identical হতে হবে। কোনো stray পার্থক্য থাকলে — ভিন্ন variable name, extra log line — হয় shared করো নাহলে সেটাও extract করো।

ধাপ ৪: Skeleton pull up করো। Pull Up Method apply করো (প্রথমে Extract Superclass দিয়ে superclass তৈরি করো যদি না থাকে)। উঠানো cook() এখন template method। উভয় subclass থেকে identical copy মুছে ফেলো।

ধাপ ৫: Parent-এ step গুলো declare করো। প্রতিটা extracted step method superclass-এ protected abstract হয়; subclass version গুলো তার implementation হয়। Compiler এখন enforce করবে যে প্রতিটা নতুন variant প্রতিটা required প্রশ্নের উত্তর দেয়।

ধাপ ৬: Optional step গুলো hook-এ রূপান্তর করো। যেখানে শুধু কিছু subclass কোনো step চায়, সেখানে abstract-এর বদলে parent-কে default (প্রায়ই empty) implementation দাও। Final template-এ কোনো remaining inline if (this instanceof ...)-style পার্থক্য review করো আর সেগুলোকেও hook-এ রূপান্তর করো।

চিত্র ৭: রিফ্যাক্টরিং-এর states — skeleton textually identical হওয়ার আগে কখনো pull up করো না
⚠️

দুটো trap সম্পর্কে সতর্ক থাকো। প্রথমত, skeleton identical হওয়ার আগে pull up করো না — "mostly same" method pull up করলে parent-এ type check দিয়ে পার্থক্য patch করতে হবে, যেটা শুরুর duplication-এর চেয়েও খারাপ। দ্বিতীয়ত, মনে রাখো subclass গুলো এখন parent-এর promised order-এ step call করার উপর নির্ভর করছে — এটা একটা real contract। পরে কোনো teammate template-এর ভেতরে দুটো লাইন reorder করলে চুপচাপ প্রতিটা subclass ভেঙে যেতে পারে। Step গুলো clearly নাম দাও, sequence document করো, আর একটা test রাখো যেটা order lock down করে।

কলেজ কর্নার: সেই দ্বিতীয় trap হলো fragile base class problem template-এর এপ্রোন পরা। Template method ইচ্ছাকৃতভাবে subclass গুলোকে parent-এর internal calling sequence-এর সাথে couple করে — এটাই পুরো বিষয়। মানে হলো sequence টা "implementation detail" থেকে "published contract"-এ upgrade হয়েছে। Template-এর body-কে public API-এর মতো treat করো: পরিবর্তনে একই যত্ন, একই review, আর ideally একটা characterization test যেটা কোনো step drop বা reorder হলে fail করে। Pattern-টা powerful কারণ এটা এক ধরনের coupling-কে official করে আর তার নাম দেয়।

একটা বড় real-life উদাহরণ 🧾

ধরো একটা school management system দুটো format-এ fee receipt generate করে: parent portal-এর জন্য HTML receipt আর SMS-এর জন্য plain-text receipt। দুটো generator আলাদাভাবে বেড়েছে, এখন প্রতিটায় চল্লিশ লাইন half-shared logic। তাদের সত্যিকারের রূপটা এখানে:

// BEFORE: one algorithm, two bodies, already drifting
class HtmlReceipt {
  generate(s: Student, items: FeeItem[]): string {
    let out = "<div class='receipt'>";
    out += `<h2>Sunrise Public School</h2>`;
    out += `<p>${s.name} — Class ${s.className} — Roll ${s.roll}</p>`;
    let total = 0;
    for (const item of items) {
      out += `<row>${item.label}: Rs.${item.amount}</row>`;
      total += item.amount;
    }
    out += `<b>Total: Rs.${total}</b>`;
    out += "</div>";
    return out;
  }
}
 
class SmsReceipt {
  generate(s: Student, items: FeeItem[]): string {
    let out = "SUNRISE PUBLIC SCHOOL\n";
    out += `${s.name} | Cl ${s.className} | Roll ${s.roll}\n`;
    let total = 0;
    for (const item of items) {
      out += `${item.label}: Rs.${item.amount}\n`;
      total += item.amount;
    }
    out += `TOTAL: Rs.${total}`;
    // bug fixed here last month... but not in HtmlReceipt:
    return out.trim();
  }
}

Procedure টা identical: school header, student line, total accumulate করতে fee items-এর উপর loop, total line, শেষ। শুধু প্রতিটা piece-এর formatting আলাদা। দুটো movement apply করো — same-named step extract করো, তারপর skeleton তোলো:

// AFTER: the algorithm lives once; formats fill in the blanks
abstract class FeeReceipt {
  generate(s: Student, items: FeeItem[]): string {   // template method
    let out = this.header();
    out += this.studentLine(s);
    let total = 0;
    for (const item of items) {
      out += this.feeLine(item);
      total += item.amount;
    }
    out += this.totalLine(total);
    return this.finish(out);
  }
 
  protected abstract header(): string;
  protected abstract studentLine(s: Student): string;
  protected abstract feeLine(item: FeeItem): string;
  protected abstract totalLine(total: number): string;
  protected finish(out: string): string { return out; }   // hook
}
 
class HtmlReceipt extends FeeReceipt {
  protected header() { return "<div class='receipt'><h2>Sunrise Public School</h2>"; }
  protected studentLine(s: Student) {
    return `<p>${s.name} — Class ${s.className} — Roll ${s.roll}</p>`;
  }
  protected feeLine(i: FeeItem) { return `<row>${i.label}: Rs.${i.amount}</row>`; }
  protected totalLine(t: number) { return `<b>Total: Rs.${t}</b></div>`; }
}
 
class SmsReceipt extends FeeReceipt {
  protected header() { return "SUNRISE PUBLIC SCHOOL\n"; }
  protected studentLine(s: Student) {
    return `${s.name} | Cl ${s.className} | Roll ${s.roll}\n`;
  }
  protected feeLine(i: FeeItem) { return `${i.label}: Rs.${i.amount}\n`; }
  protected totalLine(t: number) { return `TOTAL: Rs.${t}`; }
  protected finish(out: string) { return out.trim(); }   // SMS-only need
}

তিনটা চুপচাপ জয় লক্ষ্য করো। Total-accumulation logic — actual business logic — এখন একবার আছে, তাই "wrong total" শ্রেণির bug শুধু এক জায়গায় হতে পারে। trim() fix যেটা SMS পেয়েছিল আর HTML মিস করেছিল, এখন সেটা honest, visible, named পার্থক্য (finish) — কোনো accident নয়। আর school পরের term-এ WhatsApp receipt যোগ করলে, নতুন class হবে পাঁচটা ছোট formatting method — algorithm free পাওয়া যাবে।

শেষ দাবিটা measure করো। প্রতিটা নতুন receipt format-এর জন্য কতটা লিখতে হবে, আগে আর পরে:

চিত্র ৮: নতুন receipt format যোগ করতে লেখা লাইন — রিফ্যাক্টরিং-এর পরে skeleton free পাওয়া যায়

পুরো school project-এর অভিজ্ঞতা, যেভাবে team এটা বেঁচেছে:

চিত্র ৯: Receipt team-এর যাত্রা drifting copy থেকে shared skeleton-এ

Python-এ একই রিফ্যাক্টরিং 🐍

Python abc দিয়ে result টা বিশেষভাবে cleanly প্রকাশ করা যায়। এখানে একটা data importer যেটা file পড়ে database-এ নেয় — CSV আর JSON version পুরো pipeline duplicate করেছিল; শুধু parsing step-ই আসলে আলাদা ছিল:

from abc import ABC, abstractmethod
 
class FileImporter(ABC):
    def run(self, path: str) -> int:          # the template method
        raw = self._read(path)                 # shared
        records = self._parse(raw)             # the blank: varies per format
        valid = [r for r in records if self._is_valid(r)]   # shared
        self._before_save(valid)               # hook: default does nothing
        return self._save(valid)               # shared
 
    def _read(self, path: str) -> str:
        with open(path, encoding="utf-8") as f:
            return f.read()
 
    @abstractmethod
    def _parse(self, raw: str) -> list[dict]: ...
 
    def _is_valid(self, record: dict) -> bool:
        return bool(record.get("id"))
 
    def _before_save(self, records: list[dict]) -> None:
        pass                                    # hook — override if needed
 
    def _save(self, records: list[dict]) -> int:
        # one shared, well-tested save path for every format
        return database.bulk_insert(records)
 
 
class CsvImporter(FileImporter):
    def _parse(self, raw: str) -> list[dict]:
        header, *rows = raw.splitlines()
        keys = header.split(",")
        return [dict(zip(keys, row.split(","))) for row in rows]
 
 
class JsonImporter(FileImporter):
    def _parse(self, raw: str) -> list[dict]:
        import json
        return json.loads(raw)
 
    def _before_save(self, records: list[dict]) -> None:
        records.sort(key=lambda r: r["id"])     # JSON feeds arrive unsorted

Python-specific কিছু নোট:

  • @abstractmethod আর ABC থেকে inherit করা TypeScript-এর abstract-এর মতো compiler-style safety দেয়। _parse ভুলে যাওয়া subclass instantiate করলে সাথে সাথে raise হয়, কোনো পরবর্তী call-এ নয়।
  • Leading underscore signal করে "step method — template দ্বারা call হয়, client দ্বারা নয়।" Template (run) কে একমাত্র public entry point রাখো যাতে কেউ অর্ধেক algorithm invoke না করে।
  • Hook গুলো এখানে idiomatic: _before_save default-এ pass, আর যে subclass care করে শুধু সেটাই override করে।
  • Python-এর standard library নিজেই এই exact shape-এ ভরা — যেমন, unittest.TestCase.run() হলো একটা template method যেটা fixed order-এ setUp, test, আর tearDown call করে। তুমি এই pattern আগে থেকেই ব্যবহার করে আসছ

IDE সাপোর্ট 🛠️

Mainstream IDE-তে কোনো একক "Form Template Method" button নেই — এটা একটা composite refactoring। কিন্তু প্রতিটা ingredient automated, যেটা প্রতিটা step safe রাখে:

  • IntelliJ IDEA / PyCharm / Rider: Extract Method (Ctrl+Alt+M) varying step গুলো isolate করে; Pull Members Up unified skeleton তোলে আর একই dialog-এ remaining step গুলো abstract চিহ্নিত করতে পারে। Extract Superclass parent তৈরি করে যখন duplicated class গুলোর কোনোটার parent নেই।
  • ReSharper (Visual Studio, C#): Extract Method, Pull Members Up (member-প্রতি "Make abstract" checkbox সহ), আর Extract Superclass পুরো sequence cover করে।
  • VS Code (TypeScript): built-in Extract to method refactoring Step 2 handle করে; pull-up manual, কিন্তু TypeScript-এর abstract keyword আর compiler verify করে কিছু miss হয়নি — কোনো step missing subclass compile করতে refuse করবে।

আসলে research literature এটাকে automate করার জন্য সবচেয়ে কঠিন refactoring গুলোর একটা বলে, কারণ কোনটা একই step সেই সিদ্ধান্তটা human judgment দরকার। IDE code move করে; তুমি meaning সরবরাহ করো।

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

সুবিধাঝুঁকি / cost
Structural duplication সরিয়ে দেয় যা line-by-line diff tool দেখতেও পায় নাStep order সত্যিই identical না হলে, একটা template force করলে দুটো procedure-ই বিকৃত হয়
Algorithm-এর order একটা জায়গায় থাকে — একবার fix করো, প্রতিটা variant উপকৃত হয়Subclass গুলো parent-এর calling order-এর উপর নির্ভর করে: template edit করলে চুপচাপ override ভেঙে যেতে পারে (fragile base class)
নতুন variant হলো ছোট class — শুধু blank, কখনো skeleton নয়ডজন খানেক one-line hook-এ over-fragment করলে duplication-এর চেয়েও readability বেশি ক্ষতিগ্রস্ত হয়
Compiler enforce করে যে প্রতিটা variant প্রতিটা required step-এর উত্তর দেয়Variation compile time-এ hierarchy-তে welded — step গুলো runtime-এ swap করা যায় না
বিখ্যাত Template Method pattern তৈরি করে, named extension point সহপ্রতিটা class method name-প্রতি শুধু একটা template support করে; competing variation Strategy demand করতে পারে

কোন smells এটা সারায়? 👃

SmellForm Template Method কীভাবে সাহায্য করে
Duplicate CodeDuplicated skeleton — diff-এর কাছে invisible — একটা template method-এ collapse হয়
Shotgun SurgeryProcedure-এ পরিবর্তন প্রতিটা subclass copy-র বদলে একটা template edit করে
Divergent copiesVariant গুলো physically order-এ drift করতে পারে না, কারণ order-এর একটাই copy আছে
Oversized subclassপ্রতিটা subclass তার genuine পার্থক্যে সঙ্কুচিত হয় — কয়েকটা ছোট step method
Lazy Class (প্রতিরোধ)নতুন variant হলো ছোট কিন্তু meaningful class: তাদের প্রতিটা লাইন real পার্থক্য

দ্রুত revision box 📦

+------------------------------------------------------------------+
|        FORM TEMPLATE METHOD - REVISION CARD                      |
+------------------------------------------------------------------+
| Problem  : sibling subclasses run the SAME STEPS in the SAME     |
|            ORDER, differing only in step details.                |
|            (Two biryani recipes, four identical steps.)          |
|                                                                  |
| Solution : 1. extract each differing part into a method with     |
|               the SAME NAME in every subclass                    |
|            2. when skeletons are textually identical,            |
|               PULL UP the method -> the TEMPLATE METHOD          |
|            3. declare varying steps abstract on the parent       |
|            4. optional steps become HOOKS (default body)         |
|                                                                  |
| Produces : the Template Method design pattern                    |
|            (Hollywood principle: don't call us, we call you)     |
| Rule     : skeletons must MATCH EXACTLY before pulling up        |
| Not for  : differing step orders, or steps that must swap        |
|            at runtime (use Strategy / delegation instead)        |
+------------------------------------------------------------------+

অনুশীলন ✏️

তোমার পালা। ধরো একটা e-commerce backend দুটো courier-এর মাধ্যমে order ship করে, আর নিচের দুটো method ইতিমধ্যে একবার drift করেছে (খুঁজে বের করো):

class BlueDartShipper {
  ship(order: Order): Label {
    if (!order.address.pincode) throw new Error("Pincode required");
    const weight = order.items.reduce((w, i) => w + i.weightKg, 0);
    const cost = 50 + weight * 12;                 // BlueDart pricing
    const tracking = `BD-${order.id}-${Date.now()}`; // BlueDart format
    audit.log(`Shipped ${order.id} via BlueDart`);
    return new Label(order.address, cost, tracking);
  }
}
 
class DelhiveryShipper {
  ship(order: Order): Label {
    if (!order.address.pincode) throw new Error("Pincode required");
    const weight = order.items.reduce((w, i) => w + i.weightKg, 0);
    const cost = weight <= 1 ? 40 : 40 + (weight - 1) * 15; // Delhivery pricing
    const tracking = `DLV${order.id}X${order.address.pincode}`; // Delhivery format
    return new Label(order.address, cost, tracking);
    // note: no audit.log here — drift or design?
  }
}

এর মধ্য দিয়ে কাজ করো:

  1. প্রতিটা লাইন shared অথবা varying চিহ্নিত করো। তোমার ঠিক দুটো genuinely varying step (pricing আর tracking format) আর একটা সন্দেহজনক পার্থক্য (missing audit log) পাওয়া উচিত। সিদ্ধান্ত নাও: missing log কি accidental drift যেটা repair করতে হবে, নাকি real পার্থক্য যেটা hook হিসেবে preserve করতে হবে?
  2. উভয় class-এ identical signature সহ calculateCost(weight) আর trackingCode(order) extract করো। প্রতিটা extraction-এর পরে compile আর test করো।
  3. দুটো ship body diff করো। Character for character match করলে, একটা Shipper superclass তৈরি করো আর ship কে template method হিসেবে pull up করো।
  4. দুটো varying step protected abstract declare করো। Audit log real পার্থক্য হলে, empty default সহ একটা afterShip(order) hook যোগ করো আর BlueDartShipper-এ override করো।
  5. Payoff prove করো: flat Rs. 60 pricing আর tracking format EK/<id> সহ একটা EkartShipper যোগ করো। তুমি কতটা লিখলে গণনা করো — দশের নিচে হওয়া উচিত, যেমন চিত্র ৮ প্রতিশ্রুতি দিয়েছিল — আর confirm করো wiring-up ছাড়া কোনো existing file edit করোনি।
  6. তোমার Shipper-এর চিত্র ৬-এর মতো sequence diagram sketch করো: কোন call গুলো template থেকে নিচে যায়, আর কোন results উপরে আসে? কোনো arrow subclass থেকে template-এর algorithm-এর দিকে গেলে, Hollywood principle ভেঙেছ — fix করো।
  7. Bonus চিন্তা: business এখন চায় checkout সময় প্রতিটা order-এ courier বেছে নেওয়া, আর একটা "rate comparison" screen যেটা এক order-এ সব pricing rule চালায়। Template method এখনো fit করে, নাকি variation সবে swappable object হওয়ার দাবি করছে? এক বাক্যে বলো — আর সেই বাক্যটাই পরের lesson-এর দরজা, Replace Inheritance with Delegation

তোমার ধাপ ১-এর উত্তর যদি "missing audit log হলো ঠিক কীভাবে invisible structural duplication drift ঘটায় — fix করো নাহলে নাম দাও, কখনো accidental রেখো না," তাহলে তুমি এই পুরো lesson-এর গভীরতম বিষয়টা বুঝেছ। ফাতিমা একটা correction ছাড়াই তোমার রেসিপি কার্ড print করত। শাবাশ।

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

Form Template Method আর শুধু Pull Up Method ব্যবহারের মধ্যে পার্থক্য কী?
Pull Up Method তখন কাজ করে যখন দুটো subclass method হুবহু একই — পুরো body-টা parent-এ তুলে দেওয়া যায়। Form Template Method আরও কঠিন কেসটা handle করে, যেখানে method দুটো শুধু structure-এর দিক থেকে একই: step-এর order একই, কিন্তু step-এর details আলাদা। আগে method দুটোকে reshape করতে হয় যেন skeleton মিলে যায়, তারপর skeleton pull up করো, আলাদা step গুলো subclass-এ রেখে দাও overridable operation হিসেবে।
Form Template Method আর Template Method design pattern কি একই জিনিস?
এই দুটো একই বিল্ডিং-এ যাওয়ার দুটো আলাদা রাস্তা। Template Method pattern হলো গন্তব্য — একটা superclass method যেটা algorithm-এর skeleton define করে, আর subclass গুলো কিছু step পূরণ করে। Form Template Method হলো সেই রিফ্যাক্টরিং রাস্তা যেটা existing duplicated code থেকে শুরু করে সেই গন্তব্যে পৌঁছায়, up-front design না করে।
Hook method কী? Abstract step-এর বদলে কখন সেটা ব্যবহার করব?
Abstract step মানে প্রতিটা subclass-কে বাধ্য করা implementation দিতে। Hook হলো এমন একটা step যার একটা sensible default আছে — অনেক সময় empty body — subclass চাইলে override করতে পারে, কিন্তু বাধ্য নয়। যেসব parts-এ প্রতিটা variant সত্যিই আলাদা সেখানে abstract step ব্যবহার করো। আর optional extension point-এ hook ব্যবহার করো যেখানে শুধু কিছু variant-এর দরকার।
আমার দুটো method-এ step গুলো মিলে, কিন্তু order আলাদা — তখন কী করব?
তাহলে থামো — এই রিফ্যাক্টরিং এখানে লাগবে না। Template method-এর পুরো মূল্যটাই হলো step-এর order সব subclass-এ genuinely invariant থাকে। ভিন্ন order-এর দুটো procedure-কে এক skeleton-এ জোর করে ঢোকালে দুটোই নষ্ট হয়ে যায়। হয় আলাদাভাবে refactor করো, নাহলে এমন ছোট shared অংশ খোঁজো যার order সত্যিই fixed।
Template method-এর বদলে Strategy pattern কখন ব্যবহার করব?
যখন varying step গুলো runtime-এ বদলাতে হবে, স্বাধীনভাবে combine করতে হবে, বা আলাদাভাবে test করতে হবে। Template method compile time-এ variation-কে class hierarchy-তে আটকে দেয়। Live object-এ step swap করতে হলে সেই step-কে আলাদা object হিসেবে রাখো — এটাই Strategy, যেটায় Replace Inheritance with Delegation দিয়ে পৌঁছানো যায়।

আরো দেখো

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

Template Method Pattern: চা আর কফি, একই ধাপ

চায়ের দোকানের গল্প দিয়ে Template Method design pattern শেখো — সহজ TypeScript আর C# কোড, hooks, diagram, বাস্তব উদাহরণ আর practice task সহ।

আরও পড়ুন

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

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

আরও পড়ুন

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

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

আরও পড়ুন

Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো

Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।

আরও পড়ুন