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

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

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

22 মিনিট আপডেট: June 11, 2026beginner
strategydesign patternsbehavioraltypescriptcompositionalgorithms

রাহেলার স্কুলে যাওয়ার তিনটা পথ

ধরো রাহেলা — ঢাকার একটা স্কুলের সপ্তম শ্রেণির ছাত্রী। বাড়ি থেকে স্কুলের দূরত্ব ৪ কিলোমিটার, আর সেখানে পৌঁছানোর তিনটা উপায় আছে তার:

  • সুন্দর ঠান্ডা সকালে সে সাইকেল চালিয়ে যায়। খরচ নেই, মজা আছে, ব্যায়ামও হয়।
  • বৃষ্টির দিনে আম্মু দরজায় থামিয়ে দেন — "আজ সাইকেলে না!" — তখন সে বাস ধরে। সস্তা, ভেজা লাগে না, কিন্তু বাস স্টপ পর্যন্ত হাঁটতে হয়।
  • পরীক্ষার দিনে ভারী বইয়ের ব্যাগ নিয়ে যখন তাড়াতাড়ি পৌঁছাতে হবে, আব্বু একটা অটো ডাকেন। খরচ বেশি, কিন্তু দ্রুত আর দরজায় দরজায় পৌঁছে দেয়।

প্রতিটা সকালে দরজায় একটা ছোট নাটক হয়। রাহেলা আকাশের দিকে তাকায়। আম্মু রাহেলার দিকে তাকান। আব্বু ক্যালেন্ডারের দিকে তাকান। আর তিনটার মধ্যে একটা plan বেছে নেওয়া হয়।

এবার একটা গুরুত্বপূর্ণ ব্যাপার লক্ষ্য করো। রাহেলার লক্ষ্য কখনো বদলায় না: বাড়ি ছেড়ে ৮:৩০-এর মধ্যে স্কুলে পৌঁছানো। যা বদলায় সেটা হলো পৌঁছানোর method। প্রতিটা method নিজেই একটা সম্পূর্ণ plan — সাইকেল plan জানে পার্কের ভেতর দিয়ে shortcut কোথায়, বাস plan জানে রুট নম্বর ৪৭এ, আর অটো plan জানে ভাড়া দরাদরি কীভাবে করতে হয়।

আর এটাই মূল কথা: রাহেলা প্রতি সকালে নিজে ঠিক করে কোন plan ব্যবহার করবে। Planগুলো তার হয়ে সিদ্ধান্ত নেয় না। সাইকেল কখনো বলে না, "বৃষ্টি পড়ছে, বাসে চলে যাও।" রাহেলা জানালা দিয়ে দেখে আর বেছে নেয়।

নিচে তার সকালের সিদ্ধান্তটা দেখানো হলো — লক্ষ্য করো সিদ্ধান্ত একবারই নেওয়া হয়, দরজায়, তারপর বেছে নেওয়া plan শুধু চলতে থাকে:

চিত্র ১: দরজায় সকালের সিদ্ধান্ত — একবার বেছে নাও, তারপর plan follow করো

এটাই বাস্তব জীবনে Strategy pattern। একটাই নির্দিষ্ট লক্ষ্য, swap করা যায় এমন planগুলোর একটা পরিবার, আর একজন chooser যে একটা plan বেছে সেটা follow করে। যেকোনো সকালে plan বদলানো যায় রাহেলাকে নিজেকে না বদলেই। এই গল্পটা মাথায় রাখো — পুরো article জুড়ে আমরা এটাকেই code-এ রূপান্তরিত করব।

একটা বৃষ্টির মঙ্গলবার রাহেলার দিক থেকে এরকম দেখায়:

চিত্র ২: একটা বৃষ্টির মঙ্গলবার — লক্ষ্য নির্দিষ্ট, শুধু plan বদলানো হয়েছে

Strategy pattern কী?

Strategy হলো একটা behavioral design pattern। এটা বলে: যখন একটা class একটা কাজ বিভিন্নভাবে করতে পারে, তখন সেই সব উপায় একটা বড় if-else দিয়ে class-এর ভেতরে গুঁজে রেখো না। বরং প্রতিটা উপায়কে আলাদা ছোট class-এ বের করে আনো, সবগুলোকে একটাই common interface মানাও, আর main class-কে একটা সময়ে একটার reference ধরে রাখতে দাও।

Main class-টার নাম হলো Context (রাহেলার trip)। Common interface হলো Strategy (একটা travel plan)। আলাদা classগুলো হলো Concrete Strategies (সাইকেল plan, বাস plan, অটো plan)। Context কখনো জানে না কোন concrete plan সে ধরে আছে। শুধু interface-টা জানে — "প্রতিটা plan-ই আমাকে স্কুলে নিয়ে যেতে পারে" — আর সে কাজটা delegate করে।

Gang of Four বই (১৯৯৪) Strategy-কে আসল ২৩টি pattern-এর মধ্যে রেখেছে আর এর আরেকটা নাম দিয়েছে: Policy। এর গভীর ধারণাটা software design-এ সবচেয়ে বেশি উদ্ধৃত লাইনগুলোর একটা: "যা পরিবর্তন হয় তাকে আলাদা করো (Encapsulate what varies)।" Travel করার পদ্ধতি বদলায়, তাই আমরা প্রতিটা variation-কে নিজস্ব object-এ মুড়ে নিই আর swap করার উপযোগী করি।

💡

এক লাইনে সারসংক্ষেপ: Strategy = if-else দিয়ে কীভাবে কাজ করবে সেটা ঠিক না করে, object decide করে কাকে জিজ্ঞেস করবে — আর তুমি যেকোনো সময় "কাকে" বদলে দিতে পারো।

মনে রাখার মতো তিনটা role:

  • Context — একটা strategy ধরে রাখে আর কাজ তাকে delegate করে (রাহেলার trip, বা একটা shopping cart, বা একটা router)।
  • Strategy interface — প্রতিটা variant যে common প্রতিশ্রুতি দেয় (go(distanceKm))।
  • Concrete strategies — আসল swap করা যায় এমন algorithmগুলো (Cycle, Bus, Auto)।

রাহেলার তিনটা plan পাশাপাশি দেখো। লক্ষ্য করো এগুলো একই লক্ষ্যের জন্য সত্যিকারের ভিন্ন algorithm — ভিন্ন খরচ, ভিন্ন গতি, ভিন্ন trade-off। এটাই তাদের একটা strategy পরিবার করে তোলে:

Plan৪ কিমির সময়খরচকখন ভালোকী জানে
সাইকেল~২০ মিনিট০ টাকাঠান্ডা, শুকনো সকালপার্কের shortcut
বাস ৪৭এ~২৬ মিনিট১০ টাকাবৃষ্টি, সাধারণ দিনরুট আর stop
অটো~১২ মিনিট~৯০ টাকাপরীক্ষার দিন, ভারী ব্যাগভাড়া দরাদরি

কলেজ কর্নার: এখানে দার্শনিক চালটা হলো inheritance-এর চেয়ে composition-কে প্রাধান্য দাও — GoF বইয়ের দ্বিতীয় বড় নীতি। শুরুতে মাথায় আসে CyclingStudent, BusStudent, আর AutoStudent হিসেবে Student-এর subclass বানানো। কিন্তু তাহলে যে student সোমবার সাইকেলে যায় আর মঙ্গলবার বাসে, তাকে runtime-এ class বদলাতে হবে — inheritance সেটা পারে না। Composition পারে: একটাই Student রাখো, তাকে একটা swap করা যায় এমন plan field দাও। যখনই মনে হবে শুধু একটা behaviour বদলাতে subclass করতে হবে, Strategy সাধারণত ভালো উত্তর।

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

আগে রাহেলার trip-টা কষ্টকর উপায়ে লিখি — সব কিছু একটা class-এর মধ্যে:

// BAD CODE: one method doing the job of three, glued with if-else
class SchoolTrip {
  travel(mode: string, distanceKm: number): void {
    if (mode === "cycle") {
      const minutes = distanceKm * 5;
      console.log(`Cycling via the park shortcut: ${minutes} min, Rs 0`);
    } else if (mode === "bus") {
      const minutes = 10 + distanceKm * 4; // walk to stop + ride
      console.log(`Bus route 47A: ${minutes} min, Rs 10`);
    } else if (mode === "auto") {
      const minutes = distanceKm * 3;
      const fare = 30 + distanceKm * 15;
      console.log(`Auto, door to door: ${minutes} min, Rs ${fare}`);
    } else {
      throw new Error("Unknown mode: " + mode);
    }
  }
}

এখন ছোট মনে হচ্ছে। দেখো বাড়তে বাড়তে কী হয়:

  • প্রতিটা নতুন mode পুরনো কোড ভেঙে দেয়। আগামী মাসে রাহেলার বন্ধু নাসরিন মেট্রোর পরামর্শ দিল। তখন এই কাজ করা, test করা method খুলে আরেকটা else if যোগ করতে হবে। মেট্রো যোগ করতে গিয়ে বাসের logic ভেঙে ফেলার ঝুঁকি সত্যিই আছে। এটা Open/Closed Principle ভঙ্গ করে — কোড extension-এর জন্য খোলা থাকবে কিন্তু modification-এর জন্য বন্ধ থাকবে।
  • অসম্পর্কিত logic একসাথে থাকতে বাধ্য হয়। অটোর ভাড়া দরাদরি আর সাইকেলের shortcut একই method, একই scope, একই variables শেয়ার করে। এক branch-এ bug fix করলে সব branch আবার test করতে হয়।
  • একই mode === ... check সব জায়গায় ছড়িয়ে পড়ে। খরচ calculator mode check করে। "কী নিতে হবে" helper-ও mode check করে। Mode string পুরো codebase-এ copy হতে থাকে, আর একদিন কেউ "buss" type করে — কিছুই match হয় না।
  • Testing ঝামেলার। শুধু অটোর ভাড়ার formula test করতে হলে পুরো travel method চালিয়ে এক branch-এ নামতে হবে। অটো algorithm-টা আলাদা করে তুলে নেওয়া যাচ্ছে না।
চিত্র ৩: Pattern ছাড়া, কোডের প্রতিটা অংশ একই which-mode প্রশ্ন বারবার করে

আর এই বৃদ্ধিটা মৃদু না। প্রতিটা নতুন travel mode, which-mode প্রশ্ন করে এমন প্রতিটা method-এ আরেকটা branch যোগ করে। Strategy-তে প্রতিটা নতুন mode হলো একটা নতুন class আর chooser-এ একটা নতুন লাইন:

চিত্র ৪: Travel mode বাড়লে conditional lines কীভাবে বাড়ে — if-else ladder গুণ হয়, strategy family ধীরে বাড়ে

মূল সমস্যা: একটা class অনেক class-এর কাজ করছে। সে trip-টার মালিক আর trip করার প্রতিটা সম্ভাব্য উপায়েরও মালিক। Strategy trip রাখে আর উপায়গুলো বের করে দেয়।

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

Refactoring-এর recipe এরকম:

  1. যে algorithm বদলায় সেটা খুঁজে বের করো। এখানে travel()-এর body mode-এর সাথে বদলায়। এটাই আমরা extract করব।
  2. Strategy interface declare করো। একটা method যা প্রতিটা variant সৎভাবে promise করতে পারে: go(distanceKm)। Signature যতটা সম্ভব ছোট রাখো।
  3. প্রতিটা branch আলাদা class-এ নিয়ে যাও। CycleStrategy, BusStrategy, AutoStrategy — প্রতিটা interface implement করে আর শুধু নিজের গণিত আর নিজের settings বহন করে।
  4. Context-কে একটা strategy field দাও। SchoolTrip এখন একটা TravelStrategy রাখে আর setStrategy() offer করে যাতে runtime-এ বদলানো যায়।
  5. Conditional-কে delegation দিয়ে replace করো। যেখানে if-else ছিল, সেখানে এক লাইন লিখো: this.strategy.go(distance)
  6. Client-কে বেছে নিতে দাও। রাহেলা (client code) আবহাওয়া দেখে সঠিক strategy install করে। Choice এখন ঠিক একটাই জায়গায় থাকে।
  7. Optional: একটা default strategy। CycleStrategy দিয়ে শুরু করো যাতে কেউ configure না করলেও context কাজ করে।
চিত্র ৫: Strategy pattern-এর structure — trip যে plan plug করা আছে তাকে delegate করে

রাহেলা থেকে dotted arrow লক্ষ্য করো। শুধু সে-ই concrete plan-এর নাম জানে। Trip নিজে অন্ধ থাকে — সে শুধু interface দেখে।

বাস্তব জীবনের code উদাহরণ

এখানে TypeScript-এ রাহেলার পুরো সপ্তাহ। দেখো strategies কীভাবে runtime-এ swap হচ্ছে — একই trip object, ভিন্ন behaviour:

// ---------- The Strategy interface ----------
interface TravelStrategy {
  readonly name: string;
  go(distanceKm: number): void;
}
 
// ---------- Concrete strategies ----------
// Each plan is complete and self-contained.
class CycleStrategy implements TravelStrategy {
  readonly name = "Cycle";
  go(distanceKm: number): void {
    const minutes = distanceKm * 5;
    console.log(`  Cycling via the park shortcut: ${minutes} min, Rs 0`);
  }
}
 
class BusStrategy implements TravelStrategy {
  readonly name = "Bus 47A";
  go(distanceKm: number): void {
    const minutes = 10 + distanceKm * 4; // walk to the stop + ride
    console.log(`  Taking bus 47A: ${minutes} min, Rs 10`);
  }
}
 
class AutoStrategy implements TravelStrategy {
  readonly name = "Auto";
  private baseFare = 30;
  private perKm = 15;
  go(distanceKm: number): void {
    const minutes = distanceKm * 3;
    const fare = this.baseFare + this.perKm * distanceKm;
    console.log(`  Auto, door to door: ${minutes} min, Rs ${fare}`);
  }
}
 
// ---------- The Context ----------
// SchoolTrip never branches on the mode. It only delegates.
class SchoolTrip {
  private strategy: TravelStrategy = new CycleStrategy(); // default
 
  setStrategy(s: TravelStrategy): void {
    console.log(`[trip] plan changed to: ${s.name}`);
    this.strategy = s;
  }
 
  start(distanceKm: number): void {
    this.strategy.go(distanceKm); // ONE line, no if-else
  }
}
 
// ---------- Ravi's week (the client) ----------
const trip = new SchoolTrip();
const DISTANCE = 4;
 
console.log("Monday, cool morning:");
trip.start(DISTANCE);                  // default cycle plan
 
console.log("Tuesday, heavy rain:");
trip.setStrategy(new BusStrategy());   // swap at runtime!
trip.start(DISTANCE);
 
console.log("Wednesday, exam day, heavy bag:");
trip.setStrategy(new AutoStrategy());  // swap again!
trip.start(DISTANCE);

Output:

Monday, cool morning:
  Cycling via the park shortcut: 20 min, Rs 0
Tuesday, heavy rain:
[trip] plan changed to: Bus 47A
  Taking bus 47A: 26 min, Rs 10
Wednesday, exam day, heavy bag:
[trip] plan changed to: Auto
  Auto, door to door: 12 min, Rs 90

তিনটা গুরুত্বপূর্ণ পর্যবেক্ষণ:

  1. Trip object একবার তৈরি হয়েছিল আর তিনভাবে আচরণ করেছে। Behaviour swap হয়েছিল program চলার সময়, compile time-এ না। এই runtime swap হলো Strategy-র signature move।
  2. Strategies কখনো একে অপরকে স্পর্শ করেনি। Bus জানে না Auto আছে। শুধু রাহেলা (client code) বেছে নেয়। State pattern-এর সাথে তুলনা করো, যেখানে Fan-এর Slow state নিজেই Medium install করে।
  3. মেট্রো যোগ করা সম্পূর্ণ additive। class MetroStrategy implements TravelStrategy লিখলেই হলো। SchoolTrip, CycleStrategy, BusStrategy, AutoStrategy — কোনোটাই খুলতে হয়নি। পুরনো কোড নিরাপদ থাকে।

এখানে মঙ্গলবার সকালের call-by-call trace। রাহেলা শুধু trip-এর সাথে কথা বলে, trip শুধু interface-এর সাথে কথা বলে, bus plan উত্তর দেয়:

চিত্র ৬: মঙ্গলবার traced — রাহেলা plan install করে, trip অন্ধভাবে delegate করে, plan রিপোর্ট করে

যদি choice-টা একটা পরিষ্কার জায়গায় রাখতে চাও, Strategy-র সাথে একটা ছোট factory যুক্ত করো:

function planFor(weather: string, examDay: boolean): TravelStrategy {
  if (examDay) return new AutoStrategy();
  if (weather === "rain") return new BusStrategy();
  return new CycleStrategy();
}
 
trip.setStrategy(planFor("rain", false)); // the ONLY if-else left

If-else পুরোপুরি উধাও হয়নি — এটা একটাই সিদ্ধান্তের জায়গায় চলে গেছে। এটাই Strategy-র সৎ প্রতিশ্রুতি: "কখনো conditional না" না, বরং "which-one প্রশ্নটা ঠিক একবার, ঠিক একটা জায়গায় করা হয়"।

C#-এ একই ধারণা

রাহেলার বড় বোন সুমাইয়া একটা স্টেশনারি দোকানে part-time কাজ করে। তার billing program-এ swappable discount rule আছে — একই pattern, একটু বড় পোশাকে। আগে classic interface form, তারপর আধুনিক ভাষার দেওয়া lambda shortcut:

// Classic form: interface + classes
interface IDiscountStrategy
{
    decimal Apply(decimal subtotal);
}
 
class NoDiscount : IDiscountStrategy
{
    public decimal Apply(decimal subtotal) => subtotal;
}
 
class FestivalOffer : IDiscountStrategy   // 15% off for Diwali
{
    public decimal Apply(decimal subtotal) => subtotal * 0.85m;
}
 
class Bill
{
    private IDiscountStrategy _discount = new NoDiscount();
    public void SetDiscount(IDiscountStrategy d) => _discount = d;
    public decimal Total(decimal subtotal) => _discount.Apply(subtotal);
}
 
var bill = new Bill();
Console.WriteLine(bill.Total(1000));          // 1000
bill.SetDiscount(new FestivalOffer());
Console.WriteLine(bill.Total(1000));          // 850

এবার shortcut। যখন strategy মাত্র এক method আর সামান্য state, তখন function value অনেক কম ঝামেলায় একই কাজ করে:

// Function form: the strategy is just a Func
class QuickBill
{
    private Func<decimal, decimal> _discount = s => s; // no discount
 
    public void SetDiscount(Func<decimal, decimal> d) => _discount = d;
    public decimal Total(decimal subtotal) => _discount(subtotal);
}
 
var qb = new QuickBill();
qb.SetDiscount(s => s * 0.85m);            // festival: 15% off, one lambda
Console.WriteLine(qb.Total(1000));         // 850
qb.SetDiscount(s => s >= 500 ? s - 50 : s); // flat Rs 50 off above Rs 500
Console.WriteLine(qb.Total(1000));         // 950

দুটো form-ই Strategy pattern। Class form ব্যবহার করো যখন strategy-র একাধিক method দরকার (যেমন Compress আর Decompress), real configuration বহন করে, বা factory-তে registered named type হতে হবে। Lambda form ব্যবহার করো যখন এটা সত্যিই একটা ছোট operation।

কলেজ কর্নার: functional programming-এর ভাষায়, এক-method-এর strategy আসলে একটা higher-order function argument। Java-তে Collections.sort(list, comparator), Python-এ sorted(key=...), JavaScript-এ Array.prototype.sort(fn) — এগুলো সবই class ceremony ছাড়া Strategy pattern। এ কারণেই কেউ কেউ বলে "Strategy তো শুধু একটা function।" Mechanism সম্পর্কে তারা ঠিকই আছে, কিন্তু intent সম্পর্কে ভুল: pattern-এর মূল্য হলো stable contract সহ named, swappable family, আর এই ধারণাটা class দিয়ে বা closure দিয়ে লিখলেও টিকে থাকে। Dependency injection container একই ধারণাকে আরো এগিয়ে নেয় — strategy choice পুরোপুরি code থেকে বের হয়ে configuration-এ চলে যায়।

আরেকবার Python-এ

Python-এ lambda form প্রায় অদৃশ্য হয়ে যায়। ক্লাসের marks তিনভাবে sort করতে কোনো interface-ই দরকার নেই — key parameter-টাই strategy slot:

students = [
    ("Ravi", 82, 14), ("Sandhya", 95, 3), ("Imran", 88, 21),
]  # (name, marks, roll number)
 
# Three strategies for one job: ordering students
by_marks = sorted(students, key=lambda s: -s[1])  # toppers first
by_name  = sorted(students, key=lambda s: s[0])   # alphabetical
by_roll  = sorted(students, key=lambda s: s[2])   # roll number
 
print([s[0] for s in by_marks])  # ['Sandhya', 'Imran', 'Ravi']
print([s[0] for s in by_name])   # ['Imran', 'Ravi', 'Sandhya']
print([s[0] for s in by_roll])   # ['Sandhya', 'Ravi', 'Imran']

Sorting machinery হলো fixed context। key function হলো plug করা strategy। তুমি Python-এর প্রথম সপ্তাহ থেকেই এই pattern ব্যবহার করছ — শুধু নামটা জানতে না।

Strategy কোনো state machine না

একটু ভাবো — trip-টা নিজেই কিছু ধাপের মধ্য দিয়ে যায়: বাড়িতে, পথে, স্কুলে। এটা journey-র একটা ছোট state machine:

চিত্র ৭: trip-এর lifecycle state আছে, কিন্তু strategy মাঝপথে নিজে বদলায় না — এটাই State pattern থেকে পার্থক্য

কিন্তু মনোযোগ দিয়ে দেখো: trip এই stateগুলোর মধ্য দিয়ে যাওয়ার সময়, strategy একই থাকে। বাস plan মাঝপথে অটো plan-এ রূপান্তরিত হয় না। State pattern-এ, variantগুলো নিজেরাই context-কে এক থেকে অন্যটায় ঠেলে দেয় (Off installs Slow, Slow installs Medium)। Strategy-তে, variantগুলো অপরিচিত, আর শুধু client swap করে — সর্বোচ্চ একবার প্রতি সকালে, দরজায়। যদি কখনো দেখো তোমার "strategies" একে অপরের উপর context.setStrategy(...) call করছে, থামো। তুমি ভুলবশত একটা state machine বানিয়ে ফেলেছ — এটাকে State হিসেবে design করো।

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

Strategy হয়তো দৈনন্দিন programming-এর সবচেয়ে বেশি ব্যবহৃত pattern:

  • Sorting comparators। Java-তে Collections.sort(list, comparator) হলো textbook বাস্তব উদাহরণ — sort algorithm নির্দিষ্ট, কিন্তু comparison strategy তুমি plug করো। JavaScript-এর array.sort((a, b) => ...) আর C#-এর IComparer<T> একই ধারণা। যতবারই comparator পাঠাচ্ছ, strategy হস্তান্তর করছ।
  • Checkout-এ payment selection। বাংলাদেশের apps এই pattern প্রতিদিন ব্যবহার করে: bKash, নগদ, card, net banking, বা cash on delivery দিয়ে pay করো। প্রতিটা payment processor একটা common interface-এর পেছনে থাকে, আর বেছে নেওয়াটা checkout-এর সময় install হয়।
  • Compression আর encoding choice। Zip বনাম gzip বনাম brotli — caller codec বেছে নেয়, pipeline একই থাকে। Web server প্রতি request-এ compression strategy negotiate করে।
  • Authentication strategies। Node.js library Passport.js তার pluginগুলোকে আক্ষরিক অর্থেই "strategies" বলে — Google, Facebook, username/password দিয়ে login। একটা interface-এর পেছনে শত শত swap করা যায় এমন strategy।
  • Maps-এ route planning। গাড়ি, বাইক, হাঁটা, গণপরিবহন — একটা button swap পুরো routing algorithm বদলে দেয়, app-এর UI বদলায় না। রাহেলার সকালের choice, গ্রহ scale-এ।
  • Business app-এ validation আর pricing rules, editor-এ image filter, HTTP client-এ retry policy (fixed delay বনাম exponential backoff) — সব strategy menu।
  • পড়ার জন্য open-source code। iluwatar/java-design-patterns strategy folder class form আর lambda form পাশাপাশি দেখায়, আর refactoring.guru-এর Strategy page-এ দশটা ভাষায় উদাহরণ আছে।

আর আমাদের গল্পের সমাপ্তি টানতে — রাহেলার পুরো স্কুলের সব ছাত্রছাত্রীর daily travel strategy survey করলে মেনুটা এরকম হতো:

চিত্র ৮: এক লক্ষ্য, অনেক strategy — রাহেলার স্কুলের ছাত্রছাত্রীরা কীভাবে পৌঁছায়

প্রতিটা slice হলো একটা সম্পূর্ণ, স্বয়ংসম্পূর্ণ plan। কোনো slice অন্যটার অস্তিত্ব জানে না। Chooser হলো দরজায় থাকা ছাত্র। এই pie হলো pattern।

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

পরিস্থিতিStrategy ব্যবহার করবে?
একটা method-এ fat if-else আছে যা একই কাজের ভিন্ন উপায়ের মধ্যে বেছে নেয়হ্যাঁ — প্রতিটা উপায় extract করো
নতুন নতুন variant আসতে থাকে (নতুন payment mode, নতুন shipping rule)হ্যাঁ — যোগ করা সম্পূর্ণ additive হয়ে যাবে
Runtime-এ behaviour switch করতে হবে (user choice, config, আবহাওয়া!)হ্যাঁ — object swap করো
প্রতিটা algorithm আলাদাভাবে test করতে চাওহ্যাঁ — প্রতিটা strategy isolation-এ test হয়
অনেক subclass শুধু একটা behaviour-এ আলাদাহ্যাঁ — একটা context + strategies, class explosion-এর চেয়ে ভালো
মাত্র এক বা দুটো stable behaviour যা কখনো বদলায় নানা — plain code-ই পরিষ্কার
Variantগুলো স্বয়ংক্রিয়ভাবে একে অপরকে switch করে (lifecycle stage)না — এটা State pattern
পুরো "strategy" একটা ছোট expressionclass-এর বদলে plain function/lambda ব্যবহার করো
ধাপগুলো নির্দিষ্ট কিন্তু subclass-এ একটা মাঝের ধাপ বদলায়Template Method বিবেচনা করো

Strategy, State, আর plain code-এর মধ্যে দ্বিধায় পড়লে তোমার সমস্যাটা এই map-এ রাখো। দুটো প্রশ্ন গুরুত্বপূর্ণ: behaviour পরিবর্তন কে trigger করে, আর behaviour runtime-এ আদৌ বদলায় কি না?

চিত্র ৯: Pattern-এ হাত দেওয়ার আগে তোমার সমস্যা map-এ খুঁজে নাও

রাহেলার choice Strategy-র জায়গায় দৃঢ়ভাবে বসে আছে: behaviour প্রতিদিন বদলায় (runtime), আর caller (দরজায় রাহেলা) বেছে নেয়। State post-এর ceiling fan বিপরীতে বসে আছে: object নিজেই তার নিজস্ব ধাপের মধ্য দিয়ে হাঁটে।

ছাত্রছাত্রীরা যে সাধারণ ভুলগুলো করে

⚠️

এক নম্বর ভুল: if-else সরানোর বদলে context-এর method-এর ভেতরে নিয়ে যাওয়া। কেউ কেউ strategy class তৈরি করে কিন্তু তারপর context-এর ভেতরে if (strategy instanceof BusStrategy) লেখে। Context অবশ্যই অন্ধ থাকবে। সে একটা interface ধরে, একটা method call করে, আর concrete type কখনো উঁকি দিয়ে দেখে না। শুধু client বা একটা ছোট factory concrete নাম জানতে পারবে।

আরো কিছু ফাঁদ:

  1. একটা ফুলে যাওয়া strategy interface। যদি তোমার interface-এ ছয়টা method থাকে আর বেশিরভাগ strategy চারটা ফাঁকা রাখে, তাহলে interface অনেক বড় হয়ে গেছে। শুধু সেই operations রাখো যা প্রতিটা variant সত্যিই share করে।
  2. Context-এর internals-এর উপর গোপনে নির্ভরশীল strategy। Strategy-র যা দরকার তা parameter হিসেবে (বা constructor settings-এ) পাওয়া উচিত। Context-এর private field-এ হাত দিলে "independent, swappable" প্রতিশ্রুতি ভেঙে যায়।
  3. Default ভুলে যাওয়া। যে context-এর strategy null, সে প্রথম ব্যবহারেই crash করবে। একটা sensible default বা do-nothing strategy (যেমন NoDiscount) দাও যাতে সবসময় কাজ করে।
  4. Lambda যথেষ্ট হলে class ব্যবহার করা। s => s * 0.85-এর জন্য বিশ লাইনের ceremony অতিরিক্ত engineering। Pattern হলো swappable behaviour-এর ধারণা, class keyword নয়।
  5. Strategy-কে State-এর সাথে গুলিয়ে ফেলা। যদি দেখো strategies একে অপরের উপর context.setStrategy(...) call করছে, থামো — তুমি একটা state machine বানিয়েছ আর এটাকে State হিসেবে design করা উচিত।
💡

Interview tip: "এই সপ্তাহে Strategy-র একটা বাস্তব উদাহরণ দাও" জিজ্ঞেস করলে বলো comparator দিয়ে sorting। প্রায় নিশ্চিতভাবেই তুমি এটা করেছ, আর এটা দেখায় যে behaviour-কে value হিসেবে পাঠানোটাই pattern-এর মূল বিষয় তা তুমি বোঝো।

ভাই-বোন patternগুলোর সাথে তুলনা

Patternকীভাবে behaviour বদলায়Mechanismকখন বেছে নেওয়া হয়পরিবর্তন কে trigger করে
Strategyপুরো algorithm swap হয়Composition (context একটা object ধরে)Runtime-এClient
StateInternal stage-এর সাথে behaviour বদলায়Composition (একই skeleton!)Runtime-এStates নিজেরা
Template Methodশুধু নির্দিষ্ট ধাপ বদলায়Inheritance (subclass ধাপ override করে)Compile time-এবেছে নেওয়া subclass-এ নির্দিষ্ট
Commandএকটা action পরে চালানো/undo করতে wrappedCompositionRuntime-এInvoker queue করে
Decoratorঅতিরিক্ত behaviour layer করা হয়Composition (wrapping)Runtime-এWrapping order

পরীক্ষার জন্য দুটো তুলনা সবচেয়ে গুরুত্বপূর্ণ:

Strategy বনাম State — বিখ্যাত যমজ। Class diagram অভিন্ন: context, interface, concrete classes। পার্থক্য হলো intent। Strategy variants হলো একটা flat menu: independent, একে অপর সম্পর্কে অজ্ঞ, বাইরে থেকে বেছে নেওয়া (cash/UPI/card; cycle/bus/auto)। State variants হলো একটা connected graph: প্রতিটা ধাপ পরের ধাপ জানে আর context-কে সামনে ঠেলে দেয় (Off → Slow → Medium)।

কলেজ কর্নার: যমজদের জন্য blunt one-line test: যদি কোনো variant context.setState(otherVariant) call করে, সেটা State; যদি শুধু client swap করে, সেটা Strategy। Interviewerরা পছন্দ করেন এমন আরেকটা framing: Strategy উত্তর দেয় "কীভাবে এই কাজটা করতে হবে?" আর State উত্তর দেয় "এই object এখন কী করতে পারে?" Strategy choice সাধারণত সব একই সাথে valid (যেকোনো payment mode আজ কাজ করে); State stage সাধারণত timeline-এর পরস্পর বিরোধী point (একটা order একই সাথে Placed আর Delivered হতে পারে না)। Variantগুলোর মধ্যে তীর আঁকা যায়? State দেখছ। Variantগুলো একটা menu card তৈরি করে? Strategy দেখছ।

Strategy বনাম Template Method — একই সমস্যা, বিপরীত tools। উভয়ই behaviour-এর একটা অংশ vary করতে দেয়। Template Method inheritance ব্যবহার করে: একটা base class skeleton নির্দিষ্ট করে আর subclass ধাপ পূরণ করে, compile time-এ সিদ্ধান্ত হয়। Strategy composition ব্যবহার করে: context একটা আলাদা object ধরে আর runtime-এ পুরো algorithm replace করতে পারে। Runtime swapping দরকার হলে বা single-inheritance chain এড়াতে চাইলে Strategy বেছে নাও। অনেক variant যদি steps-এর একটা নির্দিষ্ট sequence share করে, Template Method ভালো।

চিত্র ১০: Strategy আর তার ভাই-বোনেরা — behaviour vary করার একই লক্ষ্য, ভিন্ন mechanism

এক পাতায় পুরো pattern

যদি এই mind map স্মৃতি থেকে আবার আঁকতে পারো, তাহলে pattern-টা তোমার আয়ত্তে:

চিত্র ১১: Strategy pattern, mapped — roles, key move, shortcuts, আর কোথায় থাকে

দ্রুত revision box

+--------------------------------------------------------------+
|                  STRATEGY PATTERN — REVISION                 |
+--------------------------------------------------------------+
| WHAT     : Family of interchangeable algorithms, each in     |
|            its own class behind one interface                |
| ACTORS   : Context + Strategy interface + concrete           |
|            strategies + Client who picks                     |
| KEY MOVE : Context delegates; client swaps at runtime        |
| KILLS    : Fat if-else choosing between ways of one job      |
| REMEMBER : Strategies never know each other (else: State)    |
| SHORTCUT : One-method strategy ~= a plain function/lambda    |
| AVOID IF : 1-2 stable variants; ceremony beats benefit       |
| EXAMPLES : Comparators, payment modes, compression codecs,   |
|            Passport.js auth, map routing                     |
+--------------------------------------------------------------+

Practice exercise

এগুলোর জন্য আসল কোড লেখো। নিজে type করাটাই অর্ধেক শেখা।

  1. দোকানের payment counter। CashPayment, UpiPayment, আর CardPayment strategy সহ একটা Checkout context বানাও। প্রতিটা একটা ভিন্ন flow print করে (পয়সা ফেরত দেওয়া, QR scan করা, swipe করে PIN দেওয়া)। একই checkout object ব্যবহার করে তিনটা ভিন্ন পদ্ধতিতে তিনজন customer-এর bill করো, customerদের মাঝে strategy swap করো।
  2. Marks sorter। তোমার কাছে নাম, marks, আর roll number সহ ছাত্রছাত্রীদের একটা array আছে। শুধু তোমার ভাষার built-in sort-এ comparator ব্যবহার করে তিনভাবে sort করো: marks অনুযায়ী descending, নাম alphabetically, roll number অনুযায়ী। তারপর এক বাক্যে লেখো: কেন comparator একটা strategy?
  3. Upgrade challenge। রাহেলার SchoolTrip নাও আর (a) একটা MetroStrategy, আর (b) উপরে দেখানো ছোট planFor(weather, examDay) factory যোগ করো, "strike-এর দিনে মেট্রো" rule সহ বাড়িয়ে। নিশ্চিত করো metro যোগ করতে শূন্যটা existing class স্পর্শ করেছ — যদি একটাও করে থাকে, refactor করো যতক্ষণ না করে।
  4. কলেজ stretch। পুরো travel উদাহরণটা class ছাড়াই লেখো: strategy হিসেবে plain function, mode name-কে key করে একটা map-এ, context এক function reference ধরে। তারপর দুটো version তুলনা করে তিনটা বাক্য লেখো: কোনটা ছোট, কোনটা একটা বড় codebase-এ সহজে খুঁজে পাওয়া যায়, আর চল্লিশজনের একটা দলের জন্য কোনটা বেছে নেবে?

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

Strategy pattern কী — এক লাইনে বলো?
প্রতিটা algorithm-কে main class থেকে বের করে একটা common interface-এর পেছনে আলাদা ছোট class-এ রাখো, আর main class-কে বলো যেটা plug করা আছে সেটাকে delegate করতে। Plug বদলালেই behaviour বদলে যায়।
Strategy আর State-এর পার্থক্য কী?
দুটোর class diagram দেখতে একই, কিন্তু উদ্দেশ্য আলাদা। Strategy-তে client একটা algorithm বেছে নেয়, আর algorithmগুলো একে অপরকে চেনে না। State-এ state objectগুলো একে অপরকে চেনে আর নিজেরাই context switch করে — একটা machine-এর ধাপের মতো।
একটা সাধারণ function কি strategy class-এর জায়গা নিতে পারে?
হ্যাঁ, অনেক ক্ষেত্রেই পারে। First-class function আছে এমন ভাষায়, এক-method-এর strategy মানেই একটা function বা lambda। Class ব্যবহার করো যখন strategy-র একাধিক method দরকার, নিজস্ব settings আছে, বা এটা একটা named discoverable type হতে হবে।
Strategy কি প্রোগ্রাম থেকে সব if-else সরিয়ে দেয়?
না, এটা choice-কে একটা জায়গায় নিয়ে আসে। কোথাও না কোথাও client বা factory ঠিকই decide করে কোন strategy তৈরি হবে। লাভ হলো এই decision এখন ঠিক একটা জায়গায় থাকে, অনেক method জুড়ে ছড়িয়ে পড়ে না।
Strategy কখন দরকার নেই?
যদি মাত্র একটা-দুটো সহজ behaviour থাকে যেগুলো প্রায় কখনো বদলায় না, তাহলে আলাদা interface আর class শুধু জঞ্জাল বাড়াবে। Pattern ব্যবহার করো যখন নতুন নতুন variant আসতে থাকে বা একটা method বড় conditional-এ ভরে গেছে।

আরো দেখো

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