Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle
Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।
রাহিমের টিফিন সার্ভিসের গল্প
একটা ছোট্ট গল্প দিয়ে শুরু করি।
ধরো পুরান ঢাকায় রাহিম ভাই একটা টিফিন সার্ভিস চালান — নাম আল-বরকত টিফিন সার্ভিস। প্রতিদিন ভোরবেলা রান্নাঘরে চুলার আওয়াজ শুরু হয়। ডাল, সবজি, ভাত রান্না হয়। সহকারী ফাতেমা আর জামাল টিফিনবক্সে সব ভরে। তারপর ডেলিভারি বয় রুবেল সাইকেলে করে অফিস আর মেসগুলোতে পৌঁছে দেয়। সন্ধ্যায় রুবেল খালি টিফিনবক্স ফেরত নিয়ে আসে, আর রাহিম ভাই পুরনো খাতায় টিক দেন।
প্রতিদিনের রুটিন একই, school timetable-এর মতো fixed:
- রান্না করো।
- টিফিনবক্স প্যাক করো।
- Vehicle-এ তুলো।
- Deliver করো।
- খালি বক্স ফেরত নাও।
ব্যবসা বাড়ে। রাহিম ভাই highway-র কাছে আরেকটা branch খোলেন, দায়িত্ব দেন ভাতিজা তারিককে। সেখানে কাস্টমার দূরে থাকে, সাইকেলে কুলোয় না — তারিক বাইক ব্যবহার করে। তারপর কাছের IT park থেকে ২০০ টিফিনের অর্ডার আসে। বাইকেও হবে না। রাহিম ভাইয়ের মেয়ে সুমাইয়া সেই branch চালায়, ভ্যান নিয়ে।
একদিন বিকেলে চায়ের দোকানে তিনজন একসাথে বসল। রাহিম ভাই জিজ্ঞেস করলেন, "আমাদের রুটিন কি বদলেছে?" সবাই মাথা নাড়ল না। রান্না, প্যাক, ডেলিভারি, ফেরত — প্রতিটা branch-এ একই পাঁচটা ধাপ। শুধু একটা ধাপ আলাদা: কোন vehicle ব্যবহার করবে। প্রতিটা branch নিজে সেটা ঠিক করে, কিন্তু head office-এর রুটিন কখনো বদলায় না।
এটাই হলো Factory Method pattern-এর আসল কথা। মূল plan একই থাকে। "জিনিসটা বানাও" — এই একটা ধাপ খালি রাখা হয়, আর প্রতিটা branch নিজের মতো সেটা পূরণ করে। রাহিম ভাই, রুবেল, তারিক, সুমাইয়া — এদের কথা মাথায় রাখো, পোস্টের প্রতিটা অংশে এদের সাথে দেখা হবে, আর পুরো code-ও লিখব।
Factory Method pattern আসলে কী?
Factory Method হলো একটা creational design pattern। Creational pattern মানে যেগুলো বলে object কীভাবে বানাতে হয়।
সহজ কথায়: সাধারণত আমরা new Cycle() লিখি সরাসরি। Factory Method বলে — main logic-এর ভেতরে new সরাসরি ডেকো না। একটা special method ডাকো, যেমন createVehicle()। Base class শুধু এই method-টা declare করে। Child class (subclass) সেটা override করে আর ঠিক করে কোন object return করবে।
তাহলে base class শুধু একটা general type নিয়ে কাজ করে, যেমন DeliveryVehicle। সে কখনো জানে না ভেতরে cycle আসবে নাকি বাইক নাকি ভ্যান। সে শুধু বিশ্বাস করে যা আসবে সেটা টিফিন পৌঁছে দিতে পারবে। আমাদের গল্পে রাহিম ভাইয়ের রুটিনে "সাইকেল" শব্দটা নেই। শুধু আছে "vehicle"। রুবেল চুপচাপ সেই জায়গাটা সাইকেল দিয়ে পূরণ করে, তারিক বাইক দিয়ে, সুমাইয়া ভ্যান দিয়ে।
এই pattern-টা এসেছে ১৯৯৪ সালে লেখা বিখ্যাত বই Design Patterns: Elements of Reusable Object-Oriented Software থেকে। চারজন লেখক লিখেছিলেন যাদের আদর করে বলা হয় Gang of Four (GoF)। তারা এটার আরেকটা নাম দিয়েছিলেন: Virtual Constructor — কারণ creation method-টা একটা constructor-এর মতো কাজ করে যেটা subclass বদলাতে পারে।
এক লাইনে মনে রাখো: Factory Method = parent class plan লেখে, কিন্তু প্রতিটা child class ঠিক করে কোন object বানাবে। টিফিন ভাষায়: রাহিম ভাই timetable লেখেন; প্রতিটা branch নিজের vehicle বাছে।
দুটো role খুব গুরুত্বপূর্ণ, এদের নাম মনে রাখো:
| Role | মানে | আমাদের গল্পে |
|---|---|---|
| Creator | যে class-এ factory method আর business logic আছে | টিফিন branch আর তার daily রুটিন |
| Concrete Creator | যে subclass factory method override করে | রুবেলের lane branch, তারিকের highway branch, সুমাইয়ার IT park branch |
| Product | Factory method যে general type return করে | "delivery vehicle" ধারণাটা |
| Concrete Product | Product implement করে যে actual class | Cycle, Bike, Van |
একটু deep ভাবো: "Virtual Constructor" নামটা কিন্তু একদম সঠিক। OOP-এ constructor সাধারণত virtual হয় না — new Cycle() compile time-এই fixed হয়ে যায়। Factory method সেই creation-এ polymorphism ফিরিয়ে আনে: this.createVehicle() call টা runtime-এ vtable দিয়ে dispatch হয়, ঠিক যেকোনো virtual method-এর মতো। এটাই pattern-এর পুরো কৌশল।
যে সমস্যাটা এই pattern সমাধান করে
আগে একটু কষ্টটা অনুভব করি। ধরো রাহিম ভাই একজন part-time programmer ভাড়া করলেন যে কোনো pattern ছাড়াই code লিখল। সে delivery রুটিনের ভেতরেই if-else chain ঢুকিয়ে দিল:
// BAD CODE: the routine and the vehicle choice are mixed together
class TiffinService {
runDeliveryDay(branchType: string, orders: number): void {
console.log("Cooking and packing " + orders + " tiffins...");
if (branchType === "lane") {
console.log("Loading 10 tiffins at a time on the cycle");
console.log("Pedalling slowly through the lanes...");
} else if (branchType === "highway") {
console.log("Loading 30 tiffins on the bike");
console.log("Riding fast on the highway...");
} else if (branchType === "itpark") {
console.log("Loading 200 tiffins in the van");
console.log("Driving to the IT park...");
}
console.log("Collecting empty dabbas in the evening.");
}
}এখন চলছে। রাহিম ভাই এক মাস খুশি। কিন্তু দেখো সামনে কী সমস্যা অপেক্ষা করছে:
- নতুন branch মানে পুরনো code খুলতে হবে। রাহিম ভাই যখন e-rickshaw নিয়ে নতুন branch খুলবেন, programmer-কে এই কাজ করা, test করা method-এ এসে আরেকটা
else ifযোগ করতে হবে। প্রতিটা edit পুরনো কাজে নতুন bug আনতে পারে। e-rickshaw-র code লিখতে গিয়ে সুমাইয়ার ভ্যান-এর কাজ ভেঙে যেতে পারে। - একই
if-elseসব জায়গায় ছড়িয়ে পড়ে। Loading-এ branch check। Fuel cost-এ branch check। Trip count-এ branch check। একসময় দশটা জায়গায় একই chain copy হয়, আর কেউ একদিন নয়টা আপডেট করে দশটা ভুলে যায়। ওই দশ নম্বর জায়গাতেই bug লুকিয়ে থাকে। - রুটিন আর vehicle-এর details একসাথে আটকে আছে। Delivery plan শুধু বলবে "vehicle ব্যবহার করো"। সাইকেল কীভাবে চালাতে হয় বা ভ্যানে fuel কীভাবে দেয় — এটা plan-এর কাজ না। রাহিম ভাইয়ের কী দরকার এটা জানার? তাহলে তাঁর timetable জানবে কেন?
দেখো, নতুন e-rickshaw branch তিনটা আলাদা জায়গায় ধাক্কা দিচ্ছে? Real project-এ সেটা ত্রিশটা হতে পারে। আর অভিজ্ঞতা বলে, creation code-এই সবচেয়ে বেশি এড়ানো যেত এমন bug লুকিয়ে থাকে, কারণ এটাই সবচেয়ে বেশি copy-paste হয় আর সবচেয়ে বেশি edit হয়।
এই ফাঁদটাকে software engineer-রা বলে Open/Closed Principle লঙ্ঘন। এই principle বলে: code extension-এর জন্য open থাকবে, কিন্তু modification-এর জন্য closed। নতুন কিছু যোগ করতে হলে নতুন code লিখব, পুরনো code বদলাব না। রাহিম ভাই এই নিয়ম জেনে না জেনেই মেনে চলেন — নতুন branch খুললে রান্নাঘর নতুন করে বানান না, শুধু একজন নতুন branch in-charge নিয়োগ দেন।
একটু deep ভাবো: এখানে আরো গভীর একটা principle কাজ করছে — Dependency Inversion Principle (SOLID-এর D)। High-level policy (delivery রুটিন) low-level details-এর উপর depend করবে না (Cycle, Van); দুটোই depend করবে একটা abstraction-এর উপর (DeliveryVehicle)। Factory method হলো সেই dependency invert করার সবচেয়ে সস্তা উপায়, কোনো full dependency-injection container না এনেই।
Factory Method কীভাবে কাজ করে, ধাপে ধাপে
এখন ধীরে ধীরে সমস্যাটা ঠিক করি — রাহিম ভাই যেভাবে করতেন, এক একটা sensible সিদ্ধান্ত নিয়ে।
- সব product-কে একটা common interface-এ আনো। Cycle, বাইক, ভ্যান — সবই "delivery vehicle"। তাই একটা interface
DeliveryVehicleবানাও, যেখানে প্রতিটা vehicle-কে যা যা করতে হবে তা declare করা আছে, যেমনdeliverTiffins()। গল্পে এটা রাহিম ভাইয়ের কথা: "কী সেটা আমি জানি না, কিন্তু টিফিন পৌঁছাতে আর সন্ধ্যায় ফিরে আসতে পারতে হবে।" - একটা Creator class বানাও abstract factory method সহ। একটা base class
TiffinBranchবানাও। তাতে একটা special methodcreateVehicle()থাকবে, যেটাDeliveryVehiclereturn করে। Base class এটা implement করবে না — শুধু declare করবে। এটা রাহিম ভাইয়ের timetable-এর খালি ঘরটা। - Business logic শুধু abstract product ব্যবহার করে লেখো।
runDeliveryDay()রুটিন base class-এ থাকবে। সেthis.createVehicle()call করে আর result-টাকে শুধুDeliveryVehicleহিসেবে ব্যবহার করে। Cycle, বাইক, ভ্যান — এই নামগুলো কোথাও নেই। - প্রতিটা variation-এর জন্য একটা করে subclass বানাও।
LaneBranch(রুবেল)createVehicle()override করেCyclereturn করে।HighwayBranch(তারিক)Bikereturn করে।ItParkBranch(সুমাইয়া)Vanreturn করে। - শুধু এক জায়গায় concrete creator বাছো। Program-এর শুরুর জায়গায় (কখনো কখনো composition root বলে) ঠিক করো কোন branch object বানাবে। তারপর থেকে সব কিছু concrete class সম্পর্কে অন্ধ — ঠিক রাহিম ভাইয়ের কাস্টমারদের মতো, যারা শুধু জানে গরম টিফিন আসে।
Arrow-গুলো ভালো করে দেখো। Base class TiffinBranch শুধু interface DeliveryVehicle-এর দিকে point করছে। Concrete creation হচ্ছে নিচের ছোট ছোট subclass-এ। এটাই পুরো কৌশল। এই diagram-এ যা দেখছো সেটার fancy নাম হলো two parallel hierarchies: বাম দিকে creator hierarchy, ডান দিকে product hierarchy, শুধু abstract level-এ যুক্ত।
আর এই পুরো ব্যাপারটা সময়ের সাথে কথোপকথন হিসেবে দেখো। রুটিন তার নিজের subclass-এর কাছে vehicle চাইছে, পাচ্ছে, তারপর অন্ধের মতো ব্যবহার করছে:
Factory method প্রায়ই বড় রুটিনের ভেতরে একটা ছোট্ট ধাপ। Creator-এর আসল কাজ রুটিনটাই; factory method আছে শুধু এই কারণে যাতে subclass বদলাতে পারে কী বানাবে। রাহিম ভাইয়ের timetable-ই star; vehicle-এর লাইনটা তার একটা humble খালি ঘর।
Real code-এর উদাহরণ
এখন TypeScript-এ ঠিকঠাকভাবে আল-বরকত টিফিন সার্ভিস বানাই। ধীরে ধীরে পড়ো, section by section। প্রতিটা line-এর একটা কারণ আছে।
Section 1: Product। আগে common interface আর তিনটা vehicle।
// ---------- PRODUCT ----------
// Every vehicle must promise these things.
interface DeliveryVehicle {
name: string;
capacity: number; // how many tiffins fit in one trip
deliverTiffins(count: number): void;
}
// ---------- CONCRETE PRODUCTS ----------
class Cycle implements DeliveryVehicle {
name = "cycle";
capacity = 10;
deliverTiffins(count: number): void {
console.log(` Raju pedals through the lanes with ${count} tiffins.`);
}
}
class Bike implements DeliveryVehicle {
name = "bike";
capacity = 30;
deliverTiffins(count: number): void {
console.log(` Salim rides on the highway with ${count} tiffins.`);
}
}
class Van implements DeliveryVehicle {
name = "van";
capacity = 200;
deliverTiffins(count: number): void {
console.log(` Kavita drives the van to the IT park with ${count} tiffins.`);
}
}এখনো কিছু fancy না। তিনটা class, একটা shared shape। লক্ষ্য করো প্রতিটা vehicle তার নিজের details জানে — তার capacity আর নিজের delivery style। Branch কখনো এই details জানতে হবে না, ঠিক যেমন রাহিম ভাইকে জানতে হয় না রুবেলের সাইকেলের চাকায় কতটুকু হাওয়া আছে।
Section 2: Creator। এটা pattern-এর হৃদয়।
// ---------- CREATOR ----------
abstract class TiffinBranch {
constructor(public branchName: string) {}
// THE FACTORY METHOD.
// The base class declares it but does not implement it.
protected abstract createVehicle(): DeliveryVehicle;
// The daily routine: same for EVERY branch.
// It talks only to the DeliveryVehicle interface.
runDeliveryDay(orders: number): void {
console.log(`--- ${this.branchName}: delivery day starts ---`);
console.log(`Cooking and packing ${orders} tiffins...`);
const vehicle = this.createVehicle(); // <-- the magic line
let remaining = orders;
let trip = 1;
while (remaining > 0) {
const load = Math.min(vehicle.capacity, remaining);
console.log(`Trip ${trip}: loading ${load} tiffins on the ${vehicle.name}.`);
vehicle.deliverTiffins(load);
remaining -= load;
trip++;
}
console.log(`All ${orders} tiffins delivered. Empty dabbas tomorrow!`);
}
}তিনটা জিনিস খেয়াল করো:
createVehicle()হলোabstract। Base class সৎভাবে বলছে: "কোন vehicle সেটা আমি জানি না। আমার children বলবে।"runDeliveryDay()পুরোপুরি complete business logic। Trip load করা, count করা, message print করা — সব আছে। কিন্তু কোথাওnew Cycle()নেই বাif (branch === "lane")নেই। Type নিয়ে কোনো branching নেই।vehiclevariable-এর typeDeliveryVehicle। রুটিন একইভাবে কাজ করে চাই trip-এ ১০টা ধরুক চাই ২০০টা, কারণ সেvehicle.capacityপড়ে, number hard-code করে না।
Section 3: Concrete Creator। প্রতিটা branch ছোট্ট। শুধু একটা method override করে।
// ---------- CONCRETE CREATORS ----------
class LaneBranch extends TiffinBranch {
protected createVehicle(): DeliveryVehicle {
return new Cycle();
}
}
class HighwayBranch extends TiffinBranch {
protected createVehicle(): DeliveryVehicle {
return new Bike();
}
}
class ItParkBranch extends TiffinBranch {
protected createVehicle(): DeliveryVehicle {
return new Van();
}
}Section 4: Client code। শুধু এই এক জায়গায় concrete class সম্পর্কে জানা আছে।
// ---------- CLIENT ----------
const branches: TiffinBranch[] = [
new LaneBranch("Annapoorna - Old City Lane"),
new HighwayBranch("Annapoorna - Highway"),
new ItParkBranch("Annapoorna - IT Park"),
];
for (const branch of branches) {
branch.runDeliveryDay(35);
}Run করো, দেখবে একই রুটিন তিনটা আলাদা behaviour তৈরি করছে — আর রুটিনের কোথাও vehicle type নিয়ে কোনো if-else নেই। রুবেল ৩৫টা টিফিনের জন্য চারটা cycle trip করে, তারিক দুটো বাইক trip, আর সুমাইয়া একটা ভ্যান trip-এই শেষ।
এখন পুরো গল্পের সেরা অংশ। রাহিম ভাই নতুন বাজার এলাকার জন্য e-rickshaw কিনলেন, দিলেন প্রতিবেশী নাসরিনকে। পুরনো code-এ কী বদলাতে হবে? কিছুই না। শুধু নতুন কিছু যোগ করতে হবে:
class ERickshaw implements DeliveryVehicle {
name = "e-rickshaw";
capacity = 50;
deliverTiffins(count: number): void {
console.log(` Babita glides silently with ${count} tiffins.`);
}
}
class MarketBranch extends TiffinBranch {
protected createVehicle(): DeliveryVehicle {
return new ERickshaw();
}
}পুরনো code অছোঁয়া। পুরনো test এখনো green। রুবেল, তারিক, সুমাইয়া কিছুই টের পেল না। এটাই Open/Closed Principle কাজ করার মজা।
একটু deep ভাবো: এই "add, don't edit" property testing-এও দারুণ কাজে আসে। createVehicle() একটা overridable seam, তাই unit test-এ TiffinBranch-কে subclass করে একটা FakeBranch বানানো যায় যার factory method একটা MockVehicle return করে — সেটা calls record করে। তখন কোনো real vehicle, console, বা I/O ছাড়াই রুটিনের trip-splitting logic test করা যায়। Factory method হলো Working Effectively with Legacy Code বইয়ে বলা classic "seam"-গুলোর একটা।
C#-এ একই ধারণা
C#-এ pattern-টা প্রায় একই দেখায়। এই ছোট version দেখো — বুঝবে ধারণাটা language-independent:
// Product
public interface IDeliveryVehicle
{
string Name { get; }
int Capacity { get; }
void DeliverTiffins(int count);
}
public class Cycle : IDeliveryVehicle
{
public string Name => "cycle";
public int Capacity => 10;
public void DeliverTiffins(int count) =>
Console.WriteLine($" Pedalling with {count} tiffins.");
}
public class Van : IDeliveryVehicle
{
public string Name => "van";
public int Capacity => 200;
public void DeliverTiffins(int count) =>
Console.WriteLine($" Driving with {count} tiffins.");
}
// Creator
public abstract class TiffinBranch
{
// The factory method
protected abstract IDeliveryVehicle CreateVehicle();
public void RunDeliveryDay(int orders)
{
var vehicle = CreateVehicle();
for (int left = orders; left > 0; left -= vehicle.Capacity)
{
vehicle.DeliverTiffins(Math.Min(vehicle.Capacity, left));
}
Console.WriteLine($"All {orders} tiffins delivered.");
}
}
// Concrete creators
public class LaneBranch : TiffinBranch
{
protected override IDeliveryVehicle CreateVehicle() => new Cycle();
}
public class ItParkBranch : TiffinBranch
{
protected override IDeliveryVehicle CreateVehicle() => new Van();
}Shape একই: parent-এ abstract method, child-এ override, parent-এর business logic কোনো concrete class জানে না।
আর এখানে একটা Python variation যেটা real project-এ অনেকে ব্যবহার করে — abstract না করে default implementation সহ factory method। Base branch default-এ cycle return করে, আর যে branch আলাদা কিছু চায় সে শুধু override করে:
class TiffinBranch:
def create_vehicle(self):
# Default factory method: most branches start with a cycle.
return Cycle()
def run_delivery_day(self, orders: int) -> None:
vehicle = self.create_vehicle()
left = orders
while left > 0:
load = min(vehicle.capacity, left)
vehicle.deliver_tiffins(load)
left -= load
class ItParkBranch(TiffinBranch):
def create_vehicle(self):
return Van() # only this branch overrides the defaultএই "default product" variation তখন কাজে আসে যখন একটা product ৮০% কেস cover করে। নতুন branch-গুলো free-তে sensible behaviour পায় আর শুধু যখন সত্যিই আলাদা দরকার তখন override করে — ঠিক যেমন রাহিম ভাই প্রতিটা নতুন branch-কে প্রথম দিন একটা সাইকেল দেন আর পরে upgrade করেন।
Real software-এ কোথায় দেখা যায়
এটা শুধু classroom-এর ধারণা না। বিখ্যাত library আর framework প্রতিদিন Factory Method ব্যবহার করে।
- .NET-এর
ILoggerFactory। .NET program-এ logger দরকার হলেnew ConsoleLogger()লেখা হয় না। Factory-কে জিজ্ঞেস করা হয়:loggerFactory.CreateLogger("MyApp")। Factory ঠিক করে logger console-এ লিখবে নাকি file-এ নাকি cloud service-এ। তোমার code শুধুILoggerinterface দেখে — ঠিক আমাদের branch যেমন শুধুDeliveryVehicleদেখে। - Java-র
Calendar.getInstance()আরNumberFormat.getInstance()। তুমি "একটা calendar" বা "একটা number format" চাও, Java তোমার locale আর settings-এর জন্য সঠিক concrete class return করে। তুমি নিজে concrete class-এর নাম বলো না। - SLF4J-এর
LoggerFactory.getLogger()Java world-এ একইভাবে কাজ করে: call site fixed থাকে, নিচের actual logger implementation বদলানো যায়। - ADO.NET-এর
DbCommandcreation। Database code connection-কে বলে command বানাতে, আর SQL Server, MySQL, বা Oracle-এর জন্য সঠিক concrete command ফিরে আসে। - GUI framework-গুলো factory method ব্যবহার করে যাতে framework "create a button" call করতে পারে আর তোমার subclass custom button দিতে পারে, framework fork না করেই। Framework হলো রাহিম ভাইয়ের timetable; তোমার subclass হলো branch।
Real, ভালোভাবে organize করা implementation পড়তে চাইলে দুটো GitHub repo দারুণ: iluwatar/java-design-patterns (Java, প্রতিটা pattern-এ diagram আর explanation সহ) আর faif/python-patterns (ছোট, পড়তে সহজ Python version)। Refactoring Guru-এর page-টাও ছবিসহ friendly।
কখন ব্যবহার করবে, কখন না
Pattern একটা tool, rule না। আগে তোমার project-টা এই map-এ locate করো। আল-বরকত টিফিন সার্ভিস উপরের ডান কোণে: অনেক vehicle type আছে আর আরো আসতে থাকবে।
তারপর এই table দিয়ে confirm করো:
| পরিস্থিতি | Factory Method ব্যবহার করবে? | কারণ |
|---|---|---|
| পরে আরো product type আসবে নিশ্চিত (আরো vehicle, আরো logger) | হ্যাঁ | নতুন type মানে নতুন subclass; পুরনো code safe থাকে |
| Framework বা library লিখছো যেটা অন্যরা extend করবে | হ্যাঁ | User factory method override করে নিজের product plug in করতে পারে |
| Object creation-এ extra logic আছে (caching, pooling, validation) | হ্যাঁ | Factory method সেই logic-কে একটা named home দেয় |
| Creator hierarchy আগেই আছে অন্য কারণে | হ্যাঁ | Pattern প্রায় free-তে fit হয়ে যায় |
| ঠিক একটাই product type, কখনো বদলাবে না | না | Plain new ছোট আর পরিষ্কার |
| শুধু একটা ছোট creation function দরকার, কোনো shared রুটিন নেই | না | Simple factory function-ই যথেষ্ট; inheritance দরকার নেই |
| Related object-এর পুরো একটা পরিবার বানাতে হবে | না | সেটা Abstract Factory-র কাজ, বড় cousin |
নতুন type আসলে pattern আসলে কী দেয়? ভাবো নাসরিনের e-rickshaw branch খোলার দিনটার কথা। Pattern ছাড়া programmer পুরো project-এ সব if-else খুঁজে বের করত। Pattern-এ এটা এক নতুন product class আর একটা ছোট্ট subclass:
Zero edited lines কোনো বাড়িয়ে বলা না — এটাই পুরো বিষয়। নতুন behaviour আসে একদম নতুন files হিসেবে।
একটু deep ভাবো: সৎ cost-এর কথাও বলি: Factory Method একটা parallel class hierarchy তৈরি করে — প্রতিটা product-এর জন্য একটা creator subclass। ১৫টা product থাকলে ১৫টা প্রায় খালি creator class বহন করতে হতে পারে। First-class function আছে এমন language-এ team প্রায়ই hierarchy-র বদলে একটা injected creation function ব্যবহার করে (যেমন constructor-এ () => DeliveryVehicle lambda pass করা)। এটা একই pattern, inheritance-এর বদলে composition দিয়ে, আর modern TypeScript-এ এটাই prefer করা হয়।
একটা টিফিন অর্ডারের জীবনচক্র
একটা single অর্ডারের life state হিসেবে দেখাও সাহায্য করে। খেয়াল করো factory method কোথায় fire করে: ঠিক অর্ডার accept করার পরে আর vehicle load করার আগে।
VehicleReady-র আগে আর পরের প্রতিটা state সব branch-এ একই। Pattern ঠিক একটা transition-কে isolate করে আর সেটাকে vary করতে দেয়। নিজের factory method design করার সময়, এই ধরনের diagram আঁকো আর জিজ্ঞেস করো: "আমার variants-এর মধ্যে আসলে কোন একটা transition আলাদা?" যদি উত্তর হয় "অনেকগুলো transition", তাহলে হয়তো অন্য pattern দরকার (Template Method একাধিক hook সহ, অথবা Strategy object)।
Cousin pattern-গুলোর সাথে তুলনা
Factory Method-এর creational family-তে বিখ্যাত আত্মীয় আছে। এদের আলাদা করার উপায়:
| Pattern | কী বানায় | কোন প্রশ্নের উত্তর | Shape |
|---|---|---|---|
| Factory Method | একটা product | "এই ধাপে কোন একটা object বানাব?" | Creator hierarchy-তে একটা overridable method |
| Abstract Factory | Related product-এর পরিবার | "কোন matching set of object দরকার?" | অনেক create method সহ একটা interface |
| Builder | একটা complex product | "এই বড় object কীভাবে step by step assemble করব?" | build() দিয়ে শেষ হওয়া chained step method |
| Prototype | Product-এর একটা copy | "নতুন বানানোর বদলে existing object clone করতে পারি?" | একটা clone() method |
| Simple factory (idiom) | একটা product | "একটা function-এ switch দিয়ে class বাছতে পারি?" | একটা single static function (GoF pattern না) |
টিফিন ভাষায়: Factory Method vehicle বাছে। Abstract Factory (পরের post!) একটা পুরো matching thali set দেয়। Builder একটা জটিল জিনিস ধাপে ধাপে তৈরি করে। Prototype একটা existing অর্ডার photocopy করে।
ছোট একটা note: Factory Method আসলে Template Method pattern-এর special case। runDeliveryDay() রুটিনটা একটা template — fixed skeleton — আর createVehicle() হলো সেই একটা ধাপ যেটা children customize করে। অনেক design Factory Method দিয়ে শুরু হয় কারণ এটা simple, আর পরে Abstract Factory-তে বড় হয় যখন আরো related product দরকার হয়।
ছাত্রছাত্রীরা যে ভুলগুলো করে
ভুল ১: Factory method-এর ভেতরে আবার if-else ঢুকানো। অনেকে এক createVehicle(type) লেখে base class-এ বড় switch দিয়ে। এটা "simple factory", Factory Method pattern না — আর নতুন type আসলে switch-ই edit করতে হয়। এটা রাহিম ভাই নিজে প্রতিদিন সকালে প্রতিটা branch-এ দৌড়ে গিয়ে vehicle বেছে দেওয়ার মতো। অনেক type থাকলে প্রতি type-এ এক subclass বানাও।
ভুল ২: রুটিনকে concrete type দেখতে দেওয়া। runDeliveryDay() এর ভেতরে if (vehicle instanceof Van) লেখা মানে পুরো benefit নষ্ট। রুটিন অন্ধ থাকবেই। Type check করার ইচ্ছা হলে মনে রাখো, সেই missing behaviour আসলে product class-এর ভেতরেই থাকা উচিত।
ভুল ৩: সবকিছুর জন্য factory বানানো। দুটো field-এর simple Student object-এর জন্য factory বানানো over-engineering। Pattern সমস্যা সমাধান করে। সমস্যা নেই, pattern নেই।
ভুল ৪: Single configuration point ভুলে যাওয়া। Concrete creator বাছতে হবে এক জায়গায় (program-এর entry point বা config)। পুরো app-এ new LaneBranch() ছড়িয়ে দিলে coupling সমস্যাটাকে শুধু নতুন ঠিকানায় সরানো হয়।
Revision-এর সময় quick দেখার জন্য এই table:
| ভুল | কেমন দেখায় | সমাধান |
|---|---|---|
| Factory method-এ switch | createVehicle(type) এ বড় switch | প্রতি type-এ এক concrete creator |
| রুটিন type দেখে | instanceof Van check রুটিনে | behaviour product class-এ নিয়ে যাও |
| সব জায়গায় pattern | দুই field-এর object-এ factory | ব্যথা না হওয়া পর্যন্ত plain constructor ব্যবহার করো |
| ছড়ানো concrete creator | বিশটা file-এ new LaneBranch() | একটা composition root creator বাছে |
Quick revision box
+===============================================================+
| FACTORY METHOD - QUICK REVISION |
+===============================================================+
| IDEA : Parent declares createX(); children decide |
| which concrete object to return. |
| ALSO CALLED: Virtual Constructor (GoF, 1994) |
|---------------------------------------------------------------|
| PARTICIPANTS |
| Product -> DeliveryVehicle (interface) |
| ConcreteProduct -> Cycle, Bike, Van |
| Creator -> TiffinBranch (has routine + factory) |
| ConcreteCreator -> LaneBranch, HighwayBranch, ItParkBranch|
|---------------------------------------------------------------|
| GOLDEN RULES |
| 1. Routine talks ONLY to the abstract product. |
| 2. New type = new subclass, NOT new if-else. |
| 3. Pick the concrete creator in ONE place only. |
|---------------------------------------------------------------|
| REAL LIFE : ILoggerFactory (.NET), Calendar.getInstance(), |
| SLF4J LoggerFactory.getLogger() |
| REMEMBER : "Same tiffin routine, different vehicle." |
+===============================================================+Practice করার সময়
এবার নিজে code রান্না করো! এই কাজগুলো try করো:
- Drone branch যোগ করো। আল-বরকত টিফিন পাহাড়ের উপরের hostel-এ drone delivery test করছে। একটা
Droneproduct বানাও (capacity 2, অনেক দ্রুত) আর একটাHillBranchcreator। Confirm করো পুরনো এক লাইনও edit করোনি। - Notification factory। একটা school app exam reminder পাঠায়। একটা
Notifierinterface বানাওsend(message)সহ, product হিসেবেSmsNotifier,EmailNotifier, আরWhatsAppNotifier, আর একটা creatorReminderServiceযার রুটিন message format করে, factory method call করে, আর পাঠায়। প্রতিটা channel-এর জন্য এক subclass বানাও। - Code লেখার আগে diagram আঁকো। Exercise 2-এর জন্য নিজে চিত্র ৫ (class diagram) আর চিত্র ৯ (state diagram)-এর মতো আঁকো। ঠিক কোন transition-এ factory method fire করে সেটা mark করো।
- ভাবো আর লেখো (code নয়)। তোমার বন্ধু বলছে, "আমি শুধু একটা function
makeVehicle(type: string)লিখব switch দিয়ে — এত class কেন?" ৪-৫ বাক্যে explain করো তোমার বন্ধুর simple factory কখন ঠিক আছে, আর কখন full Factory Method pattern ভালো। - Deep challenge। Creator hierarchy-র বদলে composition ব্যবহার করো:
TiffinBranch-এর constructor-এ() => DeliveryVehicletype-এর একটা parameter দাও আর তিনটা subclass মুছে দাও। দুটো design তুলনা করো — তুমি কোনটা ship করতে, আর কেন?
এতদূর পড়ার জন্য ধন্যবাদ। রাহিম ভাইয়ের টিফিন সার্ভিস তোমাকে সবচেয়ে ছোট, বন্ধুবৎসল factory শেখাল। এরপর দেখা হবে বড় cousin-এর সাথে: Abstract Factory pattern, যেখানে একটা বিয়ের বাড়ির রান্নাঘর পুরো পরিবার matching object বানায়।
সচরাচর জিজ্ঞাসা
- Factory Method pattern এক লাইনে কী?
- এটা object বানানোর এমন একটা পদ্ধতি যেখানে main class শুধু একটা special method-এর মাধ্যমে object চায়, আর child class ঠিক করে কোন object বানাবে। মানে হলো, বানানোর সিদ্ধান্তটা main logic থেকে বের হয়ে ছোট ছোট আলাদা class-এ চলে যায়।
- Factory Method আর simple factory function কি একই?
- না, ঠিক এক না। Simple factory হলো একটা function যার ভেতরে if-else দিয়ে বিভিন্ন object return করা হয়। Factory Method inheritance ব্যবহার করে — base class creation method declare করে, আর প্রতিটা subclass সেটা override করে। Simple factory সহজ, কিন্তু নতুন type আসলে সেটাই edit করতে হয়।
- সরাসরি new keyword ব্যবহার করলে সমস্যা কী?
- new সরাসরি ব্যবহার করলে তোমার code একটা fixed class-এর সাথে আটকে যায়। কাল যদি অন্য class দরকার হয়, তাহলে অনেক জায়গায় গিয়ে পুরনো code খুলে বদলাতে হবে। Factory Method সেই সিদ্ধান্তটা এক জায়গায় রাখে — নতুন type মানে নতুন code লেখা, পুরনো code ছোঁয়া না।
- কখন Factory Method বেশি হয়ে যায়?
- যদি তুমি সবসময় একই type-এর object বানাও আর সেটা কখনো বদলাবে না, তাহলে plain constructor-ই যথেষ্ট। একটা fixed type-এর জন্য factory class বানানো মানে code লম্বা করা, কোনো লাভ নেই। Pattern ব্যবহার করো তখনই যখন ভবিষ্যতে আরো type আসবে বলে মনে হয়।
- Factory Method আর Abstract Factory-র পার্থক্য কী?
- Factory Method একটা overridable method দিয়ে একটাই product বানায়। Abstract Factory বড় — এটা একসাথে related product-এর পুরো একটা পরিবার বানায়, যেমন একটা পুরো matching set। আসলে Abstract Factory সাধারণত কয়েকটা Factory Method দিয়েই তৈরি হয়।
আরো দেখো
সম্পর্কিত পাঠ
Abstract Factory Pattern: এক অর্ডার, এক ম্যাচিং থালি
বাংলাদেশি বিয়ের ক্যাটারিং গল্পের মাধ্যমে Abstract Factory design pattern বুঝে নাও — সহজ TypeScript ও Python কোড, diagram, আর practice সহ।
Builder Pattern: একটু একটু করে বানাও, ওস্তাদ দর্জির মতো
ঢাকার একটা দর্জির দোকানের গল্পের মাধ্যমে Builder design pattern শেখো — fluent TypeScript ও C# কোড, telescoping constructor এর সমস্যা, আর practice টাস্কসহ।
Singleton Pattern: পুরো স্কুলে একজনই হেড স্যার
স্কুলের হেড স্যারের গল্প দিয়ে Singleton design pattern বোঝো — সহজ TypeScript ও C# কোড, thread safety, আর কেন অনেক সিনিয়র ডেভেলপার এটাকে anti-pattern বলেন।
Prototype Pattern: জিরো থেকে না বানিয়ে ফটোকপি করো
বিয়ের কার্ডের দোকানের গল্প দিয়ে Prototype design pattern শেখো — TypeScript আর Python-এর সহজ উদাহরণ, আর shallow vs deep copy-এর পরিষ্কার demo সহ।