State Pattern: একটা object-এর মেজাজ বদলানোর গল্প
State design pattern শেখো সিলিং ফ্যানের রেগুলেটরের গল্প দিয়ে। সহজ TypeScript আর C# code, state diagram, আর real software-এর উদাহরণ সহ।
সিলিং ফ্যানের রেগুলেটরের গল্প
ধরো গ্রীষ্মের দুপুরে। বাইরে প্রচণ্ড রোদ। ঘরে ফাতেমা আর তার ছোট ভাই রুবেল বসে আছে সিলিং ফ্যানের নিচে। ফ্যানটায় একটাই button — আধুনিক রেগুলেটর।
সেই একটা button কী করে দেখো:
- ফ্যান off। ফাতেমা button চাপে। ফ্যান আস্তে ঘুরতে শুরু করে।
- একই button আবার চাপে। ফ্যান মাঝারি গতিতে যায়।
- আরেকবার চাপে। এবার ফ্যান সর্বোচ্চ গতিতে চলে। রুবেলের পড়ার কাগজ উড়ে যায়!
- একটু লম্বা চাপ দিলে ফ্যান বন্ধ হয়ে যায়।
রুবেল জিজ্ঞেস করে: "আপু, একই button। তাহলে প্রতিবার আলাদা কাজ করছে কেন?"
ফাতেমা একটু ভেবে বলে: "কারণ ফ্যান মনে রাখে সে কোথায় আছে। বন্ধ ফ্যান button-এর চাপ শোনে 'চালু হও' হিসেবে। আস্তে চলা ফ্যান একই চাপ শোনে 'আরও জোরে যাও' হিসেবে।"
একটু ভাবো। button একই, ফাতেমার আঙুলের কাজ একই। কিন্তু ফ্যানের উত্তর আলাদা। কেন? কারণ ফ্যানের উত্তর নির্ভর করে তার এই মুহূর্তের অবস্থার উপর। "off" ফ্যান চাপ মানে "আস্তে শুরু করো"। "slow" ফ্যান একই চাপ মানে "medium-এ যাও"। "fast" ফ্যান মানে "আবার slow-তে ফিরে যাও"।
মানে হলো ফ্যান যেন একটা মানুষ, যার মেজাজ বদলায়। মা যখন খুশি থাকেন তখন ice cream চাইলে পাও। রান্না করছেন তখন একই কথা বললে উত্তর হয় "পরে।" একই প্রশ্ন, আলাদা উত্তর — কারণ state বদলেছে।
এটাই হলো State pattern। একটা object, একটা button — কিন্তু কোন button কী করবে সেটা নির্ভর করে এই মুহূর্তে object কোন state-এ আছে তার উপর। পুরো আলোচনাটা ফাতেমা আর রুবেলের এই গল্পের মতো। আমরা শেষে code-এ পুরো ফ্যানটা বানাবো।
এই গরমের দুপুরের যাত্রাটা এভাবে দেখা যায়:
State pattern কী?
State pattern হলো একটা behavioral design pattern। Behavioral pattern মানে হলো — object গুলো কীভাবে কাজ করে আর একে অপরের সাথে কীভাবে কথা বলে সেটা নিয়ে।
সহজ কথায় বলি। কোনো object যদি তার ভেতরের অবস্থার উপর ভিত্তি করে আলাদাভাবে কাজ করে, তাহলে একটা বড় class-এ status variable রেখে সব জায়গায় if-else লেখা ঠিক না। বরং প্রতিটা অবস্থার জন্য একটা ছোট class বানাও। সব state class একটা common interface follow করে। মূল object — যাকে বলা হয় context — একটা state object ধরে রাখে আর সব state-dependent call সেখানে forward করে।
অবস্থা বদলালে context শুধু state object বদলে দেয়। বাইরে থেকে মনে হয় object-টা যেন নিজেই বদলে গেছে। আসলে একটা নতুন helper object এখন call গুলো handle করছে।
এই pattern এসেছে ১৯৯৪ সালের বিখ্যাত Gang of Four (GoF) বইয়ে। এটাকে Objects for States-ও বলা হয়। এটা হলো finite state machine বানানোর clean, object-oriented উপায় — মানে "এমন একটা জিনিস যার নির্দিষ্ট কিছু অবস্থা আছে আর এক অবস্থা থেকে আরেক অবস্থায় যাওয়ার নির্দিষ্ট নিয়ম আছে।"
এক লাইনে: State pattern = object-এর প্রতিটা মেজাজের জন্য আলাদা class, আর object সেই class-এ কাজ forward করে।
দুটো role মনে রাখো:
- Context — মূল object, client যার সাথে কথা বলে। আমাদের গল্পে ফ্যান।
- State — common interface, আর প্রতিটা অবস্থার জন্য একটা concrete class। আমাদের গল্পে Off, Slow, Medium, আর Fast।
আরেকটা মজার জিনিস আছে। State object গুলো সাধারণত জানে পরের state কোনটা। Slow state জানে আরেকটা চাপ মানে Medium। তাই state নিজেই context-কে বলে, "এখন Medium install করো।" Transition-এর নিয়ম আর behavior একই জায়গায় থাকে। এটাই pattern-এর মূল কথা।
একটু গভীরে যাই: তুমি আসলে এখানে Computer Science-এর theory course-এর deterministic finite state machine (FSM) বানাচ্ছো — object-oriented পোশাক পরিয়ে। State গুলো FSM-এর state, button events হলো input alphabet, আর প্রতিটা setState call হলো transition function। এটা বুঝলে OOP আর Theory of Computation দুটোই পরিষ্কার হয়ে যায়।
সমস্যাটা কোথায়?
আগে দেখি pattern ছাড়া ফ্যান কেমন লেখা হতো। ফাতেমা প্রথমবার এভাবেই লিখেছিল — একটা string variable রেখে সব জায়গায় if-else:
// BAD CODE: one class, one status variable, if-else everywhere
class CeilingFan {
private status: string = "off"; // "off" | "slow" | "medium" | "fast"
pressButton(): void {
if (this.status === "off") {
console.log("Whirr... starting slowly");
this.status = "slow";
} else if (this.status === "slow") {
console.log("Speeding up to medium");
this.status = "medium";
} else if (this.status === "medium") {
console.log("Full speed! Hold your papers!");
this.status = "fast";
} else if (this.status === "fast") {
console.log("Back to slow");
this.status = "slow";
}
}
longPress(): void {
if (this.status === "off") {
console.log("Already off");
} else {
console.log("Switching off");
this.status = "off";
}
}
showPowerUse(): void {
if (this.status === "off") console.log("0 watts");
else if (this.status === "slow") console.log("30 watts");
else if (this.status === "medium") console.log("55 watts");
else if (this.status === "fast") console.log("75 watts");
}
}এখন কাজ করে। রুবেল খুশি। কিন্তু ভেতরে ভেতরে সমস্যা জমছে:
- একই ladder প্রতিটা method-এ।
pressButton,longPress, আরshowPowerUse— তিনটাই একইstatusদিয়ে branching করছে। real ফ্যান class-এremoteSignal(),timerTick(),display()থাকবে — আর প্রতিটা নিজের if-else ladder নিয়ে আসবে। - নতুন state মানে সব method খুলতে হবে। ধরো company Sleep mode যোগ করতে চায় — রাতে আস্তে আস্তে গতি কমাবে। তাহলে প্রতিটা method খুলে প্রতিটা ladder-এ একটা করে branch যোগ করতে হবে। একটা মিস করলে bug, যেটা মধ্যরাতে দেখা যাবে।
- Transition rules ছড়িয়ে আছে। কোন state-এর পরে কোনটা আসে? উত্তর অনেক method জুড়ে ছড়িয়ে। কেউ এক জায়গায় পড়তে পারবে না।
- Typo লুকিয়ে থাকে। ভুলে
"meduim"লিখলে compiler কিছুই বলবে না। ফ্যান একদিন হঠাৎ কাজ করা বন্ধ করবে।
State বাড়লে if-else lines কতটা বাড়ে দেখো:
Code ঠিক করার আগে আসল "rule book" টা লিখি — যেটা if-else লুকিয়ে রেখেছে। প্রতিটা state machine-কে একটা সহজ table হিসেবে লেখা যায়:
| বর্তমান state | Short press করলে | Long press করলে | Power ব্যবহার |
|---|---|---|---|
| Off | Slow | Off (থাকে) | 0 watts |
| Slow | Medium | Off | 30 watts |
| Medium | Fast | Off | 55 watts |
| Fast | Slow | Off | 75 watts |
| Sleep (নতুন!) | Slow | Off | 25 watts |
এই table-ই হলো ফ্যানের সত্যি নিয়ম। আর এই সত্যির সবচেয়ে সুন্দর ছবি হলো state diagram। এখানে নতুন Sleep mode সহ পুরো machine দেখো:
Diagram পরিষ্কার। if-else code পরিষ্কার না। State pattern code-কে diagram-এর মতো করে তোলে। এটাই তার কাজ।
কীভাবে কাজ করে?
ফাতেমা তার ফ্যান refactor করতে যে steps follow করেছে সেগুলো দেখো:
- Context খোঁজো। কোন class status field-এর উপর ভিত্তি করে আলাদাভাবে কাজ করে? সেটাই context। আমাদের ক্ষেত্রে
CeilingFan। - State-dependent action গুলো বের করো। কোন method গুলো status অনুযায়ী behavior বদলায়? আমাদের ক্ষেত্রে
pressButtonআরlongPress। এগুলোই State interface-এর method হবে। - প্রতিটা state-এর জন্য একটা class।
OffState,SlowState,MediumState,FastState। পুরনো if-else-এর প্রতিটা branch সেই class-এ নিয়ে যাও। - State-কে context-এ পৌঁছানোর পথ দাও। ফ্যান প্রতিটা state method-এ pass করো। state দুটো কাজে ব্যবহার করবে: ফ্যানের data পড়তে, আর transition চাইতে।
- Status string-কে state object দিয়ে replace করো। Context এখন
private state: FanStateরাখবে আরsetState()method দেবে। - Ladder মুছে দাও। ফ্যানের প্রতিটা public method হবে মাত্র এক লাইন: call forward করো current state-এ।
- Transition wire করো। প্রতিটা state class-এর ভেতরে যখন transition দরকার, তখন
fan.setState(new NextState())call করো।
Refactor-এর পরে একটা button press ভেতরে কী করে দেখো। কোথাও কোনো প্রশ্ন করা হচ্ছে না — কোন object install আছে সেটাই উত্তর দেয়:
আর এই structure টা দেখো যেটা এটা সম্ভব করে:
সবচেয়ে সুন্দর অংশ দেখো: কোনো if-else নেই। "branching" আপনা আপনি হয় — কারণ যে state object install আছে সেই call-এর উত্তর দেয়। Object বেছে নেওয়াটাই branch।
Real code উদাহরণ
চলো TypeScript-এ ফাতেমার ফ্যান পুরোপুরি বানাই। Comment গুলো পড়ো — গল্পটা বলছে।
// ---------- The State interface ----------
// Every state must know how to answer both buttons.
interface FanState {
readonly name: string;
press(fan: CeilingFan): void; // short press of the button
longPress(fan: CeilingFan): void; // long press = switch off
}
// ---------- The Context ----------
// The fan does NOT know speed logic. It only forwards calls.
class CeilingFan {
private state: FanState;
constructor() {
this.state = new OffState(); // every fan starts off
}
setState(next: FanState): void {
console.log(` [fan] state: ${this.state.name} -> ${next.name}`);
this.state = next;
}
// public buttons — each is ONE line, no if-else
pressButton(): void {
this.state.press(this);
}
longPressButton(): void {
this.state.longPress(this);
}
}
// ---------- Concrete states ----------
class OffState implements FanState {
readonly name = "OFF";
press(fan: CeilingFan): void {
console.log("Whirr... blades start turning slowly");
fan.setState(new SlowState()); // Off -> Slow
}
longPress(fan: CeilingFan): void {
console.log("Fan is already off. Nothing to do.");
}
}
class SlowState implements FanState {
readonly name = "SLOW";
press(fan: CeilingFan): void {
console.log("Speeding up to medium");
fan.setState(new MediumState()); // Slow -> Medium
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Slow -> Off
}
}
class MediumState implements FanState {
readonly name = "MEDIUM";
press(fan: CeilingFan): void {
console.log("Full speed! Hold your homework papers!");
fan.setState(new FastState()); // Medium -> Fast
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Medium -> Off
}
}
class FastState implements FanState {
readonly name = "FAST";
press(fan: CeilingFan): void {
console.log("Cycling back to slow");
fan.setState(new SlowState()); // Fast -> Slow
}
longPress(fan: CeilingFan): void {
console.log("Switching off");
fan.setState(new OffState()); // Fast -> Off
}
}
// ---------- Meera uses the fan ----------
const fan = new CeilingFan();
fan.pressButton(); // Off -> Slow
fan.pressButton(); // Slow -> Medium
fan.pressButton(); // Medium -> Fast
fan.longPressButton(); // Fast -> Off
fan.longPressButton(); // still Off — safe, no crashOutput:
Whirr... blades start turning slowly
[fan] state: OFF -> SLOW
Speeding up to medium
[fan] state: SLOW -> MEDIUM
Full speed! Hold your homework papers!
[fan] state: MEDIUM -> FAST
Switching off
[fan] state: FAST -> OFF
Fan is already off. Nothing to do.নিচের client code আবার পড়ো। ফাতেমা একই method pressButton() call করছে, কিন্তু প্রতিবার আলাদা কাজ হচ্ছে। ফ্যান তিনটা state পার করে ফিরে এসেছে — একটাও if ছাড়া। সিদ্ধান্ত নিয়েছে fan.state-এ যে object install ছিল সে।
ফাতেমা, ফ্যান আর state object-দের এই কথোপকথন sequence diagram-এ:
Figure 3-এর Sleep mode এখন যোগ করতে চাইলে? একটা নতুন SleepState class লেখো, transition ঠিক করো, আর FastState-কে রাতে সেটার দিকে point করাও। আর কোনো পুরনো class ছোঁয়া লাগবে না। এটাই Open/Closed Principle: extension-এর জন্য খোলা, modification-এর জন্য বন্ধ।
C#-এ একই ধারণা
ফাতেমার কাজিন তারিক, Dhaka-র একটা university-তে CSE পড়ে, code দেখে বলল: "এই জিনিসই আমরা lab-এ করেছি — traffic signal দিয়ে।" তার version: তিনটা state — Red, Green, Yellow — আর একটা timer যখন "next!" বলে।
// The state interface
interface ISignalState
{
void Next(TrafficSignal signal); // timer fired, move ahead
}
// The context
class TrafficSignal
{
private ISignalState _state = new RedState();
public void SetState(ISignalState next) => _state = next;
public void TimerTick() => _state.Next(this);
}
class RedState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("RED -> GREEN: vehicles, please go");
signal.SetState(new GreenState());
}
}
class GreenState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("GREEN -> YELLOW: slow down");
signal.SetState(new YellowState());
}
}
class YellowState : ISignalState
{
public void Next(TrafficSignal signal)
{
Console.WriteLine("YELLOW -> RED: please stop");
signal.SetState(new RedState());
}
}
// Usage
var signal = new TrafficSignal();
signal.TimerTick(); // RED -> GREEN
signal.TimerTick(); // GREEN -> YELLOW
signal.TimerTick(); // YELLOW -> REDShape একই, গল্প আলাদা। Signal TimerTick() তার current state-এ forward করে, আর প্রতিটা state পরেরটা install করে।
একটা real trick: যখন state class-এ নিজের কোনো data নেই (এই example-এর মতো), তখন প্রতিটা transition-এ নতুন object বানানো অপচয়। তুমি একটা shared instance বানিয়ে সবসময় reuse করতে পারো — signal.SetState(GreenState.Instance)। এটা State pattern-কে Singleton বা Flyweight-এর সাথে combine করে। GoF বইয়েই এই trick আছে।
Python-এ music player
একই pattern, তৃতীয় ভাষা — দেখো idea টা কোনো syntax-এর মধ্যে আটকা নেই। রুবেলের প্রিয় উদাহরণ: music app-এর play button। থামা অবস্থায় মানে "বাজাও", বাজছে অবস্থায় মানে "থামাও", pause-এ মানে "আবার শুরু করো।"
class PlayerState:
"""Common interface: every state answers the play button."""
def press_play(self, player):
raise NotImplementedError
class Stopped(PlayerState):
def press_play(self, player):
print("Starting the song from the beginning")
player.state = Playing()
class Playing(PlayerState):
def press_play(self, player):
print("Pausing the song")
player.state = Paused()
class Paused(PlayerState):
def press_play(self, player):
print("Resuming from the same spot")
player.state = Playing()
class MusicPlayer: # the context
def __init__(self):
self.state = Stopped()
def play_button(self): # one line, no if-else
self.state.press_play(self)
p = MusicPlayer()
p.play_button() # Starting the song from the beginning
p.play_button() # Pausing the song
p.play_button() # Resuming from the same spotএর state diagram — ছোট কিন্তু ফ্যানের মতোই একই প্রজাতির:
Real software-এ কোথায় দেখবে
State pattern শেখার পরে চারদিকে দেখতে পাবে:
- E-commerce-এ order status। Daraz বা Chaldal-এ order Placed → Packed → Shipped → Out for Delivery → Delivered এই পথে যায়। "Cancel" button প্রতিটা state-এ আলাদাভাবে কাজ করে: Placed-এ free cancel, Out for Delivery-তে হয়তো block। এটাই state machine।
- TCP network connection। GoF বইয়েই
TCPConnectionexample ছিল। Connection Listening, Established, বা Closed — একইopen()/close()/send()প্রতিটা state-এ আলাদা। - Game character। Hero Standing, Jumping, Ducking, বা Dashing হতে পারে। Jump button প্রতিটা pose-এ আলাদা কাজ করতে হবে। Game Programming Patterns বইয়ে এই নিয়ে দারুণ একটা chapter আছে।
- Media player। Play/Pause — Stopped, Playing, Paused — আমাদের Python example-এর মতোই।
- Vending machine আর ATM। Idle, CoinInserted, Dispensing — কিংবা CardInserted, PinEntered, CashDispensed। Classic interview example।
- Document workflow। Blog post Draft → In Review → Published → Archived।
publish()প্রতিটা step-এ আলাদা মানে। - Open-source code: iluwatar/java-design-patterns repository-তে tidy Java example আছে। XState (JavaScript) আর Stateless (C#) পুরো tool বানানো হয়েছে state machine-এর উপর।
Software-এ কোথায় state machine বেশি ব্যবহার হয়:
কখন ব্যবহার করবে, কখন না
| পরিস্থিতি | State pattern ব্যবহার করবে? |
|---|---|
| Object-এর ৩+ state আছে আর অনেক method একই status দিয়ে branching করে | ✅ হ্যাঁ — সব ladder সরে যাবে |
| State বাড়তে থাকে (নতুন mode, নতুন status) | ✅ হ্যাঁ — একটা নতুন class, পুরনো কিছু ছোঁয়া লাগে না |
| Transition rules গুলিয়ে যাচ্ছে, ছড়িয়ে আছে | ✅ হ্যাঁ — প্রতিটা state নিজের "পরে কোনটা" জানে |
| স্পষ্টভাবে state machine model করছো (workflow, device, protocol) | ✅ হ্যাঁ — code আর diagram মিলে যাবে |
| মাত্র দুটো simple state, কখনো বাড়বে না | ❌ না — একটা if অনেক পরিষ্কার চারটা class-এর চেয়ে |
| Behavior আসলে status-এর উপর নির্ভর করে না | ❌ না — pattern জোর করে লাগাচ্ছো |
| প্রতিটা state-এর behavior এক লাইনের | ❌ সম্ভবত না — একটা lookup table যথেষ্ট |
| User স্বাধীনভাবে algorithm বেছে নিচ্ছে | ❌ না — সেটা Strategy pattern, State না |
State, Strategy, আর plain if-else-এর মধ্যে stuck থাকলে এই map দেখো। দুটো প্রশ্ন করো: behavior পরিবর্তনটা কে trigger করে (বাইরে থেকে caller, নাকি object নিজে ভেতর থেকে), আর runtime-এ behavior আদৌ বদলায় কিনা:
ফ্যান State territory-তে গভীরে: runtime-এ behavior বদলায়, আর object নিজেই (ফাতেমা না) সিদ্ধান্ত নেয় পরের state কোনটা। Payment choice Strategy territory-তে: runtime-এ বদলায়, কিন্তু customer বেছে নেয়, আর options নিজেরা একে অপরকে push করে না।
ছাত্রছাত্রীরা যে ভুল করে
সবচেয়ে বড় ভুল: state class বানানোর পরেও if-else রেখে দেওয়া। কেউ কেউ OffState আর SlowState বানিয়ে ভেতরে আবার if (state instanceof OffState) লেখে। এটা দুই দিক থেকেই বাজে — বেশি class আর ladder দুটোই আছে। Pattern apply করার পরে context শুধু call forward করবে, state-এর type কখনো check করবে না।
আরও কিছু ফাঁদ:
- State-কে context-এ access দিতে ভুলে যাওয়া। State-কে
fan.setState(...)call করতে হবে। state method-এ fan pass না করলে state behave করতে পারবে কিন্তু transition করতে পারবে না। তখন আবার context-এ if-else ফিরে আসবে। - প্রতিটা state সব state সম্পর্কে জেনে যাওয়া। ছয়টা state যদি বাকি পাঁচটা সম্পর্কে সব জানে, transitions জট পাকিয়ে যায়। প্রতিটা state শুধু তার real neighbors জানুক। অনেক জটিল machine-এ central transition table ব্যবহার করো।
- State-এর ভেতরে shared data রাখা। ফ্যানের data (ঘরের নাম, power rating) context-এ থাকবে। State mostly behavior রাখে। State-এ data রাখলে প্রতিটা transition-এ সেই data হারায়।
- Hot loop-এ বারবার নতুন state object বানানো। State যদি stateless হয়, তাহলে একটা instance share করো (C# section-এর Flyweight/Singleton trick)।
- Strategy দরকার ছিল, State ব্যবহার করা। User যদি menu থেকে স্বাধীনভাবে behavior বেছে নেয় আর behavior গুলো একে অপরকে switch না করে — তাহলে Strategy চাই।
নিজের code test করার সহজ উপায়: গণো কতগুলো method একই status field দিয়ে branching করছে। শূন্য বা একটা — রেখে দাও। তিন বা তার বেশি — State pattern নেওয়া ভালো।
State-এর কাছের আত্মীয়রা
State pattern-এর একটা বিখ্যাত যমজ আর কিছু আত্মীয় আছে। এই table exam-এ কাজে আসবে:
| Pattern | Skeleton | Behavior কে switch করে? | Variants কি একে অপরকে চেনে? | কী model করে |
|---|---|---|---|---|
| State | Context একটা interface-এ delegate করে | State নিজেই setState call করে | হ্যাঁ — Slow জানে পরে Medium | State machine (connected graph) |
| Strategy | Context একটা interface-এ delegate করে (identical!) | Client বেছে install করে | না — strategies একে অপরের অচেনা | বিনিময়যোগ্য algorithm-এর menu |
| Command | Action object হিসেবে wrap করা | Caller queue/undo করে | না | পরে চালানোর request |
| Observer | Subject listeners-কে notify করে | Events সব observer-কে যায় | না | One-to-many notification |
State vs Strategy row-টা বিশেষভাবে পড়ো — কারণ দুটোর class diagram pixel-for-pixel একই: একটা context, একটা interface, কয়েকটা concrete class। পার্থক্য শুধু উদ্দেশ্যে:
- Strategy হলো বাজারে কীভাবে দাম দেবে — cash, bKash, বা card। তুমি (client) বেছে নাও। Cash কখনো বলে না "এখন bKash-এ switch করো।" Options একে অপরকে চেনে না।
- State হলো আমাদের ফ্যানের মতো — Slow নিজেই সিদ্ধান্ত নেয় পরের press মানে Medium। State গুলো chain করে আর context-কে এগিয়ে নেয়।
Interview-এ সবচেয়ে জিজ্ঞেস করা question এটাই — তাই এক লাইনে মনে রাখো: কোনো variant যদি context.setState(someOtherVariant) call করে, সেটা State। শুধু client যদি variant swap করে, সেটা Strategy। আরেকটা clue: State variants-এর মধ্যে arrows আঁকা যায় (Figure 3-এর মতো graph), Strategy variants-এ arrows সম্ভব না (flat list — bKash-এর cash নিয়ে কোনো মতামত নেই)।
আরও দুটো সম্পর্ক এক লাইনে: Bridge একই delegation shape ব্যবহার করে কিন্তু দুটো class hierarchy আলাদাভাবে বাড়ানোর জন্য, state model করতে না। Flyweight/Singleton State-এর সাথে partner হয়ে stateless state object share করে।
পুরো pattern এক পাতায়
Revision box-এর আগে পুরো আলোচনা একটা mind map-এ। এটা memory থেকে আঁকতে পারলে pattern তোমার হয়ে গেছে:
Quick revision
+--------------------------------------------------------------+
| STATE PATTERN — REVISION |
+--------------------------------------------------------------+
| WHAT : Object changes behaviour when its inner state |
| changes; each state = its own class |
| ACTORS : Context (fan) + State interface + concrete states |
| KEY MOVE : Context forwards calls to current state object; |
| states call context.setState(next) themselves |
| KILLS : Repeated if-else ladders on one status field |
| REMEMBER : States know their neighbours (graph); |
| strategies do not (menu) |
| AVOID IF : Only 2 simple states that never change |
| EXAMPLES : Order status, TCP connection, game character, |
| media player, traffic signal, ceiling fan |
+--------------------------------------------------------------+Practice করো
শুধু পড়লে হবে না। Real code লেখো।
- Mobile phone ring mode। একটা
Phonecontext বানাও তিনটা state দিয়ে: Ring, Vibrate, আর Silent। Volume-down button Ring → Vibrate → Silent যায়, আর থামে। Volume-up উল্টো দিকে। Incoming call প্রতিটা state-এ আলাদাভাবে behave করে ("Ringing loudly!", "Bzzz bzzz", "Screen lights up quietly")। প্রতিটা transition print করো, আর আগে কাগজে Figure 3-এর মতো state diagram আঁকো। - ফ্যানে Sleep mode যোগ করো। এই post-এর
CeilingFancode নাও আরSleepStateযোগ করো: রাতে Fast থেকে short press গেলে Sleep, সেখানে ফ্যান আস্তে চলে আর long press এখনো off করে। কতটা পুরনো class edit করতে হলো? (Goal: শুধু একটা — যেটা এখন Sleep-এ point করবে।) - State machine খোঁজো (ভাবার task)। তোমার phone-এর যেকোনো app নাও — music player, food delivery app, game। state গুলো লিখে কাগজে transition diagram আঁকো arrows দিয়ে, Figure 3-এর মতো। কোন buttons আলাদা state-এ আলাদা কাজ করে সেটা mark করো। দেখবে কতগুলো state machine তুমি সকালের নাস্তার আগেই ব্যবহার করো।
- College-level challenge। ফ্যানের সব
new NextState()call-কে একটা central transition table দিয়ে replace করো — একটা map যেখানে (state, event) → next state। আর সব state class-কে shared singleton বানাও। দুটো design compare করো: কোনটা পড়তে সহজ, কোনটা documentation হিসেবে print করতে সহজ?
সচরাচর জিজ্ঞাসা
- State pattern কী — এক লাইনে বলো।
- State pattern মানে হলো একটা object-এর প্রতিটা অবস্থার জন্য আলাদা class বানানো। object তার কাজ সেই class-এ forward করে। অবস্থা বদলালে class বদলায়, behavior নিজে থেকেই বদলে যায়।
- State pattern আর সাধারণ if-else-এর পার্থক্য কী?
- if-else দিয়ে করলে প্রতিটা method-এ একই status check বারবার লিখতে হয়। State pattern সেই ladder সরিয়ে দেয়। প্রতিটা state class নিজের behavior নিজে জানে। নতুন state মানে শুধু একটা নতুন class — পুরনো কোনো method ছোঁয়া লাগে না।
- State pattern আর Strategy pattern কি একই জিনিস?
- দেখতে একদম একই, কিন্তু উদ্দেশ্য আলাদা। Strategy-তে client বেছে নেয় কোন algorithm চলবে — যেমন cash দিয়ে দাম দেবে নাকি bKash-এ। State-এ state নিজেই পরের state ঠিক করে — যেমন fan Slow থেকে নিজে Medium-এ যায়।
- কখন State pattern না ব্যবহার করাই ভালো?
- যদি শুধু দুটো state থাকে আর কখনো বাড়বে না, তাহলে একটা সাধারণ if-else অনেক সহজ আর পরিষ্কার। Pattern ব্যবহার করো যখন state বাড়তে থাকে, অনেক method একই status দিয়ে branching করে, বা transition rules গুলিয়ে যাচ্ছে।
- State কে বদলায় — context নাকি state class?
- সাধারণত state class নিজেই বদলায়। প্রতিটা state জানে তার পরে কোন state আসবে, তাই সে context-কে বলে 'এখন পরেরটা install করো।' এতে transition-এর rule আর behavior একই জায়গায় থাকে।
আরো দেখো
সম্পর্কিত পাঠ
Strategy Pattern: সাইকেল, বাস, নাকি অটো — তুমিই ঠিক করো
Strategy design pattern শেখো একটা সহজ স্কুলে যাওয়ার গল্পের মাধ্যমে — TypeScript আর C# কোড, runtime swapping, বাস্তব উদাহরণ, আর প্র্যাকটিস exercise সহ।
Template Method Pattern: চা আর কফি, একই ধাপ
চায়ের দোকানের গল্প দিয়ে Template Method design pattern শেখো — সহজ TypeScript আর C# কোড, hooks, diagram, বাস্তব উদাহরণ আর practice task সহ।
Visitor Pattern: যে ডাক্তার প্রতিটি class-এ যায়
স্কুলের health check-up গল্পের মাধ্যমে Visitor design pattern শেখো — double dispatch সহজ ভাষায়, TypeScript আর C# কোড, বাস্তব উদাহরণ, আর practice।
Command Pattern: প্রতিটি কাজকে একটি অর্ডার স্লিপে বদলে দাও
রেস্তোরাঁর অর্ডার স্লিপের গল্প দিয়ে Command pattern শেখো। TypeScript আর C#-এ undo ও redo সহ পুরো কোড, diagram, table, আর practice task।