Push Down Field: সুইমিং পুলের সময়সূচি শুধু ৭সি-র বোর্ডেই লাগানো উচিত
Push Down Field refactoring শেখো একটা স্কুলের নোটিশ বোর্ডের গল্পের মাধ্যমে — superclass-এ থাকা কোনো field যদি শুধু কিছু subclass ব্যবহার করে, সেটাকে ঠিক সেই subclass-এ নামিয়ে দাও। TypeScript আর C#-এ নিরাপদ ধাপ, IDE dialog, আর pull-up বনাম push-down compass সহ।
মেইন বোর্ডে সুইমিং পুলের সময়সূচি
ধরো তুমি একটা স্কুলের notice board system দেখছো — আর এবার সমস্যাটা উল্টো দিক থেকে এসেছে।
Pull Up Field গল্পের নিয়মটা মনে আছে? shared তথ্য মেইন অফিস বোর্ডে যায়। স্কুল সেটা বিশ্বস্তভাবে মেনে চলছিল। ঠিকানা উঠেছে। ফোন নম্বর উঠেছে। পরীক্ষার সময়সূচি উঠেছে। প্রধান কেরানি জনাব রহিম মেইন বোর্ড এত ভালো রাখতেন যে সবাই বিশ্বাস করত — মেইন বোর্ডে যা আছে, সেটা সবার জন্য।
কিন্তু না ভেবে নিয়ম মানলে নিজেই গণ্ডগোল হয়। একদিন ক্রীড়া শিক্ষক মেইন অফিস বোর্ডে একটা নোটিশ লাগিয়ে দিলেন: "সুইমিং পুলের সময়সূচি — সোম/বৃহ সকাল ৮:০০–৯:৩০।" উদ্দেশ্য ভালো। মেইন বোর্ডেই তো গুরুত্বপূর্ণ নোটিশ যায়, আর তার কাছে পুলের সময়সূচির চেয়ে গুরুত্বপূর্ণ আর কী আছে?
এখন ব্যাপারটা হলো — এই স্কুলে শুধু ৭সি, ফাতেমা ম্যাডামের স্পোর্টস সেকশনে সাঁতার আছে। তারিকের ৭এ-তে সেই সময়ে গণিত; জামালের ৭বি-তে ইংরেজি। পুলের সময়সূচি স্কুলের দুই-তৃতীয়াংশের কাছে কোনো অর্থই রাখে না।
এই একটা ভুল-জায়গায় লাগানো নোটিশ দেখো কী করল:
- ৭এ-র একজন ছাত্র মেইন বোর্ড পড়ে ধরে নিল সময়সূচি সবার জন্য, আর তোয়ালে নিয়ে পুলে হাজির হলো — গণিত ক্লাস মিস করে। তারিক স্যার absent দিলেন; মায়ের কাছে চিন্তার ফোন গেল; ভুল বোঝাবুঝি সামলাতে দুই দিন লাগল।
- একজন নতুন শিক্ষক ৭বি-র সময়সূচি বানাতে গিয়ে রহিম ভাইকে ফোন করলেন — ৭বি-কে কোন লেন দেওয়া হয়েছে? রহিম ভাইকে আবার বোঝাতে হলো ৭বি-র কোনো সাঁতার নেই। প্রতি টার্মে একবার এই প্রশ্ন আসে — তিনি হিসাব রাখেন।
- মেইন বোর্ড ভিড়াক্রান্ত হয়ে গেল। ছাত্রছাত্রীরা আর মনোযোগ দিয়ে পড়ে না, কারণ "এর অর্ধেকই আমার জন্য না" — আর তাই সত্যিকারের জরুরি নোটিশও মিস হয়।
মেইন বোর্ড একটা নীরব প্রতিশ্রুতি করেছিল: এখানের সব কিছু প্রতিটি সেকশনের বিষয়। পুলের নোটিশ সেই প্রতিশ্রুতি ভাঙল। বিভ্রান্তি, নষ্ট প্রশ্ন, কেউ পুরোপুরি বিশ্বাস না করা বোর্ড — এই হলো মূল্য।
সমাধানটা প্রধান শিক্ষিকা নাসরিন ম্যাডাম-এর ত্রিশ সেকেন্ড লেগেছিল। মেইন বোর্ড থেকে নোটিশ খুলে ৭সি-র সেকশন বোর্ডে লাগিয়ে দিলেন — যেখানে প্রতিটি পাঠক আসলেই সাঁতার কাটে। মেইন বোর্ড আবার সত্যি বলতে শুরু করল। আর ৭সি-র বোর্ড নিজের বিশেষ তথ্য বহন করছে, ঠিক যেখানে সুমাইয়া আর তার বন্ধুরা দেখে।
Code-এ, মেইন বোর্ড হলো superclass আর সেকশন বোর্ড হলো subclass। কোনো field যখন parent-এ থাকে কিন্তু শুধু কিছু child ব্যবহার করে, সেটাকে ঠিক সেই child-গুলোতে নামিয়ে দিতে হয়। এটাই হলো Push Down Field — এই সিরিজের শুরুর refactoring-এর সঠিক আয়নার ছবি।
Push Down Field কী?
Push Down Field Martin Fowler-এর Refactoring catalog-এ আছে, "generalization নিয়ে কাজ করা" পরিবারে। এর নির্দেশনা Pull Up Field-এর ঠিক উল্টো:
যখন superclass-এ declare করা কোনো field শুধু কিছু subclass ব্যবহার করে, সেটা superclass থেকে সরিয়ে শুধু সেই subclass-গুলোতে declare করো যেগুলো আসলে দরকার।
একটু ভাবো — field কীভাবে বেশি উপরে চলে যায়? তিনটা সাধারণ কারণ আছে:
- পরিকল্পিত feature যা কমে গেছে। Designer ভেবেছিল প্রতিটি সেকশন "একদিন" সাঁতার পাবে, তাই
swimmingPoolTimingsদিন এক থেকেইSection-এ ঢুকিয়ে দিল। সেই দিন আর এলো না। এটা Speculative Generality smell — একটা ভবিষ্যতের জন্য structure যা আসেনি। - Hierarchy-র আকার বদলে গেছে। একসময় শুধু একটা সেকশন ছিল, সেটা সত্যিই সাঁতার কাটত। পরে আরও সেকশন এলো, field উপরেই থেকে গেল, কেউ আর দেখলই না।
- অতি-উৎসাহী Pull Up। কেউ Pull Up Field পড়েছে, দুটো subclass-এ একই রকম field দেখেছে, উপরে তুলে দিয়েছে — তৃতীয় আর চতুর্থ subclass চেক না করেই।
গল্প যাই হোক, ফলাফল একটাই: superclass এমন state declare করে যা সত্যিকার অর্থে common না। প্রতিটি subclass সেটা inherit করে, চাই বা না চাই। যে child-গুলো কখনো ব্যবহার করে না তারা মৃত ভার বহন করছে — আর এর চেয়েও খারাপ হলো বিভ্রান্তিকর ভার। কারণ parent field একটা প্রতিশ্রুতি: "আমার সব child-এর এটা আছে।" পাঠকরা বিশ্বাস করে। Code completion প্রতিটি subclass-এ field দেখায়। নতুন teammate এর বিরুদ্ধে code লেখে। ৭এ-র ছাত্র তোয়ালে নিয়ে পুলে যায়।
এটাই হলো Refused Bequest smell-এর structural রূপ। Bequest মানে inheritance, আর subclass-গুলো সেটা প্রত্যাখ্যান করছে। Push Down Field হলো data-র জন্য এর সবচেয়ে সরাসরি সমাধান।
এক লাইনে: Push Down Field একটা field যা শুধু কিছু subclass ব্যবহার করে সেটাকে superclass থেকে বের করে ঠিক সেই subclass-গুলোতে নিয়ে যায় — যেমন সুইমিং পুলের সময়সূচি মেইন অফিস বোর্ড থেকে খুলে ৭সি-র বোর্ডে লাগানো, কারণ শুধু ৭সিই সাঁতার কাটে।
আর একটা বোনাস আছে। Parent-এ field-টাকে কমপক্ষে protected রাখতে হতো, যাতে subclass-গুলো access করতে পারে। একবার সেটা শুধু একটাই class-এ থাকলে, সাধারণত আবার private করে আঁটো করা যায়। ছোট visibility মানে কম জায়গা যেগুলো এটা পড়তে বা ভাঙতে পারে।
কলেজ কর্নার: push-down হলো যেখানে protected visibility trade-off তোমাকে ফেরত দেয়। protected একটা অর্ধপথের আবাস: private-এর চেয়ে প্রশস্ত কিন্তু public-এর চেয়ে সংকীর্ণ। প্রতিটি pull-up encapsulation খরচ করে sharing কিনতে; প্রতিটি push-down সেটা ফেরত দেয়। Liskov Substitution Principle-এর দিক থেকেও ভাবো: একটা parent field যা বেশিরভাগ child-এর কাছে অর্থহীন, সেটা substitutability-র spirit চুপচাপ লঙ্ঘন করে। Parent-এর interface হওয়া উচিত সব child সৎভাবে support করার intersection, যেকোনো child কখনো যা দরকার ছিল তার union নয়। Push Down Field সেই union-কে আবার intersection-এ ছাঁটে।
এই move পরিবার মানচিত্রে কোথায়:
কখন দরকার?
এই চিহ্নগুলো দেখলে বুঝবে সময় এসেছে:
- Parent field যা বেশিরভাগ subclass কখনো পড়ে বা লেখে না। IDE-এ "Find Usages" চালাও: যদি
swimmingPoolTimingsশুধুSectionSevenCথেকে ব্যবহার হয়, তাহলে field এক তলা বেশি উঁচুতে আছে। এটা Refused Bequest-এর শান্ত রূপ — কোনো error নেই, warning নেই, শুধু একটা inheritance যা child-গুলো চুপচাপ উপেক্ষা করছে। - বেশিরভাগ child-এর কাছে
null/undefinedথাকা field। ধরোPassengerCarচিরকালcargoVolumenull রাখে, আর সবাই "জানে" সেই type-এ এটা ধরবে না। Hierarchy নিজের সম্পর্কে মিথ্যা বলছে। - বারবার exception ব্যাখ্যা করতে হচ্ছে। "ওহ, সেই field — স্পোর্টস সেকশনে না থাকলে ignore করো।" এই ধরনের কথা দুইবার বলতে হলে সেটা refactoring request। রহিম ভাই প্রতি টার্মে একই প্রশ্নের উত্তর দিচ্ছেন — এটাই সেই সংকেত।
- Parent এক-child special দিয়ে ভরে গেছে। এক subclass-এর জন্য তিনটা field, আরেকটার জন্য দুটো — superclass ভিড়াক্রান্ত মেইন বোর্ড হয়ে গেছে। প্রতিটি push-down parent-এর প্রতিশ্রুতি আবার সৎ করে।
আর থামার চিহ্নগুলো:
- সব (বা প্রায় সব) subclass field ব্যবহার করে। তাহলে এটা সত্যিই common — নিচে নামালে Duplicate Code হবে, যা Pull Up Field সমাধান করতে আছে। দুটো refactoring বিপরীত — ভুল দিকে ভালো কাজ বাতিল করো না।
- Code parent type দিয়ে field access করছে। কোনো report module যদি
Sectiontyped variable-এsection.swimmingPoolTimingsপড়ে, field নামলেই সেটা ভাঙবে। এটা দেখে বোঝো: হয় সেই code subclass type নিয়ে কাজ করুক, অথবা field আসলেই parent-এর contract-এর অংশ। - "শুধু কিছু" subset বাড়তে থাকছে। আজ দুটো subclass ব্যবহার করছে, আগামী মাসে তৃতীয়টা করবে — তাহলে হয়তো একটা intermediate
SportsSectionparent দরকার, scattered copy নয়।
Pull Up Field post-এর Figure 3 এর সাথে তুলনা করো: address dot ছিল নিচে-ডানে (এখনই pull up); এই dot আছে উপরে-বামে (এখনই push down)। দুটো ভুল, দুটো বিপরীত সংশোধন, একটাই map।
এক নজরে আগে আর পরে
স্কুলের class hierarchy ভুল-জায়গায় নোটিশ সহ দেখো। পুলের সময়সূচি Section-এ, তাই প্রতিটি সেকশন এটা inherit করছে:
// BEFORE: a one-section field forced on every section
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001"; // truly shared — fine
protected poolTimings = "Mon/Thu 8:00-9:30"; // only 7C ever uses this!
protected poolLane = 0; // meaningless for 7A, 7B
}
class SectionSevenA extends Section {
// never touches poolTimings or poolLane — but inherits both
}
class SectionSevenB extends Section {
// never touches them either — dead weight, quietly misleading
}
class SectionSevenC extends Section {
swimmingPlan(): string {
return `Swimming at ${this.poolTimings}, lane ${this.poolLane}`;
}
}SectionSevenA আর SectionSevenB দুটো field বহন করছে যেগুলো তারা কখনো ব্যবহার করে না। Autocomplete একটা ৭এ object-এ poolTimings দেখাচ্ছে যেন এটা কিছু মানে। Parent-এর প্রতিশ্রুতি — "সব সেকশনের এটা আছে" — মিথ্যা। দেখো field আসলে কে পড়ছে বনাম কাকে জোর করে inherit করানো হচ্ছে:
Push Down Field-এর পরে:
// AFTER: the field lives exactly where it is used
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001"; // the parent keeps
// only what is truly common
}
class SectionSevenA extends Section {} // clean — nothing irrelevant inherited
class SectionSevenB extends Section {} // clean
class SectionSevenC extends Section {
private poolTimings = "Mon/Thu 8:00-9:30"; // moved down — and now PRIVATE
private poolLane = 4;
swimmingPlan(): string {
return `Swimming at ${this.poolTimings}, lane ${this.poolLane}`;
}
}লক্ষ্য করো একটা নয়, দুটো upgrade হয়েছে। Field নিচে নেমেছে, আর protected থেকে private-এ আঁটো হয়েছে — ৭সি-র বাইরে কারো দরকার নেই, তাই কেউ access করতেও পারবে না। Parent সৎ, child-গুলো পরিষ্কার, আর বিশেষ data সবচেয়ে ছোট সম্ভব দরজার পেছনে।
Push-down-এর আগে আর পরে অপ্রাসঙ্গিক পুলের state বহন করা class-এর সংখ্যা:
নিরাপদ পদ্ধতিতে ধাপে ধাপে
Push Down Field mechanically সহজ, কিন্তু verification ধাপটা যেকোনো pull-up-এর চেয়ে বেশি গুরুত্বপূর্ণ। কারণ তুমি একটা ব্যাপকভাবে দৃশ্যমান জায়গা থেকে কিছু সরাতে যাচ্ছো। সামগ্রিক flow:
কীভাবে করবে? এই ধাপগুলো follow করো:
-
Field-এর প্রতিটি usage খোঁজো — সত্যিই প্রতিটি। শুধু text search নয়, IDE-এর "Find Usages" চালাও parent-এর field-এ। দুটো প্রশ্নের সঠিক উত্তর দরকার: কোন subclass field স্পর্শ করছে, আর কোনো code কি parent-typed reference দিয়ে access করছে? আমাদের উদাহরণে: শুধু
SectionSevenCpoolTimingsব্যবহার করছে, আর কোনো codeSectionvariable দিয়ে পড়ছে না। সবুজ আলো। -
প্রতিটি subclass-এ field declare করো যেগুলো আসলে ব্যবহার করে। এখনকার জন্য একই visibility রাখো (
protected) যাতে আর কিছু না বদলায়। এটা intermediate state — field সংক্ষিপ্তভাবে দুই জায়গায় থাকবে, যা নিরাপদ কারণ behavior একই থাকে:// INTERMEDIATE: declared in the real user; parent copy still present class SectionSevenC extends Section { protected poolTimings = "Mon/Thu 8:00-9:30"; // new home, same visibility protected poolLane = 4; // ... }Compile করো আর test চালাও।
-
Superclass থেকে field মুছে দাও।
SectionথেকেpoolTimingsআরpoolLaneমুছো। Compile করো। যদি compiler তোমার প্রত্যাশার বাইরে কোথাও অভিযোগ করে — তাহলে ধাপ ১ একটা usage মিস করেছে, compiler সেটা খুঁজে দিয়েছে। এগোনোর আগে তদন্ত করো। -
পুরো test suite চালাও। Behavior অবশ্যই একই থাকতে হবে: ৭সি এখনো একই সময়ে, একই লেনে সাঁতার কাটে; ৭এ আর ৭বি কখনো এটা নিয়ে মাথা ঘামায়নি।
-
Visibility আঁটো করো। এখন field এক class-এ,
protectedথেকেprivateকরো। Construction-এর পরে কখনো না বদলালেreadonlyও বিবেচনা করো। আরও একবার compile আর test। -
Related member-গুলো sweep করো। Pushed-down field প্রায়ই বন্ধু নিয়ে আসে: একটা method যা এটা ব্যবহার করে, একটা constructor parameter যা এটা fill করে, একটা parent method যা এটা mention করে। Companion-গুলোও নিচে নামাও — method version-টা হলো Push Down Method।
এই ধাপগুলোতে field-এর lifecycle — লক্ষ্য করো নিম্নমুখী যাত্রা Tightened state-এ শেষ হয়, যা কোনো pull-up কখনো পায় না:
বিপজ্জনক মুহূর্ত হলো ধাপ ৩, parent থেকে delete করা। তোমার সুরক্ষা হলো ধাপ ২ আর ৩-এর মাঝে compile ও test করা, দুটো একসাথে করার পরে নয়। যদি subclass copy যোগ করা আর parent copy মোছা এক বড় edit-এ করো আর কিছু fail হয় — কোনটা কারণ বুঝতে পারবে না। দুটো ছোট ধাপ, দুটো সবুজ checkpoint। আর ধাপ ১-এর "parent-typed access" check কখনো skip করো না — test-এ cover না হওয়া কোনো report module হয়তো Section reference দিয়ে field পড়ছে, আর সেই breakage সবচেয়ে খারাপ সময়ে আসবে।
Move-এর পরে compile-time গল্পটা একটা ছবির মতো। পুরনো code যখন parent-typed reference দিয়ে pool field পড়তে চাইবে, compiler এখন প্রত্যাখ্যান করবে — আর সেই প্রত্যাখ্যান হলো refactoring কাজ করছে, ব্যর্থ হচ্ছে না:
একটা বড় বাস্তব উদাহরণ
স্কুলের পুরো timetable system দেখলে বুঝবে push-down কীভাবে একটা সত্যিকারের ভিড়াক্রান্ত parent পরিষ্কার করে। বছরের পর বছর ধরে Section সবার জন্য special জিনিস জমিয়েছে:
// BEFORE: the parent has become a crowded main board
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001"; // shared — stays
protected classTeacher = ""; // shared — stays
protected poolTimings = ""; // only the sports section (7C)
protected poolLane = 0; // only the sports section (7C)
protected labAssistant = ""; // only the science section (7B)
protected chemicalsRegister: string[] = []; // only the science section (7B)
}
class SectionSevenA extends Section {
// uses NONE of the four specials — pure refused bequest
}
class SectionSevenB extends Section {
labSafetyBriefing(): string {
return `Lab assistant ${this.labAssistant} keeps the register of ` +
`${this.chemicalsRegister.length} chemicals`;
}
}
class SectionSevenC extends Section {
swimmingPlan(): string {
return `Swimming at ${this.poolTimings}, lane ${this.poolLane}`;
}
}বেচারা SectionSevenA দেখো — তারিকের সাধারণ সেকশন চারটা field inherit করছে যেগুলো তার কোনো কাজে আসে না। আর parent class ভিড়াক্রান্ত মেইন বোর্ডের মতো — নতুন কেউ দেখলে বুঝতে পারবে না কোন field "একটা সেকশন" মানে আর কোনগুলো এক সেকশনের ব্যক্তিগত ব্যাপার।
প্রতিটি special তার আসল মালিকের কাছে নামাও, একবারে একটা field, মাঝে test করে:
// AFTER: each board carries exactly its own notices
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001";
protected classTeacher = "";
// That is ALL sections truly share — the parent's promise is honest again.
}
class SectionSevenA extends Section {
// nothing irrelevant inherited; what you see is what 7A has
}
class SectionSevenB extends Section {
private labAssistant = "Mr. Rao"; // science specials, private now
private chemicalsRegister: string[] = [];
labSafetyBriefing(): string {
return `Lab assistant ${this.labAssistant} keeps the register of ` +
`${this.chemicalsRegister.length} chemicals`;
}
}
class SectionSevenC extends Section {
private poolTimings = "Mon/Thu 8:00-9:30"; // sports specials, private now
private poolLane = 4;
swimmingPlan(): string {
return `Swimming at ${this.poolTimings}, lane ${this.poolLane}`;
}
}Superclass তার সৎ মূলে সংকুচিত হলো। প্রতিটি subclass এখন নিজেকে document করছে: SectionSevenB খুললেই lab field-গুলো বলে দেবে science section-কে কী বিশেষ করে। আর চারটা সরানো field সবই private হলো — চারটা দরজা সংকীর্ণ হলো।
উদাহরণ ছাড়ার আগে একটু ভাবো। ধরো পরের বছর স্কুল SectionSevenD খোলে, আরেকটা sports section যেটাও সাঁতার কাটে। এখন দুটো subclass-এর pool field দরকার। Push Down Field মানে দুটো copy — সহনীয়, কিন্তু smell আছে। সেই সময়ে ভালো design হলো একটা intermediate parent: Section আর দুটো swimming section-এর মাঝে একটা SportsSection class। Compass সুন্দরভাবে কাজ করে: প্রতিটি member সবচেয়ে নিচের স্তরে থাকা উচিত যেখানে নিচের সবার এখনো দরকার। এক ব্যবহারকারী — সেই subclass। কিছু ব্যবহারকারী — ঠিক সেই দলের intermediate class। সব ব্যবহারকারী — root।
Python-এ একই ধারণা
Python-এর push-down একই রকম দেখায়, আর underscore convention visibility tightening-কে নামেই দৃশ্যমান করে:
# BEFORE: one-section state declared for every section
class Section:
def __init__(self):
self._school_address = "12 Lake Road, Pune 411001"
self._pool_timings = "Mon/Thu 8:00-9:30" # only SectionSevenC uses these
self._pool_lane = 0
# AFTER: pool state lives in the sports section only
class Section:
def __init__(self):
self._school_address = "12 Lake Road, Pune 411001"
class SectionSevenA(Section):
pass # honest and empty-handed
class SectionSevenC(Section):
def __init__(self, pool_timings: str, pool_lane: int):
super().__init__()
self.__pool_timings = pool_timings # double underscore: name-mangled,
self.__pool_lane = pool_lane # the Python flavour of private
def swimming_plan(self) -> str:
return f"Swimming at {self.__pool_timings}, lane {self.__pool_lane}"Python-এ একটা বিশেষ কথা: Python-এ attribute assignment-এর সময়ে তৈরি হয়। তাই "before" hierarchy চুপচাপ প্রতিটি section instance-কে pool attribute allocate করতে বাধ্য করছে, এমনকি যখন সেগুলো dummy value ধরে। Push-down-এর পরে, শুধু SectionSevenC instance-গুলো pool state-এর জন্য memory নেয়। Python-এ push-down শুধু সততা নয় — প্রতিটি object-এ একটা ছোট memory আর clarity জয়ও।
C#-এ একই refactoring
C#-এ স্কুলের সংস্করণ, আগে:
// BEFORE: one-section state declared for every section
abstract class Section
{
protected string _schoolAddress = "12 Lake Road, Pune 411001";
protected string _poolTimings = ""; // only SectionSevenC uses these
protected int _poolLane;
}
class SectionSevenA : Section { /* inherits pool fields it never wants */ }
class SectionSevenC : Section
{
public string SwimmingPlan() =>
$"Swimming at {_poolTimings}, lane {_poolLane}";
}নিচে নামানোর পরে — visibility আঁটো করে আর field-গুলো readonly করে, কারণ একবারই set হয়:
// AFTER: pool state lives in the sports section only
abstract class Section
{
protected string _schoolAddress = "12 Lake Road, Pune 411001";
}
class SectionSevenA : Section { /* honest and empty-handed */ }
class SectionSevenC : Section
{
private readonly string _poolTimings; // private again — smallest door
private readonly int _poolLane;
public SectionSevenC(string poolTimings, int poolLane)
{
_poolTimings = poolTimings; // subclass-specific setup stays
_poolLane = poolLane; // in the subclass constructor
}
public string SwimmingPlan() =>
$"Swimming at {_poolTimings}, lane {_poolLane}";
}C#-এর জন্য কিছু বিশেষ নোট:
- আক্রমণাত্মকভাবে আঁটো করো।
protectedথেকেprivateheadline, কিন্তু C# আরও দেয়: set-once field-এর জন্যreadonly, অথবা class-এর style property prefer করলেprivateauto-property। Push-down এই upgrade-এর স্বাভাবিক মুহূর্ত, কারণ এখন তুমি একটাই class থেকে field নিয়ন্ত্রণ করছো। - Parent-typed access দেখো। যদি কোনো code
Section s = GetSection(); var t = s._poolTimings;করত, fieldSectionছাড়লেই compile বন্ধ হবে। Cast দিয়ে "ঠিক করতে" যেও না — সেটা নিজেই একটা design smell। Behaviour (SwimmingPlan()) subclass-এ দাও আর caller-দের সঠিক type ধরতে দাও। - Constructor knock-on। যদি base constructor pushed-down field initialize করত, সেই parameter আর assignment subclass constructor-এ নেমে আসে, ঠিক উপরে দেখানো মতো।
IDE সমর্থন
Push-down JetBrains পরিবারে first-class tool support পায়, অন্যত্র partial:
- IntelliJ IDEA / PhpStorm: Refactor → Push Members Down… dialog খোলে — সরানোর member tick করো, IDE সেগুলো direct subclass-এ নিয়ে যায়, parent থেকে মুছে দেয়।
- JetBrains Rider / ReSharper (C#): Refactor → Push Members Down base type থেকে direct inheritor-এ member সরায়, conflict analysis-সহ যা base type-এর মাধ্যমে usage flag করে।
- Visual Studio (ReSharper ছাড়া): built-in "push members down" নেই current releases-এ — manually করতে হয়: subclass-এ declare করো, parent থেকে মুছো, compiler guide করুক। Manual route-ই আমাদের step-by-step recipe।
- Eclipse (Java): Refactor → Push Down… বছরের পর বছর ধরে আছে, field আর method উভয়েই কাজ করে।
Tool-এর conflict checker grep-এর জায়গা নেয়, বিচার-বুদ্ধির নয়। এটা বলবে কে field ব্যবহার করছে; দুজন ব্যবহারকারী দুটো copy পাবে না intermediate class পাবে — সেই সিদ্ধান্ত শুধু তুমিই নিতে পারবে।
সুবিধা আর ঝুঁকি
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
| Superclass সত্যি বলে: এটা যা declare করে তা প্রতিটি child সংক্রান্ত | দুই বা তার বেশি subclass field ব্যবহার করলে push down duplicate কপি তৈরি করে |
| যে subclass কখনো field ব্যবহার করেনি তারা বিভ্রান্তিকর মৃত ভার বহন বন্ধ করে | Parent-typed reference দিয়ে field access করা code ভেঙে যায়, পুনর্ডিজাইন করতে হবে |
Visibility আঁটো হয় — protected থেকে private, blast radius সংকুচিত | কয়েকটি subclass-এ push করলে একটা concept ছড়িয়ে পড়তে পারে (Extract Subclass বিবেচনা করো) |
| Subclass self-documenting হয়: তার বিশেষ data ভেতরেই থাকে | Companion method আর constructor logic সাধারণত নিচে নামে |
| Refused Bequest সারায়, Speculative Generality ছাঁটে | ঠিক এক subclass ব্যবহার করলে প্রায় কোনো ঝুঁকিই নেই — pure win |
আর এখন সিরিজের দিকনির্দেশনা নিয়ে শেষ কথা — এই post pair-টা সম্পূর্ণ করে। Pull Up Field আর Push Down Field exact inverse — একটা tool, দুটো দিক, স্কুল ভবনে একটা elevator-এর মতো। পুরো সিরিজ এক table-এ:
| Refactoring | দিক | স্কুলের গল্প | যে Smell সারায় |
|---|---|---|---|
| Pull Up Field | উপরে | তিনটা বোর্ডে copy করা ঠিকানা | Duplicate Code (state) |
| Pull Up Method | উপরে | তিনটা ভাসমান leave application sheet | Duplicate Code (behaviour) |
| Pull Up Constructor Body | উপরে | প্রথম পিরিয়ডের আগে shared সকালের শুরু | Duplicate Code (initialization) |
| Push Down Field (এই post) | নিচে | পুলের সময়সূচি ৭সি-র বোর্ডে নামানো | Refused Bequest |
- Pull up করো যখন duplication সত্যিকার: একই field, একই অর্থ, প্রতিটি sibling-এ। নিচে copy রাখলে drift হয়। না pulling up করার খরচ হলো Duplicate Code।
- Push down করো যখন sharing নকল: parent field যা শুধু কিছু child স্পর্শ করে। উপরে রাখলে মিথ্যা প্রতিশ্রুতি ছড়ায়। না pushing down করার খরচ হলো Refused Bequest।
- কোনো দিকই "ভালো" না। একই field একটা system-এর জীবনে দুবার elevator চড়তে পারে: feature niche হলে নিচে নামানো, niche universal হলে আবার উপরে। Hierarchy সুস্থ যখন প্রতিটি member সবচেয়ে নিচের স্তরে যেখানে নিচের সব class এখনো দরকার — code review-এ প্রতিটি নতুন field-এ এই প্রশ্ন করো: এই নোটিশটা কোন বোর্ডে থাকা উচিত?
কোন smell সারায়?
| Smell | Push Down Field কীভাবে সাহায্য করে |
|---|---|
| Refused Bequest | Data রূপের প্রাথমিক সমাধান — child-রা কখনো না চাওয়া state inherit করা বন্ধ করে |
| Speculative Generality | Shared abstraction থেকে "একদিন সব সেকশন সাঁতার কাটবে" field সরায় |
| Large Class (স্ফীত superclass) | প্রতিটি push-down parent-কে তার সৎ, common core-এর দিকে slim করে |
| Duplicate Code | Indirect — কখন না push down করতে হবে জানা duplication এড়ায়, বিপরীত compass action-এ |
দ্রুত revision box
+--------------------------------------------------------------+
| PUSH DOWN FIELD — REVISION |
+--------------------------------------------------------------+
| Story : "Swimming Pool Timings" pinned on the MAIN board, |
| but only 7C swims -> confused 7A student at the |
| pool, crowded board nobody trusts. |
| Fix: move the notice DOWN to 7C's board only. |
| |
| Move : Field in superclass used by only SOME children |
| ---> declared only in the subclass(es) that |
| actually use it |
| |
| Do it when : usage is genuinely narrow (ideally ONE child) |
| Don't when : all/most children use it (that's Pull Up |
| territory) or parent-typed code needs it |
| |
| Safe steps : find ALL usages (incl. parent-typed access!) |
| -> declare in real user(s) -> test -> delete |
| from parent -> test -> tighten protected -> |
| private -> move companion methods down too |
| |
| Bonus : visibility shrinks; subclass self-documents |
| Inverse : Pull Up Field — same elevator, other direction |
| Cures : Refused Bequest, Speculative Generality |
| Compass : a member lives at the LOWEST level where |
| everyone below still needs it |
+--------------------------------------------------------------+অনুশীলন exercise
সিরিজের শেষ exercise — আর এবার তোমাকে elevator-এর দুটো দিকই ব্যবহার করতে হবে। ধরো স্কুলের activity-club software:
abstract class Club {
protected clubName = "";
protected meetingDay = "";
protected instrumentInventory: string[] = []; // hmm...
protected debateTopicOfMonth = ""; // hmm...
protected annualBudget = 0;
}
class MusicClub extends Club {
tuneUp(): string {
return `${this.clubName}: checking ${this.instrumentInventory.length} instruments`;
}
}
class DebateClub extends Club {
announceTopic(): string {
return `${this.clubName} topic: ${this.debateTopicOfMonth}`;
}
}
class ChessClub extends Club {
// uses clubName, meetingDay, annualBudget — and nothing else
private chessSets = 10; // wait — should this even be here? yes! why?
}তোমার কাজগুলো:
- পাঁচটা parent field-এর প্রতিটির জন্য compass চালাও: কে আসলে ব্যবহার করছে? প্রতিটিকে stays up, MusicClub-এ push down, অথবা DebateClub-এ push down চিহ্নিত করো।
- নিরাপদ ক্রমে push-down করো: আসল ব্যবহারকারীতে declare করো, test করো,
Clubথেকে মুছো, test করো,private-এ আঁটো করো। ChessClub.chessSetsইতিমধ্যেprivateআর ইতিমধ্যে subclass-এ আছে। এক বাক্যে ব্যাখ্যা করো কেন এই field হলো সঠিক placement-এর উদাহরণ — এর কখনো কোনো refactoring দরকার ছিল না।- একজন teammate argue করছে
annualBudgetpush down করা উচিত "কারণ প্রতিটি club-এর budget আলাদা।" আলাদা value মানে আলাদা concept নয় — ব্যাখ্যা করো কেন field এখনো উপরে থাকে। (Hint: প্রতিটি club-এর budget আছে; school address-এর phone number-এর চেয়ে আলাদা value ছিল কিন্তু দুটোই মেইন বোর্ডে ছিল।) - Bonus, full-series প্রশ্ন: পরের term স্কুল
OrchestraClubযোগ করে, সেটাওinstrumentInventoryরাখে। এখন দুটো club-এর field দরকার। তিনটা option সাজাও — উভয়ে duplicate করো, intermediateMusicalClubparent, অথবাClub-এ ফিরে pull up করো — আর এক লাইনে justification দিয়ে একটা বেছে নাও।
যদি task ৫ আত্মবিশ্বাসের সাথে argue করতে পারো, তাহলে পুরো ছবি পেয়েছ: Pull Up Field, Pull Up Method, আর Pull Up Constructor Body সত্যিকারের shared জিনিস উপরে নেয়, Push Down Field সত্যিকারের বিশেষ জিনিস নিচে নামায় — আর স্কুলের notice board-গুলো এত পরিপাটি থাকে যে প্রতিটি ছাত্র প্রতিটি বোর্ড বিশ্বাস করে, তারিকের ৭এ দরজা থেকে রহিম ভাইয়ের মেইন অফিস দেওয়াল পর্যন্ত।
সচরাচর জিজ্ঞাসা
- Push Down Field আসলে কী করে?
- যখন superclass-এ declare করা কোনো field শুধু কিছু subclass ব্যবহার করে, Push Down Field সেই field-টাকে superclass থেকে সরিয়ে শুধু সেই subclass বা subclass-গুলোতে নিয়ে যায় যেগুলো আসলেই ব্যবহার করে। তখন parent-এ শুধু সেই state থাকে যা সত্যিকার অর্থে সব child-এর জন্য common।
- Push Down Field কি Pull Up Field-এর উল্টো?
- হ্যাঁ, একদম তাই। এরা একে অপরের সঠিক বিপরীত, আর দুটোই দরকার কারণ দুই ধরনের ভুলই হয়। সহজ কম্পাস হলো: প্রতিটি subclass যদি field শেয়ার করে, সেটা উপরে থাকা উচিত (Pull Up); শুধু একটা বা দুটো যদি ব্যবহার করে, তাহলে নিচে নামিয়ে দাও (Push Down)।
- চারটির মধ্যে দুটি subclass field ব্যবহার করলে — নিচে নামালে কি duplicate হবে না?
- হ্যাঁ, এটাই honest trade-off। দুটো আসল ব্যবহারকারীতে দুটো copy রাখা প্রায়ই ভালো, চারটি class-এ একটা বিভ্রান্তিকর copy জোর করে রাখার চেয়ে। কিন্তু ওই দুটো subclass যদি এই একটা field-এর বেশি কিছু share করে, তাহলে একটা intermediate parent দরকার হতে পারে — Extract Subclass বিবেচনা করো।
- Field নিচে নামালে কী ভেঙে যেতে পারে?
- যেকোনো code যা superclass-typed reference দিয়ে field access করত সেটা compile হওয়া বন্ধ করবে, কারণ parent আর এটা declare করে না। এটা আসলে দরকারি তথ্য: হয় সেই code-এর subclass type নিয়ে কাজ করা উচিত, অথবা field সত্যিই parent-এ থাকা উচিত আর তোমার থামা উচিত।
- Field নিচে নামানোর পরে কেন সেটা আবার private হতে পারে?
- Superclass-এ field-টাকে কমপক্ষে protected রাখতে হতো যাতে subclass-গুলো access করতে পারে। একবার সেটা শুধু একটাই class-এ থাকলে, আর কারো access দরকার নেই — তাই private করে দাও। এতে exposure কমে আর ভবিষ্যতে পরিবর্তন করা নিরাপদ হয়।
আরো দেখো
সম্পর্কিত পাঠ
Refused Bequest: যে ছেলে মিষ্টির দোকানের রেসিপি নিতে চায়নি
Refused Bequest কোড স্মেল শেখো একটা পারিবারিক মিষ্টির দোকানের গল্পের মাধ্যমে — TypeScript ও C#-এ Liskov লঙ্ঘন আর delegation দিয়ে সমাধান ধাপে ধাপে।
Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা
বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।
Speculative Generality: যে সুইমিং পুলের জন্য পাইপ বসালে, পুলটাই হলো না
বাড়ি বানানোর গল্প দিয়ে Speculative Generality smell বোঝো। YAGNI কী, ভবিষ্যতের অনুমানে কোড লেখা কেন ক্ষতিকর, আর অব্যবহৃত abstraction কীভাবে সরাতে হয় — সব পরিষ্কার হয়ে যাবে।
Pull Up Field: সবার জন্য একটাই নোটিশ বোর্ড
Pull Up Field refactoring শিখো একটা স্কুলের নোটিশ বোর্ডের গল্প দিয়ে — যে field প্রতিটা subclass-এ বারবার কপি হয়েছে সেটাকে superclass-এ তুলে দাও, TypeScript আর C#-এ নিরাপদ ধাপ, IDE সাপোর্ট, আর pull-up বনাম push-down কোনটা কখন করবে।