Replace Constructor with Factory Method: নামে অর্ডার করো, রান্নাঘর ঠিক করুক
Replace Constructor with Factory Method রিফ্যাক্টরিং শেখো একটা ক্যান্টিনের গল্পের মাধ্যমে — before/after TypeScript ও C# কোড, নিরাপদ step-by-step migration, আর Factory Method ডিজাইন প্যাটার্নের সাথে পরিষ্কার তুলনা।
ক্যান্টিনের কাউন্টারে যত ঝামেলা
ধরো তুমি তোমার college-এর ক্যান্টিনে এসেছ দুপুরে। ক্যান্টিন চালায় সালাম ভাই, বিশ বছর ধরে। রান্না করেন জামাল চাচা, যার ডালের খ্যাতি পুরো campus-এ। আর আজ নতুন student এসেছে — রুবেল, সবে first year শুরু হয়েছে, হোস্টেলে উঠেছে, এতটাই ক্ষুধার্ত যে মেনু বোর্ডটাই খেয়ে ফেলতে পারে।
কিন্তু মেনু বোর্ড নেই। শুরুর দিকে ক্যান্টিনের একটা অদ্ভুত সিস্টেম ছিল। প্রতিটা student সরাসরি কিচেন কাউন্টারে গিয়ে নিজের থালা সাজাত, item by item, কোথাও লেখা নেই এমন নিয়ম মেনে।
বাম দিকের ঝুড়ি থেকে দুটো রুটি নাও — ডান দিকেরটা না, সেটা parcel-এর জন্য। দ্বিতীয় পাতিল থেকে ডাল, প্রথমটা না — প্রথমটা staff-এর জন্য। ভাত এক স্কুপ, চ্যাপ্টা হাতা দিয়ে। আজকের তরকারি। আচার থালার বাম কোণে সবসময়। পাপড় উপরে রাখো যাতে ভিজে না যায়।
রুবেল এর কিছুই জানে না। সে তিনটা রুটি নেয়, ভাত নেয় না। পেছনের চাচা মিষ্টি নেন কিন্তু চামচ নেন না। একটা স্কুলের বাচ্চা পুরো থালা পাপড় দিয়ে ভরে পুরো সেটের দাম দেয়। জামাল চাচা দিনের অর্ধেক সময় কাউন্টারের ওপাশ থেকে চিৎকার করতে থাকেন: "ওই পাতিল না! এক স্কুপ শুধু! ভাত কোথায় তোমার?"
আর এটাই হলো আসল সমস্যা, যেটা নিয়ে সালাম ভাই রাতে ঘুমাতে পারেন না। রান্নাঘরে যখনই কোনো পরিবর্তন হয় — নতুন ডালের পাতিল, নতুন পরিমাণ, দামের পরিবর্তন — প্রতিটা student-কে নতুন করে steps শিখতে হয়। দুইশো student, সবার মাথায় রেসিপি, সবাই আলাদাভাবে একটু একটু ভুল।
এক সোমবার সালাম ভাই একটাই সহজ কাজ করলেন। নামের সেট দিয়ে একটা মেনু ছাপালেন। "ভাত সেট।" "স্পেশাল সেট।" "মিনি সেট।" এখন রুবেল শুধু একটা নাম বলে। জামাল চাচার রান্নাঘর ঠিক করে কীভাবে বানাবে। রান্নাঘর আজকের তরকারি বদলাতে পারে, ঈদে চুপচাপ একটা special version দিতে পারে সেমাই যোগ করে — আর কোনো student-কে এর কিছু জানতে হয় না। নামটা উদ্দেশ্য বহন করে; রেসিপির স্বাধীনতা থাকে রান্নাঘরের ভেতরে।
কোডে new Meal(true, false, 2, "steel") এর মতো raw constructor call করা মানে রুবেল অর্ধেক মনে থাকা রেসিপি দিয়ে কাউন্টারে থালা সাজাচ্ছে। Meal.vatSet() এর মতো named factory method মানে মেনু থেকে অর্ডার করা। আজকের রিফ্যাক্টরিং, Replace Constructor with Factory Method, হলো সেই মেনু ছাপানোর কাজ।
Journey diagram-টা আসলে কী বলছে দেখো। নিজে-সাজানোর দিনগুলোতে রুবেল আর জামাল চাচা দুজনেই কষ্টে আছেন, কারণ রেসিপি দুইশো মাথায় ছড়িয়ে আছে। মেনুর দিনগুলোতে দুজনেই খুশি, কারণ রেসিপি ঠিক একটাই রান্নাঘরে আছে। যেসব refactoring একই সাথে caller আর maintainer দুজনকে খুশি করে, সেগুলো বিরল আর মূল্যবান — এটা তাদের একটা।
Replace Constructor with Factory Method কী?
Constructor একটা বিশেষ method। আর এর তিনটা কঠিন সীমাবদ্ধতা আছে যেগুলো কোনো mainstream language বদলাতে দেয় না।
- এর একটাই নাম — class-এর নাম। তুমি একটা constructor-কে
openSavingsআর আরেকটাকেopenCheckingবলতে পারবে না। শুধু overload স্তূপ করতে পারবে, আর similar parameter list-এর overload বিভ্রান্তির বিখ্যাত উৎস। ঝটপট বলো —new Account(true)মানে কী? Class না খুলে কেউ জানে না। - এটা সবসময় নিজের class-ই return করে। একটা
Shapeconstructor কখনোCircleদিতে পারবে না। এটা interface return করতে পারে না। এটা বলতে পারে না "input দেখে ঠিক হবে কোনটা।" যেই মুহূর্তে callernew Shape(...)লেখে, সিদ্ধান্তটা নেওয়া হয়ে গেছে, ভুল জায়গায়। - এটা সবসময় নতুন object বানায়। এটা cached বা pooled বা shared কিছু return করতে পারে না। Constructor body চলার সময় memory ইতোমধ্যে allocate হয়ে গেছে। "আমাদের কি আদৌ নতুন object দরকার?" — এই সিদ্ধান্তটা কখনো নেওয়ার সুযোগ ছিল না।
Replace Constructor with Factory Method বলে: একটা সাধারণ static method লেখো যার কাজ হলো creation। ভেতরে এটা constructor call করে। তারপর সব caller-কে new এর বদলে এই method-এ পাঠাও। শেষে constructor লুকিয়ে দাও যাতে named method-ই একমাত্র দরজা হয়।
Martin Fowler তার বিখ্যাত Refactoring catalog-এ এই move বর্ণনা করেছেন — দ্বিতীয় edition-এ এটাকে Replace Constructor with Factory Function বলা হয়, কারণ JavaScript এবং similar language-এ factory টা free-standing function হতে পারে। ধারণাটা একই: creation-কে একটা নাম আর একটা মাথা দাও।
এক লাইনের সারসংক্ষেপ: constructor হলো class-এর নাম লেখা একটা দরজা, ভেতরে কোনো পাহারাদার নেই; factory method হলো একটা named দরজা, পাহারাদার আছে যে তোমাকে check করতে, অন্যদিকে পাঠাতে, বা আগে থেকে তৈরি কিছু দিতে পারে।
Factory একটা সাধারণ method হওয়ায় এটা সাধারণ method যা করতে পারে সব করতে পারে। এটা intention-revealing নাম বহন করতে পারে। এটা input validate করে politely refuse করতে পারে। এটা কোন subclass বানাবে তা pick করতে পারে। এটা interface return করতে পারে যাতে caller কখনো concrete type না জানে। এটা cached instance দিতে পারে — refactoring.guru ঠিক এটাই highlight করে: factory method ইতোমধ্যে তৈরি object return করতে পারে, যেখানে constructor সবসময় নতুন allocate করতে বাধ্য।
কোডে ডোবার আগে পুরো ধারণাটা একটা ছবিতে দেখো।
আর এই একই তুলনা একটা table-এ, যেটা থেকে revision নিতে পারো। Interview-এর সময় এটা কাছে রাখো — এই exact প্রশ্নটা জিজ্ঞেস করা হয়।
| ক্ষমতা | Plain constructor | Static factory method |
|---|---|---|
| বর্ণনামূলক নাম বহন করা | না — সবসময় class-এর নাম | হ্যাঁ — openSavings(), vatSet() |
| Subclass return করা | না — সবসময় নিজের class | হ্যাঁ — caller কোনটা জানে না |
| Interface type return করা | না | হ্যাঁ — concrete class লুকায় |
| Cached বা pooled instance return করা | না — সবসময় allocate করে | হ্যাঁ — existing object দিতে পারে |
| বানাতে refuse করা (আগে validate) | শুধু mid-construction throw করে | হ্যাঁ — allocation-এর আগেই check |
| Function value হিসেবে pass করা | বেশিরভাগ language-এ awkward | হ্যাঁ — এটা শুধু একটা method reference |
| DI container আর serializer নিজে থেকে খুঁজে পায় | হ্যাঁ — এটা তাদের default | প্রায়ই explicit registration লাগে |
শেষের row-টা honest। Constructor-এর plainness-ই এর সুবিধা: পৃথিবীর প্রতিটা framework জানে এটা কীভাবে call করতে হয়। এই trade-off-এ risks section-এ ফিরে আসব।
কখন এটা দরকার?
Real কোডে এই signals খোঁজো।
- Constructor overload-গুলো একে অপরের সাথে লড়াই করছে।
new Account(true)vsnew Account(false)— boolean-গুলো কী বোঝায়? যখন তুমি variation-গুলো নাম দিতে চাইছ, তখন factory দরকার:Account.openSavings(),Account.openChecking()। - Caller-রা নিজেই subclass বেছে নিচ্ছে।
kind === "circle" ? new Circle() : new Square()পাঁচটা ফাইলে copy-paste হয়ে আছে। Creation-এর সিদ্ধান্ত একটা রান্নাঘরে থাকা উচিত, প্রতিটা টেবিলে না। - Concrete class লুকাতে চাও। Caller-রা
PaymentGatewayinterface-এর উপর নির্ভর করবে,new BkashGateway(...)না। Constructor interface return করতে পারে না; factory পারে। - Creation নিয়ন্ত্রণ করতে হবে। Database connection pooling, flyweight object caching, instance সীমাবদ্ধ করা, প্রতিটা নতুন object কোথাও register করা — constructor এর কোনোটা honestly করতে পারে না।
- জন্মের আগে validation। Half-built object কখনো exist করতে না পারলে factory আগে check করে clear error দেয়, garbage বানায় না।
ধরো সালাম ভাইয়ের ভাগ্নে তারিক (CSE-তে পড়ে, অবশ্যই) billing codebase audit করল। সে গত এক বছরের code review comment থেকে প্রতিটা creation-related bug categorise করল। ফলাফল এমন দেখাচ্ছিল — এটা raw constructor পাস করা codebase-এর জন্য typical।
Pie-এর প্রতিটা slice ভিন্ন costume পরা একটাই constructor সীমাবদ্ধতা। Wrong argument order হয় কারণ (2, true, false, 120) এর কোনো নাম নেই। Copy-pasted recipe drift করে কারণ রেসিপি প্রতিটা call site-এ ছড়িয়ে আছে। Invalid object exist করে কারণ দরজায় কোনো পাহারা নেই। Wrong subclass বেছে নেওয়া হয় কারণ caller-কে বাধ্য করা হয় এমন কিছু ঠিক করতে যা রান্নাঘরের করা উচিত ছিল।
রিফ্যাক্টরিং বনাম পূর্ণ ডিজাইন প্যাটার্ন
একটু ভাবো — এই রিফ্যাক্টরিং Gang of Four বইয়ের Factory Method pattern এর সাথে সম্পর্কিত, কিন্তু ছোট। Students এগুলো মিশিয়ে ফেলে সবসময়, তাই আলাদা করে দেখি।
- রিফ্যাক্টরিং (এই পেজ)
new Thing(...)কেThing.create(...)দিয়ে replace করে — সাধারণত একটা class-এ একটা static method। এটা পাঁচ মিনিটের কাজ, তাৎক্ষণিক সুবিধা। - পূর্ণ ডিজাইন প্যাটার্ন একটা architecture। একটা abstract creator class আছে একটা abstract creation method সহ, আর creator-এর subclass-গুলো সেই method override করে ঠিক করে কোন concrete product বানাবে। Framework-এ এটা চমৎকার কাজ করে।
এভাবে ভাবো। রিফ্যাক্টরিং হলো একটা ক্যান্টিনে মেনু ছাপানো। Pattern হলো একটা ক্যান্টিন চেইন যেখানে প্রতিটা শাখা একই মেনু format মানে কিন্তু প্রতিটা শাখার রান্নাঘর তার local version তৈরি করে — ঢাকার শাখা স্পেশাল সেট বানায় ইলিশ দিয়ে, চট্টগ্রামের শাখা বানায় মেজবানি গোশত দিয়ে, আর হেড অফিস শুধু "স্পেশাল সেট" নিয়ে কথা বলে কোনো রেসিপি না জেনেই। আগে রিফ্যাক্টরিং করো। পরে যদি দেখো factory-র ভেতরে বারবার type-এ switch করছ, তাহলে পূর্ণ pattern (বা Abstract Factory) হলো স্বাভাবিক পরের ধাপ। Fowler নিজেই বলেছেন এই সহজ move হলো সেই বড় pattern-গুলোর দিকে gateway।
এক নজরে before আর after
এখানে ক্যান্টিনের billing software রিফ্যাক্টরিং-এর আগে। Caller-রা নিজে হাতে meal সাজাচ্ছে।
// BEFORE: every caller assembles the thali at the counter
class Meal {
constructor(
public rotis: number,
public hasRice: boolean,
public hasDessert: boolean,
public price: number,
) {}
}
// Five files across the codebase do this, each slightly differently:
const lunch = new Meal(2, true, false, 120); // "veg thali"... I think?
const special = new Meal(3, true, true, 180); // hope the price is right
const broken = new Meal(0, false, false, 120); // an empty plate for 120!কিছুই খালি থালা আটকায় না। (2, true, false, 120) মানে কী সেটা কিছুই ব্যাখ্যা করে না। আর স্পেশাল সেটের দাম বদলালে প্রতিটা ফাইল খুঁজতে হবে।
রিফ্যাক্টরিং-এর পরে, মেনু exist করে।
// AFTER: named factory methods — the kitchen decides the recipe
class Meal {
private constructor(
public rotis: number,
public hasRice: boolean,
public hasDessert: boolean,
public price: number,
) {}
static vegThali(): Meal {
return new Meal(2, true, false, 120);
}
static specialThali(): Meal {
return new Meal(3, true, true, 180);
}
static miniThali(): Meal {
return new Meal(1, true, false, 80);
}
}
const lunch = Meal.vegThali(); // reads like the menu
const special = Meal.specialThali(); // price lives in ONE place
// new Meal(0, false, false, 120) -> compile error: constructor is privateএকসাথে তিনটা জয়। Call site-গুলো মেনুর মতো পড়া যায়। প্রতিটা সেটের রেসিপি আর দাম ঠিক একটাই জায়গায় থাকে। আর empty-plate bug এখন লেখা অসম্ভব, কারণ constructor private।
ধাপে ধাপে, নিরাপদ পথে
প্রতিটা ভালো refactoring-এর মতো, আমরা ছোট ছোট ধাপে চলব আর প্রতিটার পরে program কাজ করছে কিনা দেখব। এখানে route-টা, Fowler-এর mechanics অনুসরণ করে।
ধাপ ১: Constructor-কে delegate করে factory method বানাও। কোনো caller এখনো বদলাবে না। নতুন method শুধু পুরনো দরজাটা wrap করে।
class Meal {
constructor( /* ...same as before, still public... */
public rotis: number,
public hasRice: boolean,
public hasDessert: boolean,
public price: number,
) {}
// NEW: the factory just forwards for now
static vegThali(): Meal {
return new Meal(2, true, false, 120);
}
}Program compile হয়। Test সবুজ থাকে। কিছু সরেনি এখনো — আমরা শুধু প্রথম দরজার পাশে একটা দ্বিতীয় দরজা যোগ করেছি।
ধাপ ২: Factory-কে intention-revealing নাম দাও আর সবচেয়ে abstract sensible return type দাও। Caller-দের যদি শুধু interface দরকার হয়, interface return করো। এটাও নাম ঠিক করার সময় — vegThali() চিরতরে create1() এর চেয়ে ভালো।
ধাপ ৩: প্রতিটা new Meal(...) call খুঁজে একটা একটা করে replace করো। IDE-এর "find usages" ব্যবহার করো। প্রতিটা replacement-এর পরে compile করো আর test চালাও। যদি কোনো call site কোনো existing factory-র সাথে না মেলে, এটা useful তথ্য — হয় নতুন named factory যোগ করো, অথবা প্রশ্ন করো সেই call site আদৌ সঠিক ছিল কিনা।
ধাপ ৪: Creation-time logic factory-তে নিয়ে যাও। Caller-রা হাতে হাতে যে validation, subclass selection, default-filling করছিল তা এখন ভেতরে চলে যায়। রান্নাঘর রেসিপির দায়িত্ব নেয়।
ধাপ ৫: Constructor-কে private করো (বা protected, যদি subclass-এর দরকার হয়)। এটা পুরনো দরজায় তালা। এখন থেকে named factory-গুলোই একমাত্র পথ।
ধাপ ৬: Full test suite চালাও, প্রতিটা named factory-র জন্য test সহ। প্রতিটা সেট তার নিজস্ব ছোট test পাওয়ার যোগ্য।
Migration-এর একটা clear shape আছে। এটা একটা ছোট state machine, আর বিপজ্জনক ভুল হলো প্রথম state থেকে সরাসরি শেষে লাফ দেওয়া।
ধাপ ১-এ constructor private করো না। সব caller নতুন দরজায় যাওয়ার আগে পুরনো দরজা lock করলে পুরো codebase একসাথে ভেঙে পড়বে আর তুমি আতঙ্কে ডজনখানেক error ঠিক করবে। নিরাপদ ক্রম সবসময় এটা: নতুন দরজা যোগ করো, মাঝে মাঝে green test রেখে একটা একটা করে caller সরাও, আর শেষে পুরনো দরজা lock করো। Framework আগে check করো — DI container, serializer, আর ORM প্রায়ই reachable constructor চায়, আর হঠাৎ-private হওয়া constructor compile time-এ না, runtime-এ fail করতে পারে।
রিফ্যাক্টরিং-এর পরে একটা single order-এর runtime flow এরকম দেখায়। Customer শুধু factory-র সাথে কথা বলে; constructor একটা internal kitchen tool হয়ে গেছে।
আরও বড় real-life উদাহরণ
উদাহরণটা real decision-making সহ কিছুতে বড় করি। ধরো ক্যান্টিন এখন online order নেয়, আর সালাম ভাই তিনটা behaviour চান যা plain constructor দিতে পারে না।
১. ঈদের দিনগুলোতে specialThali() চুপচাপ upgraded EidThali subclass return করবে যাতে সেমাই যোগ আছে।
২. Standard ভাত সেটের configuration shared (cached) হবে, ৪০০ দুপুরের অর্ডারের জন্য প্রতিবার rebuild না করে — জামাল চাচা rush-এর সময় ঠিক একই কারণে আগে থেকে ডালের কাপ portion করে রাখেন।
৩. কোনো সেটে কখনো শূন্য রুটি থাকতে পারবে না — একটা জায়গায় validation rule।
এখানে সেই তিনটা করার code। খেয়াল করো named দরজার পেছনে কতটা intelligence আছে।
// The base class with a guarded, private constructor
class Thali {
private static vegTemplate: Thali | null = null;
protected constructor(
readonly name: string,
readonly rotis: number,
readonly items: string[],
readonly price: number,
) {
if (rotis <= 0) {
throw new Error(`A thali needs at least one roti, got ${rotis}`);
}
}
// 1) Caching: the same immutable template serves every order
static vegThali(): Thali {
if (Thali.vegTemplate === null) {
Thali.vegTemplate = new Thali(
"Veg Thali", 2, ["dal", "rice", "sabzi", "pickle"], 120,
);
}
return Thali.vegTemplate;
}
// 2) Subclass selection: the caller never knows which kind it got
static specialThali(today: Date = new Date()): Thali {
if (isFestival(today)) {
return new FestivalThali();
}
return new Thali(
"Special Thali", 3, ["dal", "rice", "paneer", "papad"], 180,
);
}
}
class FestivalThali extends Thali {
constructor() {
super("Festival Special", 3,
["dal", "rice", "paneer", "papad", "kheer"], 180);
}
}
// Callers stay blissfully simple:
const order1 = Thali.vegThali(); // cached — no new allocation
const order2 = Thali.specialThali(); // maybe FestivalThali, maybe notএই তিনটা behaviour-এর যেকোনো একটা raw public constructor দিয়ে achieve করার চেষ্টা করো। পারবে না। Constructor একটা fresh object allocate করতে বাধ্য, নিজের class-ই return করতে বাধ্য, আর চলে caller ইতোমধ্যে কিছু বানানোর সিদ্ধান্ত নেওয়ার পরে। Factory method একটা আঘাতে এই তিনটা সীমা সরিয়ে দেয়।
Class structure এখন একটা ছোট পরিবারের মতো দেখায়, factory method-গুলো একমাত্র public দরজা।
Arrow-গুলো মনোযোগ দিয়ে পড়ো: OrderScreen শুধু Thali-তে নির্ভর করে। FestivalThali-তে কোনো arrow নেই। ঈদে শত শত customer সেমাই পায় ordering code-এর একটা লাইনও না জেনে যে subclass exist করে। সেই অদৃশ্য arrow — যেটা diagram-এ নেই — পুরো বিষয়টার মূল কথা।
C#-এ একই রিফ্যাক্টরিং
C# এই রিফ্যাক্টরিংয়ে extra polish দেয়। Static factory method .NET-এ একটা প্রিয় idiom — TimeSpan.FromMinutes(5), Task.FromResult(x), বা Guid.NewGuid() মনে করো। এগুলো সবই named factory যা standard library public constructor-এর বদলে বেছে নিয়েছে, ঠিক এই পেজের কারণগুলোর জন্য।
public class Account
{
public string Id { get; }
public decimal Balance { get; private set; }
public decimal InterestRate { get; }
// Private constructor: the only door is a named factory
private Account(string id, decimal balance, decimal interestRate)
{
Id = id;
Balance = balance;
InterestRate = interestRate;
}
public static Account OpenSavings(string id) =>
new Account(id, balance: 0m, interestRate: 0.04m);
public static Account OpenChecking(string id) =>
new Account(id, balance: 0m, interestRate: 0m);
public static Account OpenFixedDeposit(string id, decimal amount)
{
if (amount < 1000m)
throw new ArgumentException(
"Fixed deposit needs a minimum of 1000.", nameof(amount));
return new Account(id, amount, interestRate: 0.07m);
}
}
// Call sites read like a bank form:
var savings = Account.OpenSavings("AC-101");
var fd = Account.OpenFixedDeposit("AC-102", 50_000m);Account.OpenFixedDeposit("AC-102", 50000) এর সাথে new Account("AC-102", 50000, 0.07m, 2) তুলনা করো। প্রথমটা class seven-এর student পড়তে পারবে। দ্বিতীয়টা তিন মাস পরে লেখক নিজেও ভুল পড়বে।
একটা honest C# সতর্কতা। কিছু .NET tooling constructor আশা করে: where T : new() generic constraint একটা public parameterless constructor চায়, অনেক serializer (আর কিছু ORM) reflectively object বানায়, আর DI container default হিসেবে constructor দিয়ে service resolve করে। এগুলো কোনোটাই blocker না — serializer configure করা যায়, আর container services.AddSingleton(sp => Account.OpenSavings("AC-1")) এর মতো factory delegate accept করে — কিন্তু দরজা lock করার আগে check করো।
Python-এ এক ঝলক
Python একই ধারণা @classmethod দিয়ে express করে, আর standard library-তে এর উদাহরণ ভরা: dict.fromkeys(...), datetime.fromtimestamp(...), int.from_bytes(...)। Factory নামে from বা of দিয়ে শুরু করার convention data source স্পষ্ট করে।
class Thali:
def __init__(self, name: str, rotis: int, price: int) -> None:
if rotis <= 0:
raise ValueError(f"A thali needs at least one roti, got {rotis}")
self.name = name
self.rotis = rotis
self.price = price
@classmethod
def veg(cls) -> "Thali":
return cls("Veg Thali", rotis=2, price=120)
@classmethod
def from_order_row(cls, row: dict) -> "Thali":
# a factory that adapts a foreign shape into our class
return cls(row["item_name"], int(row["roti_count"]), int(row["amount"]))
lunch = Thali.veg()
imported = Thali.from_order_row({"item_name": "Mini", "roti_count": "1", "amount": "80"})Python সত্যিকারের constructor lock করতে পারে না — private keyword নেই — কিন্তু team convention "সবসময় classmethod দিয়ে বানাও" আর leading-underscore constructor argument একই discipline practice-এ অর্জন করে। from_order_row factory একটা use-ও দেখায় যেটা এখনো stress করা হয়নি: factory হলো চমৎকার adapter, messy external data-কে একটা guarded জায়গায় clean object-এ রূপান্তর করে।
College corner: constructor বনাম static factory, বিখ্যাত বিতর্ক। Java পড়লে, এই পুরো পেজটা Joshua Bloch-এর Effective Java-এর Item 1: "constructor-এর বদলে static factory method বিবেচনা করো।" Bloch সেই সুবিধাগুলো list করেছেন যা আমরা দেখেছি — নাম, caching, subclass return — আর একটা আরও যা API design-এ গুরুত্বপূর্ণ: static factory non-public class-এর object return করতে পারে, তাই একটা library interface expose করতে পারে আর প্রতিটা implementation class সম্পূর্ণ hidden রাখতে পারে (এভাবেই Collections.unmodifiableList কাজ করে — তুমি একটা List পাও আর জানো না, জানার দরকারও নেই, কোন class এটা)। Standard naming vocabulary মুখস্থ করার যোগ্য: conversion-এর জন্য of আর from (List.of, Date.from), boxed value-র জন্য valueOf, same instance reuse হতে পারলে getInstance, fresh একটা guarantee দিলে newInstance। Bloch costs নিয়েও honest: public constructor ছাড়া class বাইরের লোক subclass করতে পারে না, আর static factory documentation-এ খুঁজে পাওয়া constructor-এর চেয়ে কঠিন, যা IDE স্বয়ংক্রিয়ভাবে list করে। এজন্যই disciplined naming convention আছে — এগুলো factory আবার discoverable করে। যখন তোমার professor জিজ্ঞেস করে "Integer.valueOf(127) == Integer.valueOf(127) কেন true কিন্তু পুরনো Java-তে new Integer(127) == new Integer(127) false ছিল" — উত্তর এই পেজে: valueOf একটা factory যা ছোট value-র জন্য cached object return করে, যেখানে new প্রতিবার allocate করতে বাধ্য ছিল।
এই class-এ কি factory দরকার?
সব class মেনুর যোগ্য না। একটু data holder যার একটাই obvious recipe আছে সেটা plain constructor দিয়ে ভালো — শুধু পানি বিক্রি করা দোকানের জন্য মেনু ছাপানো আমলাতন্ত্র। এখানে সিদ্ধান্তের একটা map।
নিচে-বাম — একটা shape সহ ছোট DTO — new রাখো। নিচে-ডান — অনেক meaningful variation কিন্তু প্রতিটা plain fresh object — factory শুধু নামের জন্যই কাজে লাগে। উপরে-বাম — একটা রেসিপি কিন্তু instance cache বা pool করতে হবে — factory return control-এর জন্য। উপরে-ডান — অনেক রেসিপি আর special return, caching আর festival subclass সহ — সেখানে factory method optional না, এটাই একমাত্র honest design।
এবং সংখ্যায় জয়টা দেখতে কেমন? তারিক সবচেয়ে সহজ possible metric মাপল: স্পেশাল সেটের রেসিপি বদলালে (এপ্রিলে দাম পরিবর্তন) কতটা ফাইল edit করতে হয়েছিল?
নয়টা থেকে একটায় নামল। এই পার্থক্য হলো Shotgun Surgery smell real time-এ সারছে — আর প্রতিটা ভবিষ্যত পরিবর্তনের সাথে এটা compound হয়।
IDE সাপোর্ট
এটা সেই ভাগ্যবান refactoring-গুলোর একটা যেখানে major IDE-তে first-class automation আছে।
- IntelliJ IDEA (Java/Kotlin).
Refactor → Replace Constructor with Factory Methodপুরো কাজটা করে দেয়: static method বানায়, project জুড়ে প্রতিটা constructor call rewrite করে, আর constructor private করে — সব একটা safe, atomic operation-এ। তুমি method নাম আর কোন class-এ factory থাকবে সেটাও বেছে নিতে পারো। - ReSharper এবং Rider (C#). .NET code-এর জন্য একই refactoring offer করে, static method generate করে আর সব usage update করে।
- Visual Studio (ReSharper ছাড়া). Single-click version নেই, কিন্তু "Find All References", "Change Signature", আর Quick Actions-এর combination manual route-কে দ্রুত আর safe করে।
- VS Code-এ TypeScript. কোনো automated refactoring নেই, তাই উপরের step-by-step section হাতে follow করো। Constructor-এ "Find All References" (cursor class-এর নামে রেখে
new ClassNameখোঁজো) তোমাকে migration checklist দেয়। Constructorprivatemark করার মুহূর্তে compiler তোমার safety net হয়ে যায় — প্রতিটা missed caller একটা লাল squiggle হয়ে যায়।
Automation দিয়েও naming নিজে করো। IDE খুশিমনে method-কে create বলবে, আর create কিছুই বলে না। OpenSavings সব বলে।
সুবিধা আর ঝুঁকি
প্রতিটা refactoring একটা trade। এখানে honest হিসাব।
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
Named creation intent express করে — openSavings() overloaded constructor-এর চেয়ে ভালো | Extra indirection; একটা obvious recipe সহ simple class-এর জন্য plain new পরিষ্কার |
| Subclass বা interface type return করতে পারে, concrete class লুকায় | কিছু DI container, serializer, আর new() generic constraint public constructor আশা করে |
| Cached বা pooled instance return করতে পারে, সবসময় allocate না করে | Caching shared state যোগ করে — cached object immutable হতে হবে নইলে সমস্যা |
| Validation আর creation rules এক জায়গায় থাকে, skip করা অসম্ভব | অনেক factory method বানানো কঠিন করে দিতে পারে "এটা কীভাবে বানাই?" |
| পূর্ণ Factory Method আর Abstract Factory pattern-এর দিকে স্বাভাবিক প্রথম ধাপ | Constructor আসলেই non-public করতে হবে, নইলে caller চুপচাপ factory bypass করবে |
| Call site-গুলো testable seam হয় — test named intent পড়তে পারে | একটু বেশি code: প্রতিটা named variation-এর জন্য এক method |
Fair rule of thumb: তোমার class-এর জন্য বাম column থেকে একটাও concrete সুবিধার নাম বলতে না পারলে constructor রাখো। মেনু সেই ক্যান্টিনের জন্য যেখানে একটার বেশি item আছে।
এটা কোন smell সারায়?
| Smell | এই রিফ্যাক্টরিং কীভাবে সাহায্য করে |
|---|---|
| Switch Statements | Caller-গুলোতে copy-paste করা creation switch একটা factory-তে সংকুচিত হয়; পরে polymorphism-এ গলে যেতে পারে |
| Large Class | জড়িয়ে যাওয়া constructor overload আর creation flag clearly named factory method-এ বেরিয়ে যায় |
| Long Parameter List | Sensible default সহ named factory-গুলো ছয়টা mysterious argument নেওয়া constructor replace করে |
| Duplicate Code | অনেক call site-এ repeated "object correctly assemble করো" dance একটা জায়গায় আসে |
| Shotgun Surgery | রেসিপি পরিবর্তন (দাম, defaults, subclass choice) একটা factory touch করে, প্রতিটা caller না |
Quick revision box
+------------------------------------------------------------------+
| REPLACE CONSTRUCTOR WITH FACTORY METHOD - REVISION CARD |
+------------------------------------------------------------------+
| Problem : constructors cannot be named, cannot return another |
| type, and always allocate a fresh object |
| Solution : static factory method wraps the constructor; |
| then make the constructor private |
| |
| THE FACTORY CAN (constructor cannot): |
| - carry a meaningful name (Account.OpenSavings) |
| - return a subclass/interface (kitchen decides the thali) |
| - return a cached instance (no fresh plate every time) |
| - validate and refuse (no zero-roti thali) |
| |
| SAFE ORDER : add factory -> migrate callers one by one |
| -> lock constructor LAST |
| Remember : refactoring = one named method; |
| full GoF pattern = subclass overrides creation |
+------------------------------------------------------------------+অনুশীলনী
তোমার পালা। ধরো একটা travel app এভাবে ticket বানাচ্ছে, আর call site-গুলো mystery boolean-এ ভরা।
class Ticket {
constructor(
public train: string,
public isAC: boolean,
public isTatkal: boolean,
public fare: number,
) {}
}
// scattered across the codebase:
const t1 = new Ticket("12952 Rajdhani", true, false, 3200);
const t2 = new Ticket("12952 Rajdhani", true, true, 3700); // tatkal: +500
const t3 = new Ticket("11077 Jhelum", false, true, 950); // is this fare right?নিজে রিফ্যাক্টরিং করো, ধাপে ধাপে:
১. কোনো caller না বদলে তিনটা static factory যোগ করো: Ticket.acTicket(train, fare), Ticket.tatkalTicket(train, baseFare) (এটা factory-র ভেতরেই ৫০০ টাকা tatkal charge যোগ করবে), আর Ticket.sleeperTicket(train, fare)।
২. তিনটা call site একটা একটা করে migrate করো, প্রতিটার পরে compile করো।
৩. Constructor private করো আর confirm করো compiler এখন সরাসরি new Ticket(...) reject করে।
৪. এক জায়গায় একটা validation rule যোগ করো: কোনো ticket-এর fare ৫০ টাকার নিচে হতে পারবে না।
৫. Bonus চিন্তা: app এখন চায় Ticket.tatkalTicket একটা TatkalTicket subclass return করবে যার bookingWindow property আছে। এই exercise constructor-এর কোন সীমাবদ্ধতা demonstrate করছে? এক বাক্যে লেখো।
৬. Stretch goal: বর্ণনা করো (শব্দে, code না) যদি ticket পরিবার — train, bus, আর flight ticket প্রতিটা AC/non-AC variant সহ — একসাথে বানাতে হয় তাহলে কী বদলাতে হবে। creational patterns পরিবারের কোন pattern তোমাকে ডাকছে?
৭. Diagram practice: Figure 5 state machine-টা তোমার Ticket-এর migration-এর জন্য sketch করো, ধাপ ১ থেকে ৩-এর প্রতিটার পরে তুমি কোন state-এ আছ সেটা mark করো।
যদি কোনো বন্ধুকে বোঝাতে পারো কেন ৫০০ টাকার tatkal charge factory-র ভেতরে থাকা উচিত, call site-এ না — যেভাবে রুবেল এখন বোঝাতে পারে কেন সেমাইয়ের সিদ্ধান্ত জামাল চাচার রান্নাঘরের, customer-এর না — তাহলে এই রিফ্যাক্টরিং তুমি সম্পূর্ণ বুঝে গেছ।
সচরাচর জিজ্ঞাসা
- এই রিফ্যাক্টরিং আর Factory Method ডিজাইন প্যাটার্নের পার্থক্য কী?
- রিফ্যাক্টরিংটা একটা ছোট কাজ — একই class-এ একটা named static method দিয়ে constructor call-টা মুড়ে দাও। পূর্ণ ডিজাইন প্যাটার্ন আরও বড় — creator class-এর একটা hierarchy থাকে যেখানে subclass-গুলো creation method override করে ঠিক করে কোন product বানাবে। রিফ্যাক্টরিং প্রায়ই সেই বড় pattern-এর দিকে যাওয়ার প্রথম ধাপ, কিন্তু বেশিরভাগ সময় সহজ static method-ই যথেষ্ট।
- প্রতিটা constructor কি factory method দিয়ে replace করতে হবে?
- না। একটা ছোট class-এর যদি একটাই সহজ উপায় থাকে বানানোর, তাহলে plain constructor-ই ছোট আর পরিষ্কার। Factory method দরকার শুধু যখন meaningful নাম, ভিন্ন return type, subclass-এর সিদ্ধান্ত, caching, বা creation-এর আগে validation দরকার হয়।
- শেষে constructor private কেন করি?
- Constructor public থাকলে caller-রা factory bypass করে সরাসরি object বানাতে পারবে। Private করলে সব creation একটাই named দরজা দিয়ে যেতে বাধ্য হয়, তাই factory-র ভেতরের নিয়মগুলো কখনো skip হওয়ার সুযোগ নেই।
- Factory method কি নতুন object ছাড়া অন্য কিছু return করতে পারে?
- হ্যাঁ, এটাই এর superpower। এটা একটা subclass, একটা interface type যেটা concrete class লুকিয়ে রাখে, একটা cached বা pooled instance, অথবা কিছুই না দিতে পারে — সরাসরি clear error দিয়ে refuse করতে পারে।
- Factory method কি framework-এর সাথে সমস্যা করে?
- কখনো কখনো করে। অনেক DI container, serializer, আর generic constraint একটা public constructor আশা করে আর static factory method নিজে থেকে খুঁজে পায় না। Constructor lock করার আগে তোমার framework check করো, অথবা container-এ factory explicitly register করো।
আরো দেখো
সম্পর্কিত পাঠ
Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle
Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।
Abstract Factory Pattern: এক অর্ডার, এক ম্যাচিং থালি
বাংলাদেশি বিয়ের ক্যাটারিং গল্পের মাধ্যমে Abstract Factory design pattern বুঝে নাও — সহজ TypeScript ও Python কোড, diagram, আর practice সহ।
Switch Statements: সেই রিসেপশনিস্ট আর তার বিশাল নিয়মের খাতা
Switch Statements code smell শেখো একটা school-এর গেটকিপারের গল্পের মাধ্যমে — TypeScript আর C#-এ duplicate switch-এর উদাহরণ সহ, আর কীভাবে polymorphism দিয়ে এটা ঠিক করবে।
Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে
Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।