Template Method Pattern: চা আর কফি, একই ধাপ
চায়ের দোকানের গল্প দিয়ে Template Method design pattern শেখো — সহজ TypeScript আর C# কোড, hooks, diagram, বাস্তব উদাহরণ আর practice task সহ।
আম্মার রান্নাঘর: দাদুর জন্য চা, আব্বার জন্য কফি ☕
ভোরবেলা। আম্মা রান্নাঘরে আছেন, দুটো অর্ডার অপেক্ষা করছে। দাদু চান তার মশলা চা, আর আব্বা চান ফিল্টার কফি। বারো বছরের সুমাইয়া রান্নাঘরের টুলে বসে দেখছে, কারণ বিজ্ঞানের হোমওয়ার্কে বলেছে "একটা প্রক্রিয়া দেখো আর তার ধাপগুলো লেখো"।
আম্মাকে মনোযোগ দিয়ে দেখো। চায়ের জন্য তিনি করেন:
- পাতিলে পানি ফুটাও।
- চা পাতা, থেঁতলানো আদা আর এলাচ দাও, ফুটতে দাও।
- দাদুর স্টিলের গ্লাসে ঢালো।
- দুধ আর দুই চামচ চিনি দাও।
কফির জন্য তিনি করেন:
- পানি ফুটাও।
- ফিল্টারে কফি পাউডারের উপর গরম পানি ঢালো, ড্রপ হতে দাও।
- আব্বার কাপে ঢালো।
- গরম ফেনা দেওয়া দুধ আর এক চামচ চিনি দাও।
সুমাইয়া দুটো তালিকা খাতায় পাশাপাশি লেখে। হঠাৎ একটা জিনিস চোখে পড়ে। ধাপ ১ আর ৩ হুবহু একই। ধাপ ২ আর ৪ আলাদা, কিন্তু দুটো রেসিপিতেই একই জায়গায় বসে আছে। আম্মা কখনো ফুটানোর আগে ঢালেন না। কখনো brew করার আগে দুধ দেন না। ক্রমটা নির্ধারিত — শুধু দুটো জায়গার বিষয়বস্তু বদলায়।
"আম্মা," সে বলে, "তুমি আসলে দুটো পানীয় বানাচ্ছো না। তুমি দুটো খালি জায়গাসহ একটাই রেসিপি চালাচ্ছো।"
আম্মা যদি তার সকালের রুটিন রেসিপি কার্ড হিসেবে লিখতেন, সেটা হতো এরকম:
পানি ফুটাও → brew করো (তোমার মতো) → কাপে ঢালো → extra যোগ করো (তোমার মতো)
একটা কার্ড, দুটো পানীয়। কাল যদি রাহেলা গ্রিন টি চায়, একই কার্ড কাজ করবে — শুধু "brew" জায়গাটা বদলাবে। আর যখন জামাল চাচা আসেন আর কড়া ব্ল্যাক কফি চান কিছু ছাড়া, কার্ডটা তখনও কাজ করে — "extras" জায়গাটা শুধু বাদ দেওয়া হয়।
এই রেসিপি কার্ডটাই Template Method pattern। নির্ধারিত কার্ড হলো template। খালি জায়গাগুলো হলো সেই ধাপ যা subclass পূরণ করে। পুরো পোস্ট জুড়ে আম্মার রান্নাঘর মাথায় রাখো — আমরা এটা লাইন বাই লাইন code করব।
এখানে সুমাইয়ার পর্যবেক্ষণ শিট, সকালের যাত্রা হিসেবে আঁকা:
Template Method pattern কী? 🧠
Template Method একটা behavioral design pattern যা inheritance-এর উপর দাঁড়িয়ে। সহজ সংজ্ঞা হলো: একটা base class একটা algorithm-এর skeleton একটাই method-এ — template method — নির্ধারিত ধাপের ক্রমে লিখে রাখে। কিছু ধাপ সেখানেই base class-এ implement করা হয় কারণ সেগুলো কখনো বদলায় না (পানি ফুটানো)। অন্য ধাপগুলো abstract ঘোষণা করা হয়, আর প্রতিটি subclass-কে সেগুলো অবশ্যই সরবরাহ করতে হবে (brew)। তৃতীয় ধরনকে বলে hooks — এর একটা default body আছে, subclass চাইলে override করতে পারে (তুমি কি আদৌ extras চাও?)।
গুরুত্বপূর্ণ প্রতিশ্রুতি এটা: subclass একটা ধাপ কী করে তা বদলাতে পারে, কিন্তু কখন হয় তা কখনো না। ক্রমটা একা base class-এর। ভালো implementation-এ template method-কে final করা হয় (বা টিম রুলে কখনো override না করার নিয়ম) যাতে কেউ ধাপগুলো এলোমেলো করতে না পারে।
এটা control-এর স্বাভাবিক দিক উল্টে দেয়। সাধারণত তোমার code library code ডাকে। এখানে, base class সঠিক মুহূর্তে subclass-কে ডাকে — "এখন আমাকে বলো তুমি কীভাবে brew করো"। এটা framework-এ এতটাই সাধারণ যে একটা ডাকনাম আছে: Hollywood Principle — "Don't call us, we'll call you."
এক লাইনে: Template Method = parent পুরো রেসিপি পাথরে লিখে রাখে, আর প্রতিটি child-এর জন্য কয়েকটা লেবেলযুক্ত খালি জায়গা রেখে দেয় নিজের মতো পূরণ করার জন্য।
এই pattern-এর cast:
- Abstract class — template method (
prepare())-এর মালিক, shared ধাপ (boilWater), abstract ধাপ (brew), আর hooks (wantsExtras) ধরে রাখে। - Concrete subclass —
MasalaChai,FilterCoffee— abstract ধাপ implement করে, হয়তো hook override করে, আর template-এ কখনো হাত দেয় না।
কলেজ কর্নার — Hollywood Principle, সঠিকভাবে: তুমি যা দেখছো সেটা হলো inversion of control (IoC) — ধারণাটা যা একটা framework-কে library থেকে আলাদা করে। Library-এর ক্ষেত্রে, তোমার code মূল flow ধরে রাখে আর দরকারে library ডাকে। Framework-এর ক্ষেত্রে, framework মূল flow ধরে রাখে আর নির্ধারিত extension point-এ তোমার code ডাকে। Template Method হলো ক্ষুদ্র আকারে IoC: prepare() হলো framework-এর মূল flow; brew() হলো তোমার extension point। পরে যখন বড় IoC দেখবে — Spring তোমার bean ডাকছে, JUnit তোমার setUp ডাকছে, Android তোমার onCreate ডাকছে — চিনে নেবে এটা একই রান্নাঘর, শুধু আকারে বড়। Base class হলো আম্মা; তুমি শুধু বলতে পারো brewing কীভাবে হবে, কখন হবে তা কখনো না।
এটা কোন সমস্যা সমাধান করে 😵
ধরো সুমাইয়া pattern ছাড়াই রান্নাঘর code করল। দুটো আলাদা class, প্রতিটিতে নিজের পুরো রেসিপি:
// BAD CODE: two classes, one copied skeleton
class MasalaChai {
prepare(): void {
console.log("Boiling water to 100 degrees"); // copy 1
console.log("Boiling tea leaves, ginger and elaichi");
console.log("Pouring into the cup"); // copy 1
console.log("Adding milk and 2 spoons sugar");
}
}
class FilterCoffee {
prepare(): void {
console.log("Boiling water to 100 degrees"); // copy 2
console.log("Dripping decoction through the filter");
console.log("Pouring into the cup"); // copy 2
console.log("Adding hot frothy milk and 1 spoon sugar");
}
}প্রতিটিতে মাত্র চারটা লাইন, আর অর্ধেকই কপি। এখন কল্পনা করো বাস্তব importer, report generator, বা game level যেখানে ত্রিশ লাইনের skeleton। বিপদ চুপচাপ বাড়তে থাকে:
- একবার ঠিক করো, দুইবার ভুলে যাও। Shared অংশে একটা bug ("গ্রিন টির জন্য পানি ১০০ নয়, ৯৫ ডিগ্রি হওয়া উচিত") প্রতিটি কপিতে ঠিক করতে হবে। একটা class মিস করলে রেসিপিগুলো নিরবে আলাদা হয়ে যায়।
- নতুন requirement ব্যথা বাড়ায়। "ফুটানোর আগে পাতিল ধুয়ে নাও" — এখন এই লাইন চা, কফি, গ্রিন টি, বাদাম দুধ... প্রতিটি class-এ, একই edit, দশবার।
- Shared shape কোথাও নেই। code-এর কোনো একটা জায়গায় বলা নেই "প্রতিটি গরম পানীয় boil → brew → pour → extras অনুসরণ করে।" নতুন programmer-কে একটা কপি থেকে রেসিপি বের করতে হবে আর আশা করতে হবে বাকিগুলো মিলবে।
- ক্রম ভুল হয়। নতুন
GreenTeaclass-কে brew করার আগে ঢালতে কিছু বাধা দেয় না। Skeleton একটা convention, নিয়ম নয়।
পানীয়ের মেনু বাড়ার সাথে duplicate লাইনগুলো গণনা করো। কপি দিয়ে, প্রতিটি নতুন পানীয় পুরো skeleton আবার paste করে। Template দিয়ে, প্রতিটি নতুন পানীয় শুধু তার দুটো খালি জায়গা লেখে:
Structure shared। ধাপগুলো আলাদা। Template Method code-কে এই পার্থক্যটা জোরে বলতে বাধ্য করে।
এটা কীভাবে কাজ করে, ধাপে ধাপে 🛠️
রেসিপি লেখার রেসিপি এই রকম:
- Algorithm-কে নামযুক্ত ধাপে ভাঙো। Boil, brew, pour, extras যোগ করো। নির্ধারিত ক্রম না পেলে এই pattern প্রযোজ্য না।
- ধাপগুলো তিনটা বালতিতে ভাগ করো। সবার জন্য একই → base class-এ concrete method। সবার জন্য আলাদা → abstract method। ঐচ্ছিক বা যুক্তিসঙ্গত default আছে → hook।
- Template method লেখো abstract base class-এ। এটা নির্ধারিত ক্রমে ধাপগুলো ডাকে। Final করো (বা টিম রুলে কখনো override না করার নিয়ম)।
- Shared ধাপ implement করো base class-এ — একবার, সবার জন্য।
- Varying ধাপ abstract ঘোষণা করো যাতে compiler প্রতিটি subclass-কে সেগুলো পূরণ করতে বাধ্য করে।
- দরকারী জায়গায় hook যোগ করো। Empty-body hooks (
beforeServing()) subclass-কে extra behavior যোগ করতে দেয়; boolean hooks (wantsExtras()) ঐচ্ছিক অংশ চালু-বন্ধ করতে দেয়। - Subclass লেখো। প্রতিটি শুধু তার খালি জায়গা পূরণ করে। কেউই template-এ হাত দেয় না।
এই পার্থক্য table-টা মাথায় গেঁথে রাখো — pattern-এর সবচেয়ে বেশি জিজ্ঞেস করা বিষয় এটা:
| ধাপের ধরন | Body কোথায়? | Subclass-কে কি implement করতে হবে? | উদাহরণ |
|---|---|---|---|
| Shared step | Base class, পূর্ণ body | না — সরাসরি inherited | boilWater() |
| Abstract step | কোথাও নেই — শুধু declaration | ✅ হ্যাঁ, বাধ্যতামূলক | brew() |
| Hook (extension) | Base class, empty body | ঐচ্ছিক | beforeServing() |
| Hook (flow control) | Base class, default return | ঐচ্ছিক | wantsExtras(): true |
আর এখানে রেসিপিটা flow হিসেবে — নির্ধারিত spine আর একমাত্র fork হিসেবে hook:
বাস্তব code-এর উদাহরণ 💻
আম্মার রান্নাঘর, সম্পূর্ণ আর চলনযোগ্য। দুটো subclass একটা skeleton শেয়ার করছে (আর তৃতীয়টা hook দেখাতে):
// ---------- The abstract base class ----------
abstract class HotDrink {
// THE TEMPLATE METHOD.
// Fixed order. Subclasses never override this.
prepare(): void {
this.boilWater();
this.brew(); // blank slot 1 — compulsory
this.pourIntoCup();
if (this.wantsExtras()) { // hook controls the optional part
this.addExtras(); // blank slot 2 — compulsory
}
this.serve();
}
// ----- shared steps: written ONCE for every drink -----
protected boilWater(): void {
console.log("Boiling water in the pan");
}
protected pourIntoCup(): void {
console.log("Pouring into the cup");
}
protected serve(): void {
console.log("Ready! Serve hot.\n");
}
// ----- abstract steps: every subclass MUST fill these -----
protected abstract brew(): void;
protected abstract addExtras(): void;
// ----- hook: subclasses MAY override; default says yes -----
protected wantsExtras(): boolean {
return true;
}
}
// ---------- Subclass 1: Dadi's chai ----------
class MasalaChai extends HotDrink {
protected brew(): void {
console.log("Boiling tea leaves with ginger and elaichi");
}
protected addExtras(): void {
console.log("Adding milk and 2 spoons of sugar");
}
}
// ---------- Subclass 2: Appa's filter coffee ----------
class FilterCoffee extends HotDrink {
protected brew(): void {
console.log("Dripping decoction through the brass filter");
}
protected addExtras(): void {
console.log("Adding hot frothy milk and 1 spoon of sugar");
}
}
// ---------- Subclass 3: Uncle's strict black coffee ----------
class BlackCoffee extends HotDrink {
protected brew(): void {
console.log("Brewing strong black coffee");
}
protected addExtras(): void {
/* never called — see the hook below */
}
protected wantsExtras(): boolean {
return false; // hook overridden: skip extras entirely
}
}
// ---------- The morning run ----------
console.log("--- Dadi's order ---");
new MasalaChai().prepare();
console.log("--- Appa's order ---");
new FilterCoffee().prepare();
console.log("--- Uncle's order ---");
new BlackCoffee().prepare();আউটপুট:
--- Dadi's order ---
Boiling water in the pan
Boiling tea leaves with ginger and elaichi
Pouring into the cup
Adding milk and 2 spoons of sugar
Ready! Serve hot.
--- Appa's order ---
Boiling water in the pan
Dripping decoction through the brass filter
Pouring into the cup
Adding hot frothy milk and 1 spoon of sugar
Ready! Serve hot.
--- Uncle's order ---
Boiling water in the pan
Brewing strong black coffee
Pouring into the cup
Ready! Serve hot.একটু ভাবো — শিক্ষকের মতো হোমওয়ার্ক চেক করার মতো পড়ো:
- প্রতিটি পানীয়ের ১ নং আর ৩ নং লাইন হুবহু একই — সেগুলো base class থেকে আসে, একবার লেখা।
- Brew লাইন আর extras লাইন প্রতিটি পানীয়ে আলাদা — subclass সরবরাহ করেছে।
- জামাল চাচার black coffee extras লাইন সম্পূর্ণ বাদ দিয়েছে —
wantsExtras()hookfalseফেরত দিয়েছে, আর template সেই ধাপ এড়িয়ে গেছে। - Client শুধু
prepare()ডেকেছে। কোন ধাপ shared, override করা, বা বাদ দেওয়া হয়েছে তা জানে না। রেসিপি একটা method, উপর থেকে নিচ পর্যন্ত পড়া যায়, একটাই জায়গায়।
এখন Hollywood Principle call-by-call দেখো। সুমাইয়া chai object-এ prepare() ডাকে — আর তারপর base class দায়িত্ব নেয় আর ঠিক সঠিক মুহূর্তে subclass-এ নামে:
আর যখন নতুন requirement আসে — "ফুটানোর পরে gas usage লগ করো" — base class-এ boilWater()-এর ভেতরে একটা লাইন যোগ করো। Chai, coffee, আর black coffee সব তাৎক্ষণিকভাবে পেয়ে যাবে। একবার ঠিক করো, সর্বত্র ঠিক।
আরও একটা দৃশ্য। প্রতিটি পানীয়, ধরন নির্বিশেষে, একই ক্রমে একই station-এ হাঁটে — শুধু দুটো station-এর ভেতরে কী হয় তা আলাদা:
C#-এ একই ধারণা 🗂️
ধরো আব্বা এমন একটা অফিসে কাজ করেন যেখানে একই pattern পরিণত বয়স্কদের পোশাকে দেখা যায়: report export করা। প্রতিটি exporter data fetch করে, format করে (varying step), আর file save করে।
abstract class ReportExporter
{
// The template method — sealed-by-convention, fixed order.
public void Export()
{
var rows = FetchRows(); // shared
string content = Format(rows); // varies — abstract
Save(content); // shared
AfterExport(); // hook — default does nothing
}
protected List<string> FetchRows() =>
new() { "Asha,92", "Bilal,88", "Chitra,95" };
protected void Save(string content) =>
Console.WriteLine($"Saving file:\n{content}\n");
protected abstract string Format(List<string> rows);
protected virtual void AfterExport() { } // empty hook
}
class CsvExporter : ReportExporter
{
protected override string Format(List<string> rows) =>
"name,marks\n" + string.Join("\n", rows);
}
class HtmlExporter : ReportExporter
{
protected override string Format(List<string> rows) =>
"<table>" + string.Concat(
rows.Select(r => $"<tr><td>{r.Replace(",", "</td><td>")}</td></tr>"))
+ "</table>";
protected override void AfterExport() =>
Console.WriteLine("Opening preview in browser...");
}
// Usage — same call, two skeleton-sharing exporters
new CsvExporter().Export();
new HtmlExporter().Export();Fetch আর save logic একবার বিদ্যমান। প্রতিটি exporter শুধু তার Format দেয়। HtmlExporter অতিরিক্তভাবে AfterExport hook ব্যবহার করে। দেখো hook কতটা স্বাভাবিক মনে হয়: CsvExporter এটার উল্লেখই করেনি।
Python-এ আরও একবার 🐍
Python abc দিয়ে একই ধারণা বলে। এখানে একটা ছোট file importer skeleton — open, parse (খালি জায়গা), validate (hook), save:
from abc import ABC, abstractmethod
class FileImporter(ABC):
def run(self, path): # THE TEMPLATE METHOD
data = self.open_file(path) # shared
rows = self.parse(data) # blank slot — compulsory
if self.should_validate(): # hook — default yes
rows = [r for r in rows if r]
self.save(rows) # shared
def open_file(self, path):
print(f"Opening {path}")
return "a,b;;c,d" # pretend file content
def save(self, rows):
print(f"Saved {len(rows)} rows")
def should_validate(self): # hook with default
return True
@abstractmethod
def parse(self, data): ...
class CsvImporter(FileImporter):
def parse(self, data):
return data.split(";") # naive csv blocks
class TrustedImporter(FileImporter):
def parse(self, data):
return data.split(";")
def should_validate(self): # hook overridden
return False # skip cleaning, trust the source
CsvImporter().run("marks.csv") # Saved 2 rows (empty block removed)
TrustedImporter().run("marks.csv") # Saved 3 rows (validation skipped)একই skeleton, তিনটা language। Pattern হলো আকার, syntax নয়।
বাস্তব software-এ এটা কোথায় দেখা যায় 🌍
Template Method চুপচাপ software জগতের বিশাল অংশ চালায়:
- Testing framework (ক্লাসিক)। JUnit, NUnit, pytest, MSTest সব তোমার test একটা নির্ধারিত lifecycle-এ চালায়: setUp → test চালাও → tearDown। Framework skeleton-এর মালিক; তুমি
setUp()/tearDown()(বা[SetUp]/[TearDown], fixture ইত্যাদি) লিখে slot পূরণ করো। কখন চলবে তা তুমি নিয়ন্ত্রণ করো না — framework তোমাকে ডাকে। খাঁটি Hollywood Principle। - সর্বত্র framework lifecycle hook। একটা Android
Activityনির্ধারিত lifecycle চালায় (onCreate → onStart → onResume → ...) আর তোমার app ধাপগুলো পূরণ করে। ASP.NET page/middleware lifecycle, game engine loop (Unity-তেAwake/Start/Update), আর React class component lifecycle method সব একই আকার অনুসরণ করে: framework-এর skeleton তোমার overridden ধাপ ডাকে। - Java-এর standard library।
java.io.InputStreammulti-byteread(byte[], off, len)implement করে একটা abstract single-byteread()-এর মাধ্যমে যা subclass সরবরাহ করে।AbstractList,AbstractMap, আর বন্ধুরা তুমি implement করা কয়েকটা abstract primitive-এর উপর ভিত্তি করে পুরো collection behavior সংজ্ঞায়িত করে। - Spring Framework template।
JdbcTemplateআর তার সাথীরা boring skeleton (connection খোলো, error handle করো, connection বন্ধ করো) fix করে আর তোমাকে শুধু interesting ধাপ (query আর row mapping) সরবরাহ করতে দেয়। Spring docs খোলাখুলিভাবে এই pattern family-কে credit দেয়। - Data pipeline আর importer। বাস্তব ETL codebase abstract importer সংজ্ঞায়িত করে — open, parse, validate, transform, write, report — যেখানে প্রতিটি file format শুধু
parseআরvalidateoverride করে। আমাদের Python উদাহরণ এরই একটা ছোট সংস্করণ। - পড়ার জন্য open-source code। iluwatar template-method উদাহরণ একটা সংক্ষিপ্ত, ভালো-comment করা Java sample, আর refactoring.guru-এর পেজ অনেক language-এ একই ধারণা বোঝায়।
কর্মরত developer-রা সত্যিকার অর্থে এই pattern কোথায় দেখে (সাধারণত নিজেরা না লিখেই) তা গণনা করলে ছবিটা এরকম:
কখন ব্যবহার করবে আর কখন না 🤔
| পরিস্থিতি | Template Method ব্যবহার করবে? |
|---|---|
| কয়েকটা class একই ক্রমে একই ধাপ করে সামান্য পার্থক্য সহ | ✅ হ্যাঁ — skeleton base class-এ তোলো |
| একটা ক্রম/invariant নিশ্চিত করতে হবে ("সবসময় শেষে file বন্ধ করো") | ✅ হ্যাঁ — final template এটা enforce করে |
| তুমি একটা framework লিখছো আর user-দের নির্ধারিত slot-এ plug in করতে দিতে চাও | ✅ হ্যাঁ — এটা framework pattern |
| বেশিরভাগ algorithm shared, শুধু ১-৩টা ধাপ ভিন্ন | ✅ হ্যাঁ — এটাই সবচেয়ে উপযুক্ত জায়গা |
| Variant-গুলোর ধাপের ভিন্ন ক্রম দরকার | ❌ না — fixed skeleton এটা মানতে পারে না |
| Runtime-এ behavior বদলাতে হবে | ❌ না — Strategy (composition) ব্যবহার করো |
| Variant-গুলোর মধ্যে সত্যিকার কোনো shared অংশ নেই | ❌ না — common base মিথ্যা হবে |
| তোমার language/team inheritance এড়িয়ে চলে | ❌ Composition পছন্দ করো: step function parameter হিসেবে পাঠাও |
যখন Template Method আর Strategy-এর মধ্যে দ্বিধায় পড়ো, তোমার সমস্যা এই মানচিত্রে রাখো। দুটো প্রশ্ন: algorithm-এর কতটুকু বদলায়, আর কখন variation বেছে নিতে হয়?
চা বনাম কফি গভীর Template Method এলাকায়: ক্ষুদ্র variation (দুটো slot), কোন class instantiate করবে সেটা বেছে নেওয়ার মুহূর্তে নির্ধারিত। Payment mode Strategy এলাকায়: পুরো flow আলাদা আর user runtime-এ বেছে নেয়।
ছাত্রছাত্রীরা যে সাধারণ ভুল করে 🚧
এক নম্বর ভুল: subclass-কে template method নিজেই override করতে দেওয়া। যেই মুহূর্তে MasalaChai prepare() পুনরায় সংজ্ঞায়িত করে, গ্যারান্টি চলে যায় — এটা ফুটানো বাদ দিতে পারে বা আগে ঢালতে পারে, আর "একটা কর্তৃত্বপূর্ণ রেসিপি" প্রতিশ্রুতি ভেঙে পড়ে। Language অনুমতি দিলে template final/sealed করো, অথবা code review-এ কঠোরভাবে enforce করো।
আরও কিছু ফাঁদ এড়াও:
- অনেক বেশি abstract step। Base class আটটা abstract method ঘোষণা করলে, প্রতিটি subclass আটটা body লিখতে বাধ্য — এমনকি তুচ্ছগুলোও। এটা subclass-কে boilerplate দিয়ে শাস্তি দেয়। বাধ্যতামূলক ধাপ সর্বনিম্ন রাখো; বাকিগুলো default সহ hook বানাও।
- Hook যা গোপনে বাধ্যতামূলক হয়ে গেছে। প্রতিটি subclass যদি একটা "hook" override করে, তাহলে এটা কখনো ঐচ্ছিক ছিল না — এটাকে abstract step-এ পরিণত করো যাতে compiler enforce করে।
- Base class-এর বিশ্বাস ভাঙা (Liskov violation)। Template তার ধাপ বিশ্বাস করে। একটা
brew()যা exception throw করে, garbage ফেরত দেয়, বা গোপনে cup ঢেলে দেয় — সে ভেতর থেকে পুরো রেসিপি নষ্ট করে। একটা ধাপের কাজ তার slot-এর কাজ করা — এর বেশি কিছু নয়। - Template-এর বাইরে থেকে ধাপ ডাকা। Client code যদি সরাসরি
drink.brew()ডাকতে শুরু করে, সাবধানে নির্ধারিত ক্রম bypass হয়। Step method-গুলোprotectedরাখো যাতে শুধু রেসিপি সেগুলো চালাতে পারে। - ক্রম ভিন্ন হলে pattern জোর করা।
GreenTea-এর pour-then-brew দরকার হলে, double hook আর flag দিয়ে template বিকৃত করো না। Pattern-এর পুরো মূল্য হলো নির্ধারিত ক্রম — ক্রম নিজেই ভিন্ন হলে Strategy বা plain composition বেছে নাও।
পাঠকদের সাহায্য করা একটা naming অভ্যাস: template method-এর নাম দাও পুরো কাজের পরে (prepare, export, runTest) আর ধাপের নাম দাও slot-এর পরে (brew, format, setUp)। তখন যে কেউ template পড়লে পুরো algorithm একটা সুন্দর সুচিপত্র হিসেবে দেখতে পাবে।
চাচাতো ভাইদের সাথে তুলনা 👯
| Pattern | কী বদলায়? | Mechanism | কখন নির্ধারিত? | সাধারণ ব্যবহার |
|---|---|---|---|---|
| Template Method | একটা algorithm-এর পৃথক ধাপ | Inheritance — subclass override করে | Compile time (subclass বেছে নিয়ে) | Framework, importer, test runner |
| Strategy | পুরো algorithm | Composition — context একটা object ধরে | Runtime (object swap করো) | Payment mode, comparator |
| Factory Method | শুধু object-creation ধাপ | Inheritance | Compile time | Template-এর ভেতরে, প্রায়ই |
| Builder | Director দিয়ে construction ধাপ | Composition | Runtime | Multi-part object assembly |
মূল তুলনা হলো Template Method বনাম Strategy — ক্লাসিক inheritance বনাম composition দ্বন্দ্ব:
- Template Method বলে: "আমার থেকে inherit করো। আমি skeleton fix করি; তুমি চিহ্নিত ধাপ override করো।" Variation compile time-এ বেছে নেওয়া — একবার কোনো object
FilterCoffeeহলে, এটা সেভাবেই brew করে সবসময়। Variation-এর দানা প্রতি ধাপে। - Strategy বলে: "আমার reference ধরো। যখন খুশি swap করো।" Variation runtime-এ বেছে নেওয়া, আর দানা হলো পুরো algorithm। কোনো inheritance chain নেই, single-base-class limit নেই।
মোটামুটি গাইড: নির্ধারিত shared ক্রম যেখানে ছোট overridable slot → Template Method। Mid-program behavior বদলাতে হবে, বা inheritance এড়াতে চাও → Strategy। দুটো এমনকি সহযোগিতাও করে: template method-এর একটা ধাপ একটা strategy object-এ delegate করতে পারে।
কলেজ কর্নার: exam-এর জন্য মনে রাখার মতো আরেকটা সম্পর্ক: Factory Method হলো একটা specialized Template Method। একটা বড় template-এর ভেতরে "object তৈরি করো" ধাপটা নিজেই একটা factory method — subclass সিদ্ধান্ত নেয় রেসিপি কোন product নিয়ে কাজ করবে। আর একটা design-review প্রশ্ন নিজেকে জিজ্ঞেস করার মতো: যেহেতু Template Method variant-কে সবসময়ের জন্য base class-এর সাথে যুক্ত করে (inheritance হলো সবচেয়ে শক্তিশালী coupling), আধুনিক codebase প্রায়ই পরিণত template-গুলো Strategy আকারে refactor করে — skeleton একটা plain function হয়ে যায় যা তার ধাপ parameter হিসেবে গ্রহণ করে। Functional programmer-রা সেই আকারকে higher-order function বলে; GoF বলত composition দিয়ে পুনর্নির্মিত Template Method। উভয় spelling জানা, আর কোনটি কখন মূল্য রাখে তা জানা, আসল দক্ষতা।
পুরো pattern এক পাতায় 🗺️
এই mind map স্মৃতি থেকে আঁকতে পারলে, তুমি pattern-এর মালিক:
দ্রুত পুনরালোচনা বাক্স 📦
+--------------------------------------------------------------+
| TEMPLATE METHOD PATTERN — REVISION |
+--------------------------------------------------------------+
| WHAT : Base class fixes the algorithm skeleton in ONE |
| method; subclasses fill marked step slots |
| ACTORS : Abstract class (template + steps + hooks) |
| + concrete subclasses |
| STEPS : shared (base body) | abstract (must fill) |
| | hook (may override, has default) |
| KEY RULE : Subclass changes WHAT a step does, never WHEN |
| NICKNAME : Hollywood Principle — don't call us, we call you |
| KILLS : Copy-pasted skeletons drifting apart |
| AVOID IF : Order of steps differs per variant, or you need |
| runtime swapping (then: Strategy) |
| EXAMPLES : JUnit setUp/tearDown, Android lifecycle, |
| InputStream.read, JdbcTemplate, importers |
+--------------------------------------------------------------+Practice exercise ✍️
এগুলো নিজে তৈরি করো — type না করলে pattern আটকাবে না:
- সকালের assembly template। ধরো প্রতিটি স্কুল assembly অনুসরণ করে: লাইন ধরো → প্রার্থনা → বিশেষ আইটেম (ভিন্ন) → ঘোষণা → চলে যাও। একটা abstract
Assemblyclass এই template দিয়ে লেখো, তারপরMondayAssembly(বিশেষ আইটেম: শপথ),FridayAssembly(বিশেষ আইটেম: ছাত্র প্রতিভা শো), আর একটাhasAnnouncements()hook যাSaturdayAssemblyfalse-এ override করে। প্রতিটি দিনের জন্য পুরো flow print করো। - পরীক্ষার প্রশ্নপত্র generator। একটা abstract
ExamPaperচালায়: header print → নির্দেশনা print → প্রশ্ন print (abstract) → নম্বর table print।MathsPaperআরHistoryPaperতৈরি করো। তারপর একটাneedsGraphSheet()hook যোগ করো যা শুধুMathsPaperচালু করে, template-কে সঠিক মুহূর্তে "গ্রাফ শিট সংযুক্ত করুন" print করায়। - Refactor hunt (চিন্তার কাজ)। তোমার লেখা যেকোনো project খোলো যেখানে দুটো class চাচাতো ভাইয়ের মতো — দুটো parser, দুটো screen, দুটো report writer। তাদের method পাশাপাশি রাখো আর একই লাইনে underline করো। অর্ধেকের বেশি মিললে, (কাগজে) abstract base class স্কেচ করো: কোন লাইন template হবে, কোন লাইন abstract step হবে, আর কোনগুলো hook-এর যোগ্য?
- কলেজ stretch। Exercise 1 থেকে তোমার
Assemblytemplate নাও আর inheritance ছাড়াই rebuild করো: একটাrunAssembly(specialItem, hasAnnouncements)function যা varying step-কে function argument হিসেবে গ্রহণ করে। তিনটা বাক্যে দুটো design তুলনা করো: কোনটা test করা সহজ, কোনটা আবিষ্কার করা সহজ, আর Sports Day-তে ক্রম নিজেই বদলানোর requirement আসলে কোনটা টিকে থাকবে?
সচরাচর জিজ্ঞাসা
- Template Method pattern এক লাইনে কী?
- একটা base class একটা algorithm-এর পুরো রেসিপি একটাই method-এ নির্দিষ্ট ক্রমে লিখে রাখে, আর কয়েকটা ধাপ খালি রেখে দেয়। Subclass শুধু সেই খালি ধাপগুলো পূরণ করে — রেসিপির ক্রম বদলানো বা কোনো ধাপ বাদ দেওয়ার কোনো সুযোগ নেই।
- abstract step আর hook-এর পার্থক্য কী?
- abstract step প্রতিটি subclass-কে বাধ্যতামূলকভাবে implement করতে হয় — brew-এর মতো, এটা ছাড়া রেসিপিই চলবে না। Hook override করা ঐচ্ছিক — এর একটা default body আছে, প্রায়ই খালি, যেমন ঐচ্ছিক add-toppings ধাপ। Abstract = বাধ্যতামূলক হোমওয়ার্ক; hook = ঐচ্ছিক বোনাস প্রশ্ন।
- Hollywood Principle কী?
- মানে হলো 'তুমি আমাদের ডেকো না, আমরা তোমাকে ডাকব'। Subclass নিজে নিয়ন্ত্রণ নেয় না। Base class রেসিপি চালায় আর সঠিক মুহূর্তে subclass-কে ডাকে। Subclass শুধু behavior দেয়; ক্রম কখনো নিয়ন্ত্রণ করে না।
- Template Method আর Strategy-এর পার্থক্য কী?
- Template Method একটা algorithm-এর STEPS বদলায় inheritance ব্যবহার করে, compile time-এ নির্ধারিত। Strategy পুরো ALGORITHM বদলে দেয় composition ব্যবহার করে, runtime-এ পরিবর্তনযোগ্য। Program চলার সময় behavior বদলাতে চাইলে Strategy ব্যবহার করো।
- কখন Template Method এড়িয়ে চলা উচিত?
- যখন variant-গুলোর ধাপের ক্রম আলাদা হওয়া দরকার — fixed skeleton এটা মানতে পারে না। এছাড়াও এড়িয়ে চলো যখন variant-গুলোর মধ্যে সত্যিকার অর্থে কোনো shared অংশ নেই, বা তোমার language style inheritance-এর বদলে composition আর plain function পছন্দ করে।
আরো দেখো
সম্পর্কিত পাঠ
Strategy Pattern: সাইকেল, বাস, নাকি অটো — তুমিই ঠিক করো
Strategy design pattern শেখো একটা সহজ স্কুলে যাওয়ার গল্পের মাধ্যমে — TypeScript আর C# কোড, runtime swapping, বাস্তব উদাহরণ, আর প্র্যাকটিস exercise সহ।
State Pattern: একটা object-এর মেজাজ বদলানোর গল্প
State design pattern শেখো সিলিং ফ্যানের রেগুলেটরের গল্প দিয়ে। সহজ TypeScript আর C# code, state diagram, আর real software-এর উদাহরণ সহ।
Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle
Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।
Visitor Pattern: যে ডাক্তার প্রতিটি class-এ যায়
স্কুলের health check-up গল্পের মাধ্যমে Visitor design pattern শেখো — double dispatch সহজ ভাষায়, TypeScript আর C# কোড, বাস্তব উদাহরণ, আর practice।