আগের পর্বে ৫ টি Behavioral Design Patterns নিয়ে আলোচনা করেছি । এই পর্বে আরো ৫ টি Behavioral Design Pattern নিয়ে আলোচনা করবো ।
সেগুলো হলো ঃ-
১। Chain of Responsibility Design Pattern
২। Mediator Design Pattern
৩। Interpreter Pattern
৪। Observer Design Patter
৫। State Design Pattern
Chain of Responsibility Design Pattern
এই pattern এর মূল কাজ হচ্ছে, কোনো একটি request কে একাধিক handler এর মধ্যে pass করা, যতক্ষণ না সেই request এর জন্য উপযুক্ত handler মিলে। একে বলা যেতে পারে এক ধরনের chain যেখানে একের পর এক handler কাজ করে, যদি তারা পারে সেই request কে handle করতে, নাহলে তারা chain এ পরের handler এর কাছে forward করে।
ধরি আমাদেরকে একটি logging system তৈরি করতে হবে যেখানে বিভিন্ন level এর log থাকবে—info, warning, error। আমরা চাই এই system এ request আসলে, সেটা প্রথমে info logger চেক করবে, যদি না পারে তাহলে সেটা warning logger এবং সবশেষে error logger চেক করবে। এক্ষেত্রে, chain of responsibility design pattern ব্যবহার করা যেতে পারে।
প্রথমে আমাদের একটি abstract class তৈরি করতে হবে যা একটি interface তৈরি করবে। প্রতিটি handler কে এই interface implement করতে হবে।
class Logger {
constructor(level) {
this.level = level;
this.nextLogger = null;
}
setNextLogger(nextLogger) {
this.nextLogger = nextLogger;
}
logMessage(level, message) {
if (this.level <= level) {
this.write(message);
}
if (this.nextLogger !== null) {
this.nextLogger.logMessage(level, message);
}
}
// Abstract method
write(message) {
throw new Error("This method should be overridden!");
}
}
এই Logger ক্লাস আমাদের abstract class হিসেবে কাজ করছে। এর মধ্যে setNextLogger method টি chain এর পরবর্তী logger সেট করতে সাহায্য করছে। logMessage method টি মূলত request process করবে এবং chain এর মাধ্যমে request forward করবে।
এবার আমরা info, warning, এবং error logger তৈরি করবো, যেগুলো Logger class extend করবে:
class InfoLogger extends Logger {
constructor(level) {
super(level);
}
write(message) {
console.log("Info: " + message);
}
}
class WarningLogger extends Logger {
constructor(level) {
super(level);
}
write(message) {
console.log("Warning: " + message);
}
}
class ErrorLogger extends Logger {
constructor(level) {
super(level);
}
write(message) {
console.log("Error: " + message);
}
}
প্রতিটি Logger class তার নিজস্ব write method override করে, যেখানে message console এ log করা হবে।
এবার আমরা logger গুলো chain আকারে configure করবো:
function getChainOfLoggers() {
const errorLogger = new ErrorLogger(3);
const warningLogger = new WarningLogger(2);
const infoLogger = new InfoLogger(1);
infoLogger.setNextLogger(warningLogger);
warningLogger.setNextLogger(errorLogger);
return infoLogger;
}
এখানে getChainOfLoggers function টি আমাদের logger গুলোর chain তৈরি করছে। InfoLogger প্রথমে কাজ করবে, তারপর WarningLogger এবং সবশেষে ErrorLogger।
Mediator Design Pattern in JavaScript
এই pattern এর মূল কাজ হচ্ছে, একাধিক object এর মধ্যে সরাসরি যোগাযোগ না করিয়ে একটি mediator এর মাধ্যমে যোগাযোগ করানো। এতে object গুলোর মধ্যে dependency কমে যায় এবং তাদের মধ্যে কম coupling তৈরি হয়।
ধরি আমাদের একটি chat room তৈরি করতে হবে যেখানে বিভিন্ন user একে অপরের সাথে communicate করবে। আমরা চাই এই communication সরাসরি না হয়ে একটি mediator এর মাধ্যমে হোক, যা message pass করবে।
প্রথমে আমাদেরকে একটি Mediator class তৈরি করতে হবে যা communication পরিচালনা করবে।
class ChatRoom {
showMessage(user, message) {
const time = new Date().toLocaleTimeString();
const sender = user.getName();
console.log(`[${time}] ${sender}: ${message}`);
}
}
এই ChatRoom ক্লাস হচ্ছে আমাদের mediator যা user গুলোর মধ্যে message pass করবে।
এবার আমরা User class তৈরি করবো, যেটি mediator এর সাথে communicate করবে:
class User {
constructor(name, chatMediator) {
this.name = name;
this.chatMediator = chatMediator;
}
getName() {
return this.name;
}
sendMessage(message) {
this.chatMediator.showMessage(this, message);
}
}
এখানে User class এর sendMessage method টি mediator এর showMessage method call করে message send করছে।
এবার আমরা কিছু user তৈরি করবো এবং তাদের মাধ্যমে communication করবো ChatRoom এর mediator ব্যবহার করে:
const chatRoom = new ChatRoom();
const user1 = new User("Alice", chatRoom);
const user2 = new User("Bob", chatRoom);
user1.sendMessage("Hello, Bob!");
user2.sendMessage("Hi, Alice!");
এই Mediator Design Pattern এর মাধ্যমে আমরা user গুলোর মধ্যে সরাসরি যোগাযোগ না করিয়ে mediator ব্যবহার করেছি। User class গুলো একে অপরের সাথে কোনো dependency ছাড়াই ChatRoom class এর মাধ্যমে communicate করছে।
Interpreter Design Pattern
এই pattern এর মূল কাজ হচ্ছে, একটি language এর grammar কে define করা এবং সেই grammar অনুযায়ী expression গুলোর interpretation করা। এটি সাধারণত এমন ক্ষেত্রে ব্যবহৃত হয় যেখানে একটি language বা expression system কে process করতে হবে।
ধরি আমাদের একটি simple math expression evaluator তৈরি করতে হবে, যেখানে আমরা addition এবং subtraction interpret করতে চাই। এক্ষেত্রে, আমরা Interpreter Pattern ব্যবহার করতে পারি, যা expression গুলোকে interpret করবে এবং তাদের result return করবে।
প্রথমে আমরা একটি abstract expression তৈরি করবো যা আমাদের expression গুলোকে represent করবে। এই expression গুলো আমাদের language এর grammar হিসেবে কাজ করবে।
class Expression {
interpret(context) {
throw new Error("This method should be overridden!");
}
}
এবার আমরা দুটি concrete expression তৈরি করবো—একটি addition এর জন্য এবং অন্যটি subtraction এর জন্য।
class NumberExpression extends Expression {
constructor(number) {
super();
this.number = number;
}
interpret() {
return this.number;
}
}
class AddExpression extends Expression {
constructor(leftExpression, rightExpression) {
super();
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
interpret() {
return this.leftExpression.interpret() + this.rightExpression.interpret();
}
}
class SubtractExpression extends Expression {
constructor(leftExpression, rightExpression) {
super();
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
interpret() {
return this.leftExpression.interpret() - this.rightExpression.interpret();
}
}
এখানে NumberExpression শুধুমাত্র একটি সংখ্যা return করে, এবং AddExpression ও SubtractExpression দুইটি expression কে interpret করে তাদের যোগ বা বিয়োগ করে।
এখন আমরা বিভিন্ন expression interpret করে দেখতে পারি
// 10 + 5
const expression1 = new AddExpression(new NumberExpression(10), new NumberExpression(5));
console.log(expression1.interpret()); // Output: 15
// 10 - 5
const expression2 = new SubtractExpression(new NumberExpression(10), new NumberExpression(5));
console.log(expression2.interpret()); // Output: 5
// (10 + 5) - 3
const expression3 = new SubtractExpression(
new AddExpression(new NumberExpression(10), new NumberExpression(5)),
new NumberExpression(3)
);
console.log(expression3.interpret()); // Output: 12
এই উদাহরণে, আমরা Interpreter Design Pattern ব্যবহার করে একটি mini language তৈরি করেছি যা addition এবং subtraction handle করে। NumberExpression কেবল একটি সংখ্যা return করে, আর AddExpression এবং SubtractExpression তাদের নির্ধারিত mathematical operation handle করে।
Observer Design Patter
এই pattern এর কাজ হচ্ছে, একটি subject (also known as observable) এবং এক বা একাধিক observer এর মধ্যে one-to-many relationship তৈরি করা। যখন subject এ কোনো পরিবর্তন আসে, তখন সেই পরিবর্তন সকল observer কে নোটিফাই করা হয়।
ধরি আমাদের একটি weather station রয়েছে, যা বিভিন্ন temperature display কে আপডেট করবে যখন তাপমাত্রা পরিবর্তন হবে। এক্ষেত্রে, Observer Design Pattern ব্যবহার করে আমরা এটি implement করতে পারি। WeatherStation হবে আমাদের subject, এবং TemperatureDisplay গুলো হবে observers।
প্রথমে আমাদের Subject class তৈরি করতে হবে, যেখানে আমরা observer গুলোকে subscribe, unsubscribe, এবং notify করবো।
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
এখানে, subscribe method নতুন observer কে observers array তে যোগ করে, আর unsubscribe method সেই observer কে সরিয়ে দেয়। notify method দিয়ে সকল observer কে আপডেট করা হয়।
এবার আমাদের observer class তৈরি করতে হবে, যেখানে update method থাকবে যা subject থেকে data নেবে এবং কিছু কাজ করবে।
class Observer {
update(data) {
throw new Error("This method should be overridden!");
}
}
Observer class হচ্ছে একটি abstract class, যেখানে update method কে override করে নতুন behavior define করা যাবে।
এখন আমরা TemperatureDisplay নামের একটি concrete observer তৈরি করবো, যা তাপমাত্রার আপডেট পাবে এবং সেই অনুযায়ী কাজ করবে।
class TemperatureDisplay extends Observer {
update(temperature) {
console.log(`Temperature Display: The current temperature is ${temperature}°C`);
}
}
এখানে, TemperatureDisplay তাপমাত্রার পরিবর্তন receive করে এবং তা console এ প্রদর্শন করে।
এবার আমরা আমাদের WeatherStation তৈরি করবো, যেটি একটি subject হিসেবে কাজ করবে এবং তাপমাত্রার পরিবর্তন observer দের কাছে পাঠাবে।
class WeatherStation extends Subject {
setTemperature(temp) {
console.log(`Weather Station: New temperature measurement: ${temp}°C`);
this.notify(temp);
}
}
এখানে, WeatherStation তাপমাত্রা সেট করার জন্য setTemperature method ব্যবহার করছে এবং প্রতিটি observer কে নতুন তাপমাত্রা পাঠানোর জন্য notify method call করছে।
এবার আমরা WeatherStation এবং TemperatureDisplay ব্যবহার করে দেখবো কিভাবে Observer Pattern কাজ করে:
const weatherStation = new WeatherStation();
const tempDisplay1 = new TemperatureDisplay();
const tempDisplay2 = new TemperatureDisplay();
weatherStation.subscribe(tempDisplay1);
weatherStation.subscribe(tempDisplay2);
weatherStation.setTemperature(25); // Output: Both displays show 25°C
weatherStation.setTemperature(30); // Output: Both displays show 30°C
weatherStation.unsubscribe(tempDisplay1);
weatherStation.setTemperature(35); // Output: Only tempDisplay2 shows 35°C
এখানে WeatherStation হচ্ছে subject, এবং TemperatureDisplay গুলো হচ্ছে observers। যখন setTemperature method call করা হয়, তখন এটি সকল observer কে নোটিফাই করে যে নতুন temperature পাওয়া গেছে। observers সেই temperature নিয়ে তাদের নিজ নিজ update method call করে।
State Design Pattern
এই pattern এর কাজ হচ্ছে, একটি object তার অভ্যন্তরীণ state এর উপর ভিত্তি করে আচরণ পরিবর্তন করতে পারে। এটি object গুলোর behavior কে encapsulate করে রাখে এবং বিভিন্ন state অনুযায়ী আলাদা behavior নির্ধারণ করতে দেয়।
ধরি আমরা একটি document তৈরির application তৈরি করছি যেখানে document বিভিন্ন state এ থাকতে পারে—যেমন Draft, Moderation, এবং Published। প্রতিটি state এর জন্য আলাদা behavior দরকার হবে। এক্ষেত্রে, State Design Pattern ব্যবহার করে আমরা এটি implement করতে পারি।
প্রথমে আমরা একটি State interface বা class তৈরি করবো, যা আমাদের বিভিন্ন state কে represent করবে।
class State {
publish(context) {
throw new Error("This method should be overridden!");
}
moderate(context) {
throw new Error("This method should be overridden!");
}
}
এই State class হচ্ছে abstract এবং এতে দুইটি method থাকবে—publish এবং moderate—যা প্রতিটি state এর জন্য আলাদা ভাবে implement করা হবে।
এবার আমরা বিভিন্ন state তৈরি করবো, যেমন DraftState, ModerationState, এবং PublishedState।
class DraftState extends State {
publish(context) {
console.log("Document is now in moderation.");
context.setState(new ModerationState());
}
moderate(context) {
console.log("Drafts cannot be moderated.");
}
}
class ModerationState extends State {
publish(context) {
console.log("Document is now published.");
context.setState(new PublishedState());
}
moderate(context) {
console.log("Document is being reviewed by moderators.");
}
}
class PublishedState extends State {
publish(context) {
console.log("Document is already published.");
}
moderate(context) {
console.log("Published documents cannot be moderated.");
}
}
খানে প্রতিটি state class এর নিজস্ব behavior আছে। DraftState থেকে ModerationState এ যাওয়া যায়, এবং ModerationState থেকে PublishedState এ যাওয়া যায়। PublishedState এ পৌঁছানোর পর আর publish বা moderate করা যাবে না । এখন আমাদের একটি Document class তৈরি করতে হবে, যা state management পরিচালনা করবে।
class Document {
constructor() {
this.state = new DraftState();
}
setState(state) {
this.state = state;
}
publish() {
this.state.publish(this);
}
moderate() {
this.state.moderate(this);
}
}
Document class এর মাধ্যমে আমরা state পরিবর্তন করতে পারি এবং তার বর্তমান state অনুযায়ী বিভিন্ন behavior implement করতে পারি।
এবার আমরা Document class এবং তার বিভিন্ন state ব্যবহার করে দেখবো:
const document = new Document();
document.publish(); // Output: Document is now in moderation.
document.moderate(); // Output: Document is being reviewed by moderators.
document.publish(); // Output: Document is now published.
document.publish(); // Output: Document is already published.
document.moderate(); // Output: Published documents cannot be moderated.
এখানে Document class হচ্ছে context, এবং তার state অনুযায়ী তার behavior পরিবর্তন হচ্ছে। যখন DraftState এ document থাকে, তখন এটি ModerationState এ যেতে পারে এবং যখন ModerationState এ থাকে তখন এটি PublishedState এ যেতে পারে। PublishedState এ পৌঁছানোর পর document আর publish বা moderate করা যাবে না।
কখন ব্যবহার করবেন:
Chain of Responsibility Design Pattern : যখন multiple objects sequentially request বা event handle করবে এবং সেই request/processing কে chain আকারে একের পর এক object এ pass করতে হবে। আপনি যদি চান কোনো request নির্দিষ্ট handler দ্বারা process হবে, এবং যদি না পারে, তাহলে সেই request কে chain এ পরবর্তী handler এ forward করা হবে। কটি support system, যেখানে ticket গুলো প্রথমে support staff, পরে manager এবং শেষে director handle করে যদি আগের জন resolve না করতে পারে।
Mediator Design Pattern : যখন আপনি চান multiple objects নিজেদের মধ্যে সরাসরি যোগাযোগ না করে, বরং একটি mediator object এর মাধ্যমে যোগাযোগ করবে।Object গুলো একে অপরের উপর tightly coupled না থেকে mediator এর মাধ্যমে loose coupling এ কাজ করবে।উদাহরণ: একটি chat room, যেখানে user গুলো mediator এর মাধ্যমে message পাঠাবে এবং mediator message distribute করবে।
Interpreter Design Pattern : যখন একটি language বা grammar তৈরি করতে হবে, এবং সেই grammar অনুযায়ী expression গুলোকে interpret করতে হবে।আপনাকে যখন একটি নির্দিষ্ট syntax বা language কে parse এবং evaluate করতে হবে।উদাহরণ: একটি calculator তৈরি করা যেখানে mathematical expressions interpret করতে হবে।
Observer Design Pattern : যখন object গুলোতে one-to-many dependency তৈরি করতে হবে। অর্থাৎ, একটি object এর পরিবর্তন হলে অনেকগুলো object কে সেই পরিবর্তনের নোটিফিকেশন দিতে হবে।উদাহরণ: একটি weather station, যা temperature পরিবর্তন হলে সকল display device কে notify করবে।
State Design Pattern : যখন কোনো object এর behavior তার state এর উপর ভিত্তি করে পরিবর্তিত হবে।Object গুলো dynamically state পরিবর্তন করবে এবং সেই state অনুযায়ী behavior define করবে।উদাহরণ: একটি document, যা draft, review, এবং published state অনুযায়ী ভিন্ন ভিন্ন behavior দেখাবে।
