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

Command Pattern: প্রতিটি কাজকে একটি অর্ডার স্লিপে বদলে দাও

রেস্তোরাঁর অর্ডার স্লিপের গল্প দিয়ে Command pattern শেখো। TypeScript আর C#-এ undo ও redo সহ পুরো কোড, diagram, table, আর practice task।

22 মিনিট আপডেট: June 11, 2026beginner
design-patternsbehavioral-patternscommandundo-redotypescriptcsharpoop

ভাই, একটা ভুনা খিচুড়ি দাও!

ধরো রুবেল তার পরিবারকে নিয়ে শুক্রবার সন্ধ্যায় গেছে পুরান ঢাকার "হোটেল মদিনা"-য়। টেবিল ৪-এ বসলো সবাই। ওয়েটার করিম ভাই এলো হাসিমুখে, হাতে একটা ছোট্ট নোটপ্যাড। রুবেল বললো: "এক প্লেট ভুনা খিচুড়ি, দুটো পরোটা, আর একটা মিষ্টি লাচ্ছি।"

করিম কি সাথে সাথে রান্নাঘরে দৌড় দিল রান্না করতে? না! সে অর্ডারটা একটা স্লিপে লিখলো। স্লিপ ছিঁড়ে রান্নাঘরের জানালার রেলে ক্লিপ করে দিলো। ভেতরে রাঁধুনি ফাতেমা একটা একটা করে স্লিপ তুলে যা লেখা আছে তাই রান্না করছেন। তিনি রুবেলকে চেনেন না। রুবেল তাকে চেনে না। স্লিপটাই দুজনের সংযোগ।

একটু থামো। সেই ছোট্ট কাগজের স্লিপটা কিন্তু চুপচাপ একটা জাদু করছে:

  • রুবেল কখনো রাঁধুনির সাথে কথা বলে না। আজ রাতে কে রান্না করছেন — সেটা সে জানেও না।
  • করিম ভাই স্লিপ বহন করেন কিন্তু রান্না করতে পারেন না। রেসিপি বোঝারও দরকার নেই।
  • স্লিপটা সব ধরে রাখে: কোন আইটেম, কতটুকু, কোন টেবিল। এটা রেলে queue-তে অপেক্ষা করতে পারে। কোনো প্লেট ভেঙে গেলে ফাতেমা একই স্লিপ দেখে আবার রান্না করতে পারেন। রাতে দোকান বন্ধের সময় মালিক মি. জামাল সব স্লিপ গুনে দিনের হিসাব মেলান।
  • রাঁধুনি ফাতেমা স্লিপ পড়ে আসল কাজটা করেন।

আরও একটা ব্যাপার — সন্ধ্যার মাঝখানে রুবেলের ছোট বোন মন পাল্টালো: "ভাই, লাচ্ছিটা বাদ!" করিম ভাই সেই স্লিপটা রেল থেকে টেনে নিলেন। অর্ডার undo হয়ে গেলো, রান্না হওয়ার আগেই। একটু পরে সে আবার মন পাল্টালো, আর করিম ভাই একই স্লিপটা আবার ক্লিপ করলেন। অর্ডার redo হলো। কোনো ঝামেলা নেই, নতুন করে লেখার দরকার নেই — স্লিপ তো আগে থেকেই আছে।

সেই অর্ডার স্লিপটাই হলো Command pattern। একটা request, object হিসেবে লেখা, যেটা ভ্রমণ করতে পারে, অপেক্ষা করতে পারে, আবার হতে পারে, এমনকি বাতিলও হতে পারে। এই রেস্তোরাঁর গল্পটা মাথায় রাখো — নিচের প্রতিটি section রুবেলের টেবিল ৪ follow করে।

সন্ধ্যার পুরো ঘটনাটা এক নজরে:

চিত্র ১: টেবিল ৪ থেকে রান্নাঘর পর্যন্ত অর্ডার স্লিপের পথ

আর একই সন্ধ্যাটা journey হিসেবে:

চিত্র ২: হোটেল মদিনায় একটা অর্ডার স্লিপের পুরো যাত্রা

Command pattern আসলে কী?

Command হলো Gang of Four (GoF) বইয়ের (১৯৯৪) একটা behavioral design pattern। এটাকে Action বা Transaction-ও বলা হয়।

রিভিশনের জন্য এই definition মাথায় রাখো:

Command pattern একটা request-কে standalone object-এ পরিণত করে। Object-টা request সম্পর্কে সব কিছু store করে — receiver, operation, আর parameters — একটা ছোট্ট interface-এর পেছনে। সাধারণত একটাই execute() method থাকে, আর প্রায়ই undo() method-ও।

একটা request object হয়ে গেলে, plain method call দিয়ে যা অসম্ভব ছিল তা করা যায়:

  • Store করো variable বা list-এ, রেলে স্লিপের মতো।
  • Queue করো পরে চালানোর জন্য, বা অন্য thread-এ, স্লিপের পালার অপেক্ষার মতো।
  • Log করো disk-এ, যাতে crash হওয়া program তার history replay করতে পারে — রাতে মি. জামালের স্লিপ গোনার মতো।
  • Undo করো, কারণ object নিজেই জানে কীভাবে নিজেকে উল্টাতে হবে — রেল থেকে স্লিপ টেনে নেওয়ার মতো।

Pattern-এর পাঁচটা role আছে। রেস্তোরাঁর সাথে মিলিয়ে দেখো:

Roleকাজরেস্তোরাঁ সংস্করণ
Commandexecute() (আর undo()) সহ Interface"অর্ডার স্লিপ" ধারণাটা
ConcreteCommandএকটা আসল request; receiver + details store করেপূরণ করা স্লিপ: "১টা খিচুড়ি, টেবিল ৪"
InvokerCommand trigger করে; কখনো রান্না করে নাকরিম ভাই আর রান্নাঘরের রেল
Receiverআসল কাজটা করেরাঁধুনি ফাতেমা
ClientCommand তৈরি করে আর সব কিছু জোড়া লাগায়রুবেল, অর্ডার দেওয়া customer
💡

মনে রাখার shortcut: "চিৎকার না করে, লিখে দাও।" রাঁধুনিকে সরাসরি চিৎকার করে অর্ডার দেওয়ার (direct method call) বদলে একটা স্লিপ লেখো (command object)। লেখা যেকোনো কিছু অপেক্ষা করতে পারে, বারবার হতে পারে, গোনা যায়, আর ছিঁড়ে ফেলা যায়। এক লাইনে এই pattern-এর পুরো শক্তি এটাই।

পুরো pattern একটা mind map-এ ধরে যায়। পরীক্ষার আগে স্মৃতি থেকে এটা আবার আঁকো:

চিত্র ৩: এক নজরে Command pattern

এটা কোন সমস্যা সমাধান করে

Pattern ছাড়া কী কষ্ট হয় দেখা যাক। ধরো মি. জামাল তোমাকে রেস্তোরাঁর billing screen বানাতে বললেন। বাটন আছে: Add Item, Remove Item, Apply Discount। অলস পথ হলো প্রতিটা বাটনের ভেতরে সরাসরি কাজটা করে দেওয়া:

// ❌ Each button directly does the work — tight coupling everywhere
addItemButton.onClick = () => {
  bill.items.push({ name: "Paneer Butter Masala", price: 320 });
  screen.refresh();
};
 
removeItemButton.onClick = () => {
  bill.items.pop();
  screen.refresh();
};

দেখতে নিরীহ। কিন্তু সমস্যাগুলো একে একে আসতে শুরু করে, ইফতারের পরে ভিড়ের মতো:

১. Keyboard shortcut সমস্যা। মি. জামাল চাইলেন Ctrl+Z দিয়ে শেষ action undo হোক। কিন্তু action গুলো কোথাও record হয়নি! কাজ হয়েছে আর মিলিয়ে গেছে — চিৎকার করা অর্ডারের মতো যেটা কেউ লেখেনি। undo করার কিছু নেই। ২. Duplicate logic সমস্যা। একই "add item" এখন menu entry থেকেও, touchscreen থেকেও কাজ করতে হবে। তিনটা জায়গায় copy-paste করলে। একদিন এক জায়গায় bug ঠিক করলে বাকি দুটো ভুলে যাবে। ৩. Queue সমস্যা। Rush hour-এ অর্ডারগুলো queue-তে জমা রেখে একটা একটা করে চালাতে হবে। কিন্তু button click তো queue-তে রাখার জিনিস না — এটা সাথে সাথেই হয়ে যায়। ৪. History সমস্যা। মি. জামাল জিজ্ঞেস করলেন, "আজকে কী কী action নেওয়া হয়েছে দেখাও।" তোমার কাছে কোনো record নেই — action গুলো কখনো object ছিল না, শুধু method call ছিল যেগুলো হারিয়ে গেছে।

মূল সমস্যা হলো coupling: যেটা কাজ trigger করে (button) সেটা যেটা কাজ করে (bill) তার সাথে আটকে আছে। মাঝখানে কোনো layer নেই যেখানে request ধরে রাখা, store করা, বা উল্টানো যাবে। স্লিপ ছাড়া রেস্তোরাঁ মানে কাস্টমার সরাসরি রাঁধুনিকে চিৎকার করছে — রাত ৮টায় বিশৃঙ্খলা, রাত ১১টায় কোনো record নেই।

Command pattern সেই missing layer যোগ করে: প্রতিটা trigger একটা command object তৈরি বা ধারণ করে, আর শুধু command-ই receiver-এর সাথে কথা বলে। Invoker প্রতিটা executed command একটা history stack-এও push করে — আর হঠাৎ undo, logging, আর queue সহজ হয়ে যায়।

কীভাবে কাজ করে, ধাপে ধাপে

রেস্তোরাঁ ব্যবহার করে pattern টা একটু একটু করে তৈরি করা যাক।

ধাপ ১: Command interface declare করো। দুটো method: execute() আর undo()

ধাপ ২: Receiver চিহ্নিত করো। যে class আসল কাজটা জানে — আমাদের Kitchen (বা editor-এ Document)। কখনো business logic command-এ নিয়ে যেও না। ফাতেমা রান্না করেন; স্লিপ কখনো রান্না করে না। Command শুধু delegate করে।

ধাপ ৩: প্রতিটা request type-এর জন্য একটা concrete command লেখো। PrepareDishCommand, CancelDishCommand। প্রতিটা constructor-এ receiver আর parameters store করে, তাই এটা চালানোর জন্য পুরোপুরি প্রস্তুত — ফাঁকা স্লিপ না, পূরণ করা স্লিপ।

ধাপ ৪: Invoker তৈরি করো। আমাদের Waiter command ধরে, চালায়, আর একটা history stack রাখে। execute() এর পরে command-টা stack-এ push করো। Undo করতে pop করে undo() call করো।

ধাপ ৫: Client-এ wire করো। Kitchen তৈরি করো, তার চারপাশে command তৈরি করো, waiter-এর হাতে দাও।

একটা অর্ডার system-এর মধ্য দিয়ে কীভাবে যায় দেখো, message by message:

চিত্র ৪: customer থেকে রান্নাঘর পর্যন্ত একটা অর্ডার স্লিপের যাত্রা, তারপর undo

করিম ভাইয়ের code সুন্দরভাবে অজ্ঞ। সে যে স্লিপই ধরুক execute() call করে — খিচুড়ি, বিরিয়ানি, বা মিষ্টি — সব তার কাছে একই দেখায়। এই uniformity-টাই কারণ কেন একই invoker queue আর undo stack দুটোই চালাতে পারে।

Undo machinery চারটা ছোট নিয়ম মেনে চলে। শিখে নাও — interview-এ এগুলো প্রিয় প্রশ্ন:

execute a command        -> push it onto the UNDO stack
undo                     -> pop UNDO, call undo(), push onto REDO stack
redo                     -> pop REDO, call execute(), push onto UNDO stack
new command after undo   -> CLEAR the redo stack

redo stack কেন clear করতে হয়? ধরো রুবেল "add lassi" undo করলো, তারপর "add gulab jamun" অর্ডার করলো। এখন "add lassi" redo করলে এমন একটা future replay হবে যেটা আর অর্থবহ না — সন্ধ্যা এগিয়ে গেছে। Clear করলে history সৎ থাকে।

একটা single command নিজের ছোট্ট একটা জীবন কাটায়। এখানে সেটা states হিসেবে দেখা যাক:

চিত্র ৫: একটা command-এর জীবন, তৈরি থেকে undo আর redo পর্যন্ত

কলেজের কোণা: তুমি যে undo stack বানাতে যাচ্ছো সেটাই আসল editor-এর ভেতরের machinery। কিন্তু production system-এ কিছু সীমা আর কৌশল থাকে। Editor গুলো stack depth সীমাবদ্ধ করে — যেমন শেষ ২০০ command — যাতে memory নিয়ন্ত্রণে থাকে। ছোট command গুলো coalesce করে — "hello" টাইপ করলে পাঁচটা না, একটাই command হয়, তাই Ctrl+Z একটা অক্ষর না, পুরো শব্দটা মুছে ফেলে। আর দুটো undo style আলাদা করে: inverse-operation undo (command নিজের reverse জানে, memory-তে সস্তা) বনাম snapshot undo (execute করার আগে পুরানো state সংরক্ষণ করো — Memento pattern — ভারী কিন্তু কাজ করে যখন action-এর কোনো পরিষ্কার গাণিতিক বিপরীত নেই, যেমন "blur filter apply করো")। বেশিরভাগ বড় app দুটোই মিলিয়ে ব্যবহার করে।

বাস্তব কোড উদাহরণ

এখানে TypeScript-এ পুরো রেস্তোরাঁ — working undo আর redo সহ। Comments পড়ো; সেগুলোই গল্পটা বলে।

// ----- The RECEIVER: the kitchen does the real work -----
class Kitchen {
  private board: string[] = []; // dishes currently being prepared
 
  prepare(dish: string): void {
    this.board.push(dish);
    console.log(`Kitchen: started preparing ${dish}.`);
  }
 
  stopPreparing(dish: string): void {
    const index = this.board.lastIndexOf(dish);
    if (index !== -1) {
      this.board.splice(index, 1);
      console.log(`Kitchen: cancelled ${dish}.`);
    }
  }
 
  showBoard(): void {
    console.log(`Kitchen board: [${this.board.join(", ")}]`);
  }
}
 
// ----- The COMMAND interface: every order slip looks the same -----
interface Command {
  execute(): void;
  undo(): void;
}
 
// ----- A CONCRETE COMMAND: one filled order slip -----
class PrepareDishCommand implements Command {
  // The slip stores the receiver AND the parameters.
  constructor(
    private kitchen: Kitchen,
    private dish: string,
  ) {}
 
  execute(): void {
    this.kitchen.prepare(this.dish); // delegate to the receiver
  }
 
  undo(): void {
    this.kitchen.stopPreparing(this.dish); // the reverse action
  }
}
 
// ----- The INVOKER: the waiter runs slips and keeps history -----
class Waiter {
  private undoStack: Command[] = [];
  private redoStack: Command[] = [];
 
  place(command: Command): void {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = []; // a fresh action clears the redo future
  }
 
  undo(): void {
    const command = this.undoStack.pop();
    if (!command) {
      console.log("Waiter: nothing to undo.");
      return;
    }
    command.undo();
    this.redoStack.push(command);
  }
 
  redo(): void {
    const command = this.redoStack.pop();
    if (!command) {
      console.log("Waiter: nothing to redo.");
      return;
    }
    command.execute();
    this.undoStack.push(command);
  }
}
 
// ----- The CLIENT: Aarav orders -----
const kitchen = new Kitchen();
const waiter = new Waiter();
 
waiter.place(new PrepareDishCommand(kitchen, "Paneer Butter Masala"));
waiter.place(new PrepareDishCommand(kitchen, "Butter Naan"));
waiter.place(new PrepareDishCommand(kitchen, "Sweet Lassi"));
kitchen.showBoard();
 
console.log("\nAarav's sister: cancel the lassi please!");
waiter.undo(); // undo last order
kitchen.showBoard();
 
console.log("\nAarav's sister: actually, bring the lassi back!");
waiter.redo(); // redo it
kitchen.showBoard();

আউটপুট:

Kitchen: started preparing Paneer Butter Masala.
Kitchen: started preparing Butter Naan.
Kitchen: started preparing Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan, Sweet Lassi]
 
Aarav's sister: cancel the lassi please!
Kitchen: cancelled Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan]
 
Aarav's sister: actually, bring the lassi back!
Kitchen: started preparing Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan, Sweet Lassi]

তিনটা সুন্দর জিনিস লক্ষ্য করো:

১. Waiter class কখনো খাবারের কথা বলে না। এটা একটা drawing app (DrawCircleCommand) বা smart home (LightsOnCommand)-এর জন্যও হুবহু কাজ করবে। Invoker গুলো সম্পূর্ণ reusable। ২. Undo-তে আমাদের প্রায় কিছুই লাগেনি। প্রতিটা command তার নিজের reverse জানে, আর waiter দুটো stack রাখে। এটাই আসল editor-এ Ctrl+Z-এর পুরো machinery। ৩. Macro বানানো সহজ। একটা "Family Combo" command-এ command-এর একটা list থাকতে পারে আর সবগুলো চালাতে পারে — command দিয়ে তৈরি command (Command + Composite):

class ComboCommand implements Command {
  constructor(private commands: Command[]) {}
 
  execute(): void {
    this.commands.forEach((c) => c.execute());
  }
 
  undo(): void {
    // undo in REVERSE order — last action reversed first
    [...this.commands].reverse().forEach((c) => c.undo());
  }
}
 
waiter.place(
  new ComboCommand([
    new PrepareDishCommand(kitchen, "Veg Biryani"),
    new PrepareDishCommand(kitchen, "Raita"),
  ]),
);
waiter.undo(); // cancels Raita first, then Biryani — one clean undo

History stack গুলো কাজের সময় কীভাবে শ্বাস নেয় দেখো। প্রতিটা edit undo stack বাড়ায়; প্রতিটা undo সেটা কমায় (আর redo বাড়ায়):

চিত্র ৬: একটা session-এ undo stack বাড়তে আর কমতে থাকে

শেষ bar-টা মনোযোগ দিয়ে দেখো: নতুন edit-এর পরে count ১০ না, ৯। redo stack-এ বসে থাকা দুটো command নতুন action দিয়ে clear হয়ে গেছে — আমাদের ৪ নম্বর নিয়ম, ঠিকঠাক কাজ করছে।

C#-এ একই জিনিস

C# এই pattern-কে ঘরের মতো লাগায় — WPF আর অন্যান্য UI framework-এ built-in ICommand interface-ও আছে। এখানে আমাদের রেস্তোরাঁ, সংক্ষিপ্ত করে:

public interface ICommandSlip
{
    void Execute();
    void Undo();
}
 
// Receiver
public class Kitchen
{
    private readonly List<string> _board = new();
 
    public void Prepare(string dish)
    {
        _board.Add(dish);
        Console.WriteLine($"Kitchen: preparing {dish}.");
    }
 
    public void StopPreparing(string dish)
    {
        _board.Remove(dish);
        Console.WriteLine($"Kitchen: cancelled {dish}.");
    }
}
 
// Concrete command
public class PrepareDishCommand : ICommandSlip
{
    private readonly Kitchen _kitchen;
    private readonly string _dish;
 
    public PrepareDishCommand(Kitchen kitchen, string dish)
        => (_kitchen, _dish) = (kitchen, dish);
 
    public void Execute() => _kitchen.Prepare(_dish);
    public void Undo() => _kitchen.StopPreparing(_dish);
}
 
// Invoker with history
public class Waiter
{
    private readonly Stack<ICommandSlip> _history = new();
 
    public void Place(ICommandSlip slip)
    {
        slip.Execute();
        _history.Push(slip);
    }
 
    public void UndoLast()
    {
        if (_history.TryPop(out var slip)) slip.Undo();
    }
}
 
// Client
var kitchen = new Kitchen();
var waiter = new Waiter();
waiter.Place(new PrepareDishCommand(kitchen, "Masala Dosa"));
waiter.Place(new PrepareDishCommand(kitchen, "Filter Coffee"));
waiter.UndoLast(); // cancels Filter Coffee

আর সম্পূর্ণতার জন্য Python-এ একই skeleton — command শুধু দুটো method সহ object:

class PrepareDishCommand:
    def __init__(self, kitchen, dish):
        self.kitchen = kitchen
        self.dish = dish
 
    def execute(self):
        self.kitchen.prepare(self.dish)
 
    def undo(self):
        self.kitchen.stop_preparing(self.dish)
 
 
class Waiter:
    def __init__(self):
        self.history = []
 
    def place(self, command):
        command.execute()
        self.history.append(command)
 
    def undo_last(self):
        if self.history:
            self.history.pop().undo()

আধুনিক C#, TypeScript, আর Python-এ খুব সহজ command গুলো plain lambda-ও হতে পারে। এতে class-এর আনুষ্ঠানিকতা কমে। কিন্তু যেই মুহূর্তে undo() বা serialization দরকার হবে, একটা proper command class তার জায়গা বুঝিয়ে দেবে।

এখানে আমরা যা তৈরি করলাম তার পুরো class structure:

চিত্র ৭: Command pattern-এর পাঁচটা role আর কীভাবে সেগুলো সংযুক্ত

বাস্তব software-এ কোথায় দেখবে

এই pattern জানলে সব জায়গায় অর্ডার স্লিপ চোখে পড়বে।

১. Editor-এ Undo/redo। Microsoft Word, Google Docs, Photoshop, আর VS Code সব তোমার edit গুলো history stack-এ command-এর মতো object হিসেবে track করে। Ctrl+Z চাপলে editor শেষ command pop করে উল্টে দেয়। প্রতিটা undo arrow-এর পেছনে একটা স্লিপের রেল আছে।

২. Job আর task queue। Background job system — ভাবো email sender, report generator, message queue — "কী করতে হবে" একটা stored object-এ serialize করে, queue-এ push করে, আর worker পরে সেটা execute করে — হয়তো অন্য machine-এ। একটা job হলো এমন একটা command যে ভ্রমণ করতে শিখেছে।

৩. Redux action। Redux-এ app-এর state-এর প্রতিটা পরিবর্তন একটা plain action object — একটা typed note "ADD_ITEM with this payload" — data-তে রূপান্তরিত request। Action গুলো object হওয়ায়, redux-undo-এর মতো tool পুরো app-কে প্রায় বিনামূল্যে undo/redo দিতে পারে। Redux DevTools প্রতিটা action log আর replay করতে পারে — সেটাই "time-travel debugging"।

৪. GUI button আর menu। Classic desktop framework — Java Swing-এর Action, WPF-এর ICommand — একটা command দিয়ে toolbar button, menu item, আর keyboard shortcut একসাথে চালাতে দেয়। আগে যে duplicate-logic সমস্যার কথা বলেছিলাম, সেটা ঠিক হয়ে যায়।

৫. Transaction আর recovery। Database আর কিছু server প্রতিটা operation apply করার আগে একটা log-এ লেখে। Crash-এর পরে logged command গুলো replay করে সর্বশেষ consistent state ফিরিয়ে আনে। "Transaction" আক্ষরিক অর্থেই এই pattern-এর একটা official alternate নাম।

৬. Smart home routine। Voice assistant-এ "Good night" বললে একটা macro command চলে: লাইট বন্ধ, দরজা lock, AC ২৪ ডিগ্রি। একটা trigger, অনেক স্লিপ।

বাস্তব softwareCommand objectPattern কী সম্ভব করে
Word / Docs / VS Codeএকটা edit operationUndo/redo stack
Background job queueএকটা serialized jobপরে চালাও, retry করো, অন্য জায়গায় চালাও
Reduxএকটা action objectLogging, time travel, redux-undo
WPF / Swing UIICommand / Actionএক action, অনেক trigger
Databaseএকটা logged transactionReplay দিয়ে crash recovery
Smart home sceneDevice command-এর routineএক trigger থেকে macro

কলেজের কোণা: "প্রতিটা command log করো" ধারণাটাকে চরম পর্যায়ে নিয়ে গেলে পাবে event sourcing। Event-sourced system-এ, command আর সেগুলো থেকে তৈরি event-এর log-ই হলো database। তোমার bank account একটা সংখ্যা হিসেবে store হয় না — এটা account খোলার পর থেকে প্রতিটা deposit আর withdrawal-এর replay। Balance দরকার? স্লিপ গুলো replay করো। গত মার্চের balance দরকার? গত মার্চ পর্যন্ত স্লিপ replay করো। মি. জামাল ইতোমধ্যে এটা করেন: মধ্যরাতে তার অর্ডার স্লিপের স্তূপ replay করাই তার হিসাবের খাতা। EventStoreDB আর CQRS-এর মতো pattern এই একটা insight থেকে পুরো architecture তৈরি করে: যদি প্রতিটা পরিবর্তন একটা object হয়, তাহলে history হয়ে যায় data যেটা query, audit, আর time-travel করা যায়।

রাতে বন্ধ করার সময় মি. জামাল দিনের স্লিপ গুলো সাজান। বেশিরভাগ রান্না হয়েছে; কিছু বাতিল হয়েছে; কয়েকটা বাতিল হয়ে আবার ফিরে এসেছে, বিখ্যাত লাচ্ছির মতো:

চিত্র ৮: এক শুক্রবার সন্ধ্যায় সব অর্ডার স্লিপের পরিণতি

কখন ব্যবহার করবে, কখন না

পরিস্থিতিব্যবহার করবে?কারণ
Undo/redo দরকারহ্যাঁএটাই সবচেয়ে বড় কারণ
Operation queue করতে, schedule করতে, বা অন্য thread-এ চালাতে হবেহ্যাঁCommand গুলো object — object অপেক্ষা করতে পারে
কী হয়েছে তার log/history রাখতে চাওহ্যাঁপ্রতিটা executed command একটা list-এ push করো
একটা action button, menu, আর shortcut তিনটা থেকেই fire করতে হবেহ্যাঁএক command, অনেক invoker
Macro চাও — এক click = অনেক stepহ্যাঁComposite command child command ধরে
একটা button চিরকাল একটাই কাজ করে, history দরকার নেইনাDirect call বা simple callback যথেষ্ট
প্রতিটা ছোট getter-কে "শুদ্ধতার জন্য" command-এ মুড়ছোনাPure ceremony; pattern সত্যিকারের সমস্যা সমাধান করতে হয়
Operation reverse হয় না কিন্তু undo-এর প্রতিশ্রুতি দিচ্ছোসাবধানSnapshot ব্যবহার করো (Memento) অথবা undo-এর প্রতিশ্রুতিই দিও না

নিজের feature এই map-এ রাখো। তোমার action গুলোর যত বেশি "পরে" দরকার — পরে undo, পরে চালাও, পরে audit — স্লিপের জন্য সেই argument তত শক্তিশালী:

চিত্র ৯: Command তোমার সমস্যায় fit করে কিনা সেটা দ্রুত বোঝার map

শিক্ষার্থীরা যে ভুলগুলো করে

⚠️

ভুল ১: স্লিপের ভেতরে রান্না করা। শিক্ষার্থীরা প্রায়ই আসল business logic command class-এর ভেতরে নিয়ে যায়। ভুল! ফাতেমা রান্না করেন; স্লিপ শুধু কী রান্না করতে হবে তা বলে। Command-এর কাজ receiver-কে delegate করা। তোমার command যদি ২০০ লাইনের হয়, তাহলে রাঁধুনির কাজ কাগজে উঠে এসেছে।

⚠️

ভুল ২: Redo stack clear করতে ভুলে যাওয়া। Undo-এর পরে user নতুন কোনো action করলে redo stack অবশ্যই clear করতে হবে। ভুলে গেলে user এমন একটা timeline-এ "redo" করতে পারবে যেটা আর নেই — app-এর state তখন অর্থহীন হয়ে যাবে। এটাই সবচেয়ে বেশি দেখা undo/redo bug।

আরও কিছু ফাঁদ এড়িয়ে চলো:

  • Macro forward order-এ undo করা। [add biryani, add raita]-এর combo undo করতে হবে [remove raita, remove biryani] — সবসময় order উল্টাও। প্লেট stack করার কথা ভাবো: শেষে রাখা প্লেট আগে ওঠে।
  • Command-এর মধ্যে mutable state share করা। দুটো command একই পরিবর্তনশীল data-র reference ধরলে একটা undo করলে অন্যটার "আগের" ধারণা নষ্ট হতে পারে। প্রতিটা command-এর undo data নিজের ভেতরেই রাখো — যেমন প্রতিটা স্লিপ নিজের table number বহন করে।
  • Invoker যখন command তৈরি করে। করিম ভাইয়ের কখনো menu ঠিক করা উচিত না। Invoker client থেকে command পায়; নিজে তৈরি করে না।
  • Unbounded history। চিরকাল প্রতিটা command push করলে memory শেষ হবে। আসল editor history depth সীমাবদ্ধ করে — যেমন শেষ ১০০ command। রেলে জায়গা সীমিত; পুরানো স্লিপ box-এ যায়।
  • অর্ধেক-সম্পন্ন execute। execute() মাঝপথে fail করলে তারপরও undo stack-এ push করলে undo() এমন কাজ উল্টাবে যেটা পুরোপুরি হয়নি। Successful execute-এর পরেই push করো।

চাচাতো pattern গুলোর সাথে তুলনা

Command "behavior-কে object-এ wrap করো" pattern পরিবারের একজন। কীভাবে আলাদা করবে দেখো:

Patternকী wrap করা হয়?মূল প্রশ্নবিশেষ ক্ষমতা
Commandএকটা request: কী করতে হবে + receiver + params"এই action store/queue/undo করা যাবে?"History, queue, undo
Strategyএকটা algorithm: একটা কাজ কীভাবে করবে"এই কাজটা কোন উপায়ে করা উচিত?"Swappable algorithm
Chain of Responsibilityকিছু না — এটা handler সংযুক্ত করে"কে এই request handle করবে?"Runtime-এ handler খোঁজে
MementoState-এর একটা snapshot"আগে কেমন ছিল?"State restore, undo-এর সাথে Command-এর জুটি
Observerএকটা subscription list"এটা ঘটলে কে জানতে চায়?"অনেক listener-এ broadcast

Strategy-র সাথে confusion সবচেয়ে বেশি হয়। দুটোই দেখতে "একটা method সহ object"-এর মতো। পার্থক্যটা হলো: Strategy উত্তর দেয় কীভাবে — "quicksort দিয়ে sort করবো না mergesort দিয়ে?" Command উত্তর দেয় কী — "text insert করো", "row delete করো" — আর তার সাথে storage, queuing, আর undo যোগ করে। রেস্তোরাঁর ভাষায়: Strategy হলো ফাতেমা একই খাবারের জন্য দুটো রেসিপির মধ্যে একটা বেছে নেন; Command হলো স্লিপ যেটা বলে টেবিল ৪-এ খাবার চাই।

বন্ধুত্বপূর্ণ জুটিগুলোও মনে রেখো: Command + Composite = macro command (Family Combo); Command + Memento = snapshot undo — রান্নার আগে kitchen board-এর ছবি তোলো, undo করতে ছবিটা ফিরিয়ে আনো; আর command-এর queue সাধারণত একটা Iterator দিয়ে চলে — এই series-এর পরের পাঠ।

দ্রুত রিভিশন বক্স

+=====================================================================+
|                  COMMAND PATTERN — QUICK REVISION                   |
+=====================================================================+
| WHAT     : Turn a request into an object with execute() + undo().   |
| STORY    : Restaurant order slip. Aarav=Client, Ravi=Invoker,       |
|            Slip=Command, Chef Lakshmi/Kitchen=Receiver.             |
| ROLES    : Command | ConcreteCommand | Invoker | Receiver | Client  |
| RULE     : Commands DELEGATE to receivers; they do not do the work. |
| UNDO     : execute -> push UNDO | undo -> pop UNDO, push REDO       |
|            redo -> pop REDO, push UNDO | new action -> CLEAR REDO   |
| WINS     : undo/redo, queues, logging/replay, macros, one action    |
|            from many triggers                                       |
| RISKS    : class explosion, indirection, tricky undo state          |
| SEEN IN  : editor Ctrl+Z, job queues, Redux actions, WPF ICommand,  |
|            database transaction logs, event sourcing                |
| COUSIN   : Strategy = HOW to do a job; Command = WHAT to do, plus   |
|            store / queue / undo.                                    |
+=====================================================================+

নিজে করে দেখো

নিজে টাইপ করো — টাইপ করলে এমন স্মৃতি তৈরি হয় যা শুধু পড়লে হয় না।

টাস্ক ১: TV রিমোট। Television receiver তৈরি করো on(), off(), setChannel(n) সহ। PowerOnCommand, PowerOffCommand, আর SetChannelCommand তৈরি করো — SetChannelCommand অবশ্যই আগের channel মনে রাখবে যাতে undo() ফিরে যেতে পারে। History stack আর undoLast() বাটন সহ একটা RemoteControl invoker তৈরি করো। Test: power on → channel 5 → channel 9 → undo (5-এ ফিরে) → undo (TV কি reverse করতে কী মনে রাখে?)।

টাস্ক ২: Redo সহ Text editor। Document receiver তৈরি করো insert(text) আর deleteLast(count) সহ। একটা TypeCommand তৈরি করো আর invoker-কে পূর্ণ undo আর redo stack দাও — "নতুন action-এ redo clear করো" নিয়ম সহ। Test: "Namaste" টাইপ করো, " Bangladesh" টাইপ করো, undo, undo, redo — document পড়া উচিত "Namaste"।

টাস্ক ৩ (চ্যালেঞ্জ): Replay সহ Order queue। তোমার restaurant waiter-কে প্রতিটা placed command log: Command[]-এ store করাও। একটা replayDay(kitchen) function যোগ করো যেটা একটা নতুন kitchen তৈরি করে আর প্রতিটা logged command পর্যায়ক্রমে re-execute করে, kitchen board হুবহু পুনর্নির্মাণ করে। তুমি এইমাত্র database crash recovery, Redux time-travel debugging, আর event sourcing-এর মূল idea implement করে ফেলেছো — মি. জামালের মধ্যরাতের স্লিপ গোনা, কোডে লেখা।

তিনটা শেষ করলে Ctrl+Z আর কখনো আগের মতো দেখাবে না — সেই ছোট্ট shortcut-এর পেছনে আছে একটা অর্ডার স্লিপের রেল, চুপচাপ ছেঁড়ার অপেক্ষায়।

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

সহজ ভাষায় Command pattern কী?
এটা একটা request-কে — কী করতে হবে, কোন object-এ, কোন details সহ — একটা standalone object-এ ভরে রাখে। সেই object-এ একটাই execute() method থাকে। তুমি চাইলে সেই object store, queue, log, বা পরে undo করতে পারো।
Command pattern undo আর redo-এর জন্য এত কাজের কেন?
কারণ প্রতিটি action নিজেই একটা object হয়ে যায়, আর সেই object জানে নিজেকে কীভাবে উল্টাতে হবে। App executed command গুলো history stack-এ রাখে। undo করতে চাইলে শেষ command pop করে তার undo() call করলেই হলো।
Command pattern-এ invoker আর receiver কে কে?
Invoker হলো যেটা command trigger করে — যেমন একটা button বা ওয়েটার — এটা শুধু execute() call করে। Receiver হলো যেটা আসল কাজটা করে — যেমন document বা রান্নাঘর।
বাস্তব software-এ Command pattern কোথায় দেখা যায়?
Word, Google Docs, VS Code-এর মতো editor-এ undo/redo; পরে task চালানোর জন্য job queue; front-end app-এ Redux action; আর অনেক framework-এ GUI button আর menu item-এ।
Command আর Strategy-এর মধ্যে পার্থক্য কী?
দুটোই behavior-কে object-এ রাখে। Strategy বদলায় একটা কাজ কীভাবে করা হবে — একই কাজের আলাদা algorithm। Command বদলায় কী করা হবে, তাই request টা নিজেই queue, log, বা undo করা যায়।

আরো দেখো

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

Chain of Responsibility Pattern: যে যেটা পারে সে সামলাও, নইলে পাস করো

Chain of Responsibility pattern শেখো একটা school leave application-এর গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, table, আর practice task সহ।

আরও পড়ুন

Iterator Pattern: একে একে প্রতিটা element-এ যাও

Iterator pattern শেখো ট্রেনের টিকেট চেকারের গল্প দিয়ে। TypeScript-এ custom iterator বানাও, for...of আর generator ব্যবহার করো, আর C#-এ IEnumerable দেখো।

আরও পড়ুন

Memento Pattern: বস ফাইটের আগে গেম সেভ করো

ভিডিও গেমের save point-এর গল্প দিয়ে Memento pattern বোঝো — TypeScript আর Python-এ undo-র উদাহরণ, diagram, table আর সহজ practice task সহ।

আরও পড়ুন

Strategy Pattern: সাইকেল, বাস, নাকি অটো — তুমিই ঠিক করো

Strategy design pattern শেখো একটা সহজ স্কুলে যাওয়ার গল্পের মাধ্যমে — TypeScript আর C# কোড, runtime swapping, বাস্তব উদাহরণ, আর প্র্যাকটিস exercise সহ।

আরও পড়ুন