Pull Up Field: সবার জন্য একটাই নোটিশ বোর্ড
Pull Up Field refactoring শিখো একটা স্কুলের নোটিশ বোর্ডের গল্প দিয়ে — যে field প্রতিটা subclass-এ বারবার কপি হয়েছে সেটাকে superclass-এ তুলে দাও, TypeScript আর C#-এ নিরাপদ ধাপ, IDE সাপোর্ট, আর pull-up বনাম push-down কোনটা কখন করবে।
একটা ঠিকানা, তিনটা নোটিশ বোর্ড
ধরো তুমি ঢাকার একটা স্কুলে আছ। ক্লাস সেভেনে তিনটা সেকশন — ৭A, ৭B, আর ৭C। প্রতিটা সেকশনের ক্লাসরুমের দরজার পাশে নিজস্ব নোটিশ বোর্ড। আর নিচে, মেইন গেটের কাছে, প্রধান অফিসে পুরো স্কুলের জন্য একটা বড় নোটিশ বোর্ড। অফিসটা দেখাশোনা করেন জনাব সালাম, একজন শান্ত মানুষ — সেই বোর্ডের প্রতিটা নোটিশ তার মুখস্থ।
ক্লাস টিচারদেরও ভূমিকা আছে এখানে। মিসেস নাসরিন ৭A দেখেন, জনাব জামাল ৭B দেখেন, আর মিস সুমাইয়া ৭C দেখেন। তাদের সবার উপরে আছেন প্রধান শিক্ষিকা ড. ফাতেমা বেগম — এই সিরিজে তার সাথে বারবার দেখা হবে, কারণ তিনিই স্কুলের তথ্য-সমস্যাগুলো ঠিক করেন। আর তার প্রতিটা সংশোধন আসলে একটা refactoring।
কিছু বছর আগে সেকশনগুলো একটা pen-pal project শুরু করল। ছাত্রছাত্রীরা অন্য জেলার বাচ্চাদের চিঠি লিখত, আর প্রতিটা খামে ফেরতের ঠিকানা লিখতে হত। তাই রুবেল, ৭A-এর মনিটর, ৭A-এর নোটিশ বোর্ডে সুন্দর করে পুরো স্কুলের ঠিকানা লিখে দিল। করিম, ৭B-এর মনিটর, ৭B-এর বোর্ডে একই কাজ করল। ফারহানা, ৭C-এর মনিটর, সেও তাই করল। তিনটা বোর্ড, একই ঠিকানার তিনটা হাতে-লেখা কপি। কেউ মনে করেনি এটা সমস্যা। কেন করবে? প্রতিটা মনিটর শুধু সাহায্য করছিল।
কিছুদিন সব ঠিকঠাক চলল। তারপর ডাক বিভাগ এলাকার পোস্টাল কোড বদলে দিল। জনাব সালাম সেই বিকেলেই অফিসের রেকর্ড আপডেট করলেন। রুবেল মিসেস নাসরিনের কাছ থেকে শুনে ৭A-এর বোর্ড ঠিক করল। ফারহানা পরদিন সকালে ৭C-এর বোর্ড ঠিক করল। কিন্তু করিম পুরো সপ্তাহ জ্বরে বাড়িতে। আর ৭B-তে আর কেউ মনে করল না যে এটা তাদের বোর্ড পরিবর্তনের দায়িত্ব।
পরের দুই মাস ধরে ৭B থেকে যাওয়া প্রতিটা চিঠিতে পুরনো পোস্টাল কোড। উত্তর ফিরে আসতে লাগল। চট্টগ্রামের pen-pal-রা ভাবল ৭B-এর বাচ্চারা চিঠি লেখা বন্ধ করে দিয়েছে। ৭B-এর বাচ্চারা ভাবল তাদের pen-pal-রা ভুলে গেছে। জনাব জামাল সত্যিটা জানলেন যখন তার টেবিলে ফেরত আসা খামের একটা বান্ডেল এল — সবগুলোতে প্রাপক পাওয়া যায়নি stamp মারা। এত কষ্ট শুধু একটা কারণে — তিনটা কপির মধ্যে একটা আপডেট হয়নি।
ড. ফাতেমা বেগম সোমবারের সমাবেশে একটাই নিয়ম ঘোষণা করলেন: "স্কুলের ঠিকানা প্রতিটা সেকশনের জন্য একই। এটা একবারই লেখা হবে, প্রধান অফিস বোর্ডে। শুধু জনাব সালাম এটা রক্ষণাবেক্ষণ করবেন। সেকশন বোর্ডে শুধু সেই সেকশনের নিজস্ব জিনিস থাকবে — ক্লাস টাইমটেবিল, ক্লাস টিচারের নাম, ডিউটি রোস্টার।"
ব্যাপারটা বুঝলে? সবাই যে তথ্য শেয়ার করে সেটা একটা shared জায়গায়, উপরে থাকা উচিত। শুধু একটা সেকশনের তথ্য সেই সেকশনের বোর্ডেই থাকে।
কোডে, সেকশন বোর্ডগুলো হল subclass, আর প্রধান অফিস বোর্ড হল superclass। যখন প্রতিটা subclass একই field-এর নিজের কপি declare করে, আমরা সেই field-টা উপরে superclass-এ তুলে দিই। এই refactoring-কে বলে Pull Up Field।
জনাব সালামের দৃষ্টিকোণ থেকে পুরো কষ্টের-তারপর-শান্তির অভিজ্ঞতাটা একটা journey হিসেবে দেখো:
satisfaction score দেখো। সংশোধনের আগে প্রতিটা আপডেটে বিল্ডিং জুড়ে ক্লান্তিকর ঘুরে বেড়ানো, আর একটা বোর্ড মিস করার সত্যিকার সম্ভাবনা। সংশোধনের পরে — একটাই শান্ত edit, পুরো স্কুল সঠিক।
Pull Up Field কী জিনিস?
Pull Up Field হল Martin Fowler-এর Refactoring catalog-এর সবচেয়ে সহজ refactoring-গুলোর একটা। এটা generalization নিয়ে কাজ করে — মানে parent-child class সম্পর্ক পরিপাটি করা। নিয়মটা ছোট:
যখন দুই বা তার বেশি subclass একই field declare করে — একই তথ্য, একই অর্থ — কপিগুলো মুছে দাও আর field-টা একবারই তাদের common superclass-এ declare করো।
এরকম duplicate field কেন আসে? কারণ class hierarchy সাধারণত নিচ থেকে বাড়ে। একজন developer SectionSevenA লেখে, তাকে দরকারি field দেয়। পরে কেউ SectionSevenB লেখে — প্রায়ই copy-paste করে — একই field আবার আসে। তারপর SectionSevenC আসে। কেউ duplication plan করেনি। এটা জমে গেছে, ঠিক যেভাবে তিনজন মনিটর একে অপরের সাথে আলোচনা না করেই ঠিকানা কপি করেছে।
সমস্যাটা ঠিক পোস্টাল কোডের সমস্যার মতো। "একই জিনিস"-এর তিনটা declaration চুপচাপ আলাদা হয়ে যেতে পারে। একটা subclass এটাকে schoolAddress বলে, আরেকটা বলে address। একটা initialize করে, আরেকটা ভুলে যায়। যখন value-এর type বা অর্থ পরিবর্তন করতে হবে, তোমাকে প্রতিটা subclass-এ পরিবর্তন মনে রাখতে হবে — আর compiler তোমাকে সেই মিস করা কপির কথা মনে করিয়ে দেবে না।
আরেকটা বড় কারণ আছে pull up করার। যতক্ষণ data subclass-গুলোতে ছড়িয়ে আছে, superclass কোনো behaviour host করতে পারবে না যেটা এই data ব্যবহার করে। একটা shared printEnvelope() method parent-এ থাকতে পারবে না যদি তার দরকারি address শুধু children-এ থাকে। Pull Up Field parent-কে data দেয়, আর এটা unlock করে পরের refactoring, Pull Up Method — সেটাই আসলে বড় পুরস্কার।
এক লাইনে: Pull Up Field এমন একটা field superclass-এ তুলে দেয় যেটা প্রতিটা subclass duplicate করছিল — তিনটা সেকশন বোর্ডের বদলে প্রধান অফিস বোর্ডে ঠিকানা লেখার মতো।
একটু গভীরে যাই: ড. ফাতেমা বেগম যা enforce করলেন সেটা হল DRY principle — Don't Repeat Yourself — inheritance hierarchy-তে state-এ প্রয়োগ। DRY প্রায়ই বলা হয় "একই কোড দুবার লিখো না", কিন্তু আসল কথাটা আরও ধারালো: জ্ঞানের প্রতিটা অংশের সিস্টেমে একটাই, authoritative representation থাকা উচিত। স্কুলের ঠিকানা হল জ্ঞানের একটা অংশ। তিনটা বোর্ড মানে তিনটা representation, আর সিস্টেমে truth-এর কোনো single source ছিল না। Pull Up Field shared state-এর জন্য একটাই authoritative representation ফিরিয়ে আনে — ঠিক যেভাবে database normalization drifting column-এর জন্য করে। মনে রেখো: duplicate subclass field হল OOP-তে ঠিক সেটাই যা database-এ denormalized, drifting column।
আরেকটা কথা। Pull Up Field-এর একটা exact উল্টোটা আছে — Push Down Field। Pull Up একটা field উপরে নেয় কারণ সবাই এটা শেয়ার করে। Push Down নিচে নেয় কারণ শুধু কিছু children-এর এটা দরকার। পুরো hierarchy move-এর পরিবারটা একটা ছোট map-এ এঁটে যায়:
দুটো দিকের তুলনা benefits section-এ করব।
কখন এটা দরকার?
তোমার codebase-এ এই লক্ষণগুলো দেখো:
- একই field দুই বা তার বেশি sibling subclass-এ দেখা দিচ্ছে। একই type, একই উদ্দেশ্য, প্রায়ই একই comment। এটাই Duplicate Code-এর classic রূপ — শুধু এবার duplicate হচ্ছে state, logic নয়।
- Field-গুলোর নাম সামান্য আলাদা কিন্তু অর্থ এক। ৭A-এর class-এ
schoolAddress, ৭B-তেaddress, ৭C-তেschoolAddr। ভবিষ্যতের reader-কে তিনটা file খুলে তুলনা করতে হবে বুঝতে যে এগুলো আসলে একই concept। সেই কষ্টটা প্রতিটা reader বহন করে। - একটা পরিবর্তন কয়েকটা জায়গায় করতে হয়েছে। সাম্প্রতিক কোনো কাজে — field-এর type পরিবর্তন, rename, default পরিবর্তন — তিনটা subclass-এ একই edit করতে হয়েছে? তাহলে field-টা pull up হওয়ার জন্য চিৎকার করছে।
- একটা method pull up করতে চাইছ কিন্তু parent-এ compile হচ্ছে না।
this.schoolAddressread করে এমন method superclass-এ যেতে পারবে না যতক্ষণ field নিচে আছে। Pull Up Field হল Pull Up Method আর Pull Up Constructor Body-এর আগের standard preparation step।
আর কখন করবে না:
- শুধু কিছু subclass field-টা ব্যবহার করে। ধরো শুধু ৭C-এর
swimmingPoolTimingsদরকার। সেটাকে parent-এ তুললে ৭A আর ৭B-কে এমন তথ্য বহন করতে বাধ্য করা হবে যা তারা কখনো ছোঁয় না। এটাই Refused Bequest-এর পথ। সবার জন্য shared data-র জন্য pull up করো। কিছু-children-only data নিচে রাখো।
কিছু pull করার আগে একটা সহজ প্রশ্ন করো: "এই field কি প্রতিটা subclass-এর জন্য একই অর্থ বহন করে, আজকে আর সম্ভবত ভবিষ্যতেও?" সৎ উত্তর হ্যাঁ হলে pull up করো। উত্তর "হ্যাঁ, বেশিরভাগ, তবে..." হলে থামো আর আবার ভাবো। দুটো field যারা শুধু নাম শেয়ার করে কিন্তু ভিন্ন অর্থ বহন করে তাদের আলাদাই রাখতে হবে — সেগুলো merge করলে মিথ্যা abstraction তৈরি হয়, যেটা একটু duplication-এর চেয়ে অনেক বেশি খারাপ।
সিদ্ধান্তটা আসলে দুটো axis-এ অবস্থান — কতটা subclass এটা ব্যবহার করে আর এটা এখন কোথায় আছে। আমাদের স্কুলের ঠিকানা সরাসরি "pull up now" কোণে:
এভাবে পড়ো: আমাদের address field সব সেকশন ব্যবহার করে (ডানদিকে) কিন্তু এখন subclass-এ আছে (নিচে) — এটাই "pull up now" zone। refactoring-এর পরে, dot সরাসরি উপরে উঠে "healthy in base"-এ যাবে।
এক নজরে আগে আর পরে
দেখো TypeScript-এ পোস্টাল কোডের বিপর্যয়ের সময় স্কুলটা কেমন ছিল। প্রতিটা section class shared তথ্যের নিজের কপি রাখছে:
// BEFORE: the same fields declared three times
abstract class Section {
// the office board is empty — it knows nothing!
}
class SectionSevenA extends Section {
protected schoolAddress = "12 Lake Road, Pune 411001"; // Priya's copy
protected classTeacher = "Mrs. Iyer";
}
class SectionSevenB extends Section {
protected address = "12 Lake Road, Pune 411001"; // Arjun's copy, different name!
protected classTeacher = "Mr. Bose";
}
class SectionSevenC extends Section {
protected schoolAddress = "12 Lake Road, Pune 411038"; // Meera's copy — already drifted!
protected classTeacher = "Ms. Fernandes";
}৭C-এর দিকে ভালো করে তাকাও। এর কপিতে ইতিমধ্যে আলাদা কোড আছে — 411001-এর বদলে 411038। কেউ এটা decide করেনি। কপিগুলো শুধু আলাদা হয়ে গেছে, ঠিক নোটিশ বোর্ডের মতো। আর ৭B চুপচাপ field-এর নাম বদলে দিয়েছে, তাই schoolAddress খুঁজলে সেটাও পাওয়া যাবে না।
Pull Up Field-এর পরে:
// AFTER: shared facts live once, in the parent
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001"; // declared ONCE
}
class SectionSevenA extends Section {
protected classTeacher = "Mrs. Iyer"; // only section-specific facts remain
}
class SectionSevenB extends Section {
protected classTeacher = "Mr. Bose";
}
class SectionSevenC extends Section {
protected classTeacher = "Ms. Fernandes";
}ঠিকানা একবারই লেখা, অফিস বোর্ডে। প্রতিটা section board-এ শুধু সেই section-এর নিজের জিনিস: নিজের class teacher। পোস্টাল কোড আবার বদলালে সংশোধন এক লাইন-এ — শুধু জনাব সালামকে জানতে হবে।
class diagram-গুলো movement স্পষ্ট করে দেখাবে:
move করার আগে duplication তিনটা section-এ সমানভাবে ছড়িয়ে আছে:
move-এর পরে, সেই pie একটা slice-এ সংকুচিত হয় — superclass-এ। এটাই মূল কথা।
নিরাপদ উপায়ে ধাপে ধাপে
Pull Up Field দেখতে এক মিনিটের কাজ, আর প্রায়ই তাই। কিন্তু নিরাপদ পথে একটা নির্দিষ্ট ক্রম আছে। ধাপ এড়িয়ে গেলেই "এক মিনিটের কাজ" সন্ধ্যাব্যাপী debugging session হয়ে যায়। ড. ফাতেমা বেগমের সমাধানের মতো overall flow এরকম:
পুরো recipe এখানে।
-
নিশ্চিত করো field-গুলো সত্যিই একই জিনিস বোঝায়। প্রতিটা subclass খোলো। type, initial value, আর — সবচেয়ে গুরুত্বপূর্ণ — অর্থ পরীক্ষা করো। তিনটা section-এ
schoolAddressমানে "এই স্কুলের ঠিকানা", তাই এগুলো match করে। কিন্তু ৭C-এর field যদি "sports annexe-এর ঠিকানা" হত, তাহলে সেটা শুধু একইরকম দেখাত — pull up করা ভুল হত। -
আগে সব কপিকে একটা common নাম দাও। ৭B-এর field-কে
addressবলা হচ্ছে। এটাকে ৭B-এর ভেতরেschoolAddress-এ rename করো, সব reference update করো, test চালাও। move করার আগে rename করলে প্রতিটা পরিবর্তন ছোট আর check করা যায়:// INTERMEDIATE STEP: same name everywhere, nothing moved yet class SectionSevenB extends Section { protected schoolAddress = "12 Lake Road, Pune 411001"; // was: address protected classTeacher = "Mr. Bose"; } -
যেকোনো drifted value ঠিক করো। ৭C-এর কপিতে
411038আছে। এটা কি পুরনো leftover নাকি সত্যিকার পার্থক্য? জিজ্ঞেস করো, check করো, decide করো। আমাদের গল্পে এটা কখনো update হয়নি — একটা সত্যিকার bug যেটা এই refactoring বিনামূল্যে খুঁজে দিয়েছে। সব কপিতে একই value রাখো। পার্থক্যটা ইচ্ছাকৃত হলে field-টা আসলে shared না — এখানেই থামো। -
superclass-এ field declare করো। subclass কপিগুলো
privateছিল? তাহলে parent-এর version অন্ততprotectedহতে হবে, নাহলে subclass দেখতে পাবে না। TypeScript আর C#-এprotectedমানে "আমি আর আমার children ব্যবহার করতে পারব।" -
একটা subclass থেকে field মুছো। Compile করো। Test করো। শুধু
SectionSevenAথেকেschoolAddressসরাও। subclass এখন parent-এর কপি inherit করে, পুরনো code কাজ করতে থাকে। test suite চালাও। -
বাকি প্রতিটা subclass-এর জন্য একে একে পুনরাবৃত্তি করো। ৭B থেকে মুছো, test করো। ৭C থেকে মুছো, test করো। ছোট ছোট পদক্ষেপে কাজ করলে test fail হলে ঠিক কোন deletion কারণ সেটা জানা যাবে।
-
ঐচ্ছিকভাবে, field-কে accessor-এর পেছনে লুকাও। Refactoring Guru পরামর্শ দেয় Self-Encapsulate Field দিয়ে follow up করতে — subclass-গুলো field সরাসরি না ছুঁয়ে getter-এর মাধ্যমে data reach করুক। এটা parent-কে পরে value কীভাবে store করে সেটা বদলানোর স্বাধীনতা দেয়।
এই ধাপগুলোতে field-এর lifecycle একটা ছোট state machine:
শেষে একবার নয়, প্রতিটা subclass স্পর্শ করার পরেই test চালাও। একসাথে তিনটা কপি মুছে test fail হলে তিনটা সন্দেহভাজন। একটা একটা মুছলে সবসময় ঠিক একটাই সন্দেহভাজন। Refactoring-এর safety আসে ছোট ছোট ধাপে সাথে সাথে green test থেকে — এটাই পুরো কৌশল।
একটু গভীরে: ধাপ ৪ একটা সত্যিকার design trade-off লুকিয়ে রাখে। parent field protected করলে visibility বেড়ে যায় — এখন প্রতিটা বর্তমান আর ভবিষ্যত subclass সরাসরি read আর write করতে পারে, সবাইকে parent-এর storage decision-এর সাথে couple করে। অনেক style guide আর SonarQube-এর মতো tool এই কারণেই protected mutable field flag করে। পরিপক্ক বিকল্প হল parent-এ private field রাখা সাথে protected getter। subclass-গুলো shared fact read করতে পারবে কিন্তু scribble করতে পারবে না। কিছু বেশি line-এর বিনিময়ে অনেক ছোট mutation surface। field conceptually read-only হলে (স্কুলের ঠিকানা section-এর ভেতর থেকে খুব কমই বদলায়) getter form পছন্দ করো।
Runtime কীভাবে দেখে এটাকে
pull-up-এর পরে runtime-এ আসলে কী হয় সেটা এক মুহূর্ত দেখা দরকার। যখন একটা section object তৈরি হয় আর envelope label চাওয়া হয়, address inherited parent state থেকে fetch হয় — কোনো copying নেই:
এটাই "উপরে একটা বোর্ড"-এর সঠিক technical অর্থ। subclass-এর নিজের কোনো address নেই। সে উপরে পৌঁছায় — base class যেটা ধরে রাখে সেখান থেকে নেয়। আর কিছু drift করার নেই।
একটু বড় বাস্তব উদাহরণ
ধরো Sunrise School-এর notice-board system একটু বেশি realistic। refactoring-এর আগে, প্রতিটা section class তিনটা shared fact পুনরাবৃত্তি করছে — ঠিকানা, ফোন নম্বর, আর principal-এর নাম — পাশাপাশি নিজের data:
// BEFORE: three shared facts, copied into every section
abstract class Section {}
class SectionSevenA extends Section {
protected schoolAddress = "12 Lake Road, Pune 411001";
protected schoolPhone = "020-2553-1100";
protected principalName = "Dr. Meena Kulkarni";
protected classTeacher = "Mrs. Iyer";
protected studentCount = 38;
envelopeLabel(): string {
return `Class 7A, ${this.schoolAddress}`;
}
}
class SectionSevenB extends Section {
protected schoolAddress = "12 Lake Road, Pune 411001";
protected schoolPhone = "020-2553-1100";
protected principalName = "Dr. Meena Kulkarni";
protected classTeacher = "Mr. Bose";
protected studentCount = 41;
envelopeLabel(): string {
return `Class 7B, ${this.schoolAddress}`;
}
}
class SectionSevenC extends Section {
protected schoolAddress = "12 Lake Road, Pune 411001";
protected schoolPhone = "020-2553-1100";
protected principalName = "Dr. Meena Kulkarni";
protected classTeacher = "Ms. Fernandes";
protected studentCount = 35;
envelopeLabel(): string {
return `Class 7C, ${this.schoolAddress}`;
}
}গুনে দেখো: তিনটা shared field × তিনটা section = তিনটা fact-এর জন্য নয়টা declaration। নতুন principal আসলে তিনটা edit। নতুন ফোন নম্বর হলে আরও তিনটা। আর envelopeLabel() method-গুলো দেখো — প্রায় একই, কিন্তু এখনো উপরে নিয়ে যাওয়া যাচ্ছে না, কারণ এর দরকারি address children-এ আছে।
এখন তিনটা shared field উপরে pull করি, একটা একটা করে, প্রতিটার পর test চালিয়ে:
// AFTER: shared facts upstairs, section facts downstairs
abstract class Section {
protected schoolAddress = "12 Lake Road, Pune 411001";
protected schoolPhone = "020-2553-1100";
protected principalName = "Dr. Meena Kulkarni";
// Now that the data lives here, shared behaviour CAN live here too.
envelopeLabel(): string {
return `${this.sectionName()}, ${this.schoolAddress}`;
}
protected abstract sectionName(): string;
}
class SectionSevenA extends Section {
protected classTeacher = "Mrs. Iyer";
protected studentCount = 38;
protected sectionName(): string { return "Class 7A"; }
}
class SectionSevenB extends Section {
protected classTeacher = "Mr. Bose";
protected studentCount = 41;
protected sectionName(): string { return "Class 7B"; }
}
class SectionSevenC extends Section {
protected classTeacher = "Ms. Fernandes";
protected studentCount = 35;
protected sectionName(): string { return "Class 7C"; }
}নয়টা declaration তিনটা হয়ে গেল। আর দেখো envelopeLabel()-এ কী হল — address উপরে যেতেই, duplicate method-টাও অনুসরণ করতে পারল। সেই দ্বিতীয় move হল Pull Up Method, পরের post-এর বিষয়। এটাই এই page-এর সবচেয়ে গুরুত্বপূর্ণ শিক্ষা। Pull Up Field খুব কমই আসল লক্ষ্য। এটা দরজা খোলে। আগে data, তারপর behaviour।
রক্ষণাবেক্ষণের হিসাবটাও বদলে যায়। ধরো একটা shared fact পরিবর্তন হল — নতুন principal, নতুন পোস্টাল কোড, নতুন ফোন নম্বর। কতটা class body edit করতে হবে?
তিনটা edit একটায় সংকুচিত হয়। আর গভীর জয়টা সাশ্রয় হওয়া typing নয় — বরং ভুলে যাওয়ার সুযোগ মুছে দেওয়া। ৭B-এর বিপর্যয় কখনো effort নিয়ে ছিল না। এটা ছিল এমন একটা কপি যা কেউ মনে রাখেনি।
Python-এ একই ধারণা
Python-এ protected keyword নেই, কিন্তু single-underscore prefix convention একই ভূমিকা পালন করে। move-টাও অভিন্ন:
# BEFORE: each section class repeats the shared fact
class Section:
pass
class SectionSevenA(Section):
def __init__(self):
self._school_address = "12 Lake Road, Pune 411001" # copy 1
self._class_teacher = "Mrs. Iyer"
class SectionSevenB(Section):
def __init__(self):
self._school_address = "12 Lake Road, Pune 411001" # copy 2
self._class_teacher = "Mr. Bose"
# AFTER: declared once, inherited by all
class Section:
SCHOOL_ADDRESS = "12 Lake Road, Pune 411001" # one class-level copy
class SectionSevenA(Section):
def __init__(self):
self._class_teacher = "Mrs. Iyer"
class SectionSevenB(Section):
def __init__(self):
self._class_teacher = "Mr. Bose"Python-এর একটা বোনাস দেখো। address প্রতিটা instance-এর জন্যও আর প্রতিটা subclass-এর জন্যও একই, তাই এটা instance attribute-এর বদলে class attribute (SCHOOL_ADDRESS) হতে পারে। Python-এর স্বাভাবিক উপায় বলার যে "এই fact টা type-এর, প্রতিটা object-এর নয়।" প্রতিটা section-এর প্রতিটা instance normal attribute lookup-এ একই single value read করে — ঠিক একটা ছাত্র অফিস বোর্ড দেখতে নিচে হেঁটে যাওয়ার মতো।
C#-এ একই refactoring
C#-এও ধারণা অভিন্ন। শুধু spelling বদলায়। আগে:
// BEFORE: every section carries its own copy
abstract class Section { }
class SectionSevenA : Section
{
protected string _schoolAddress = "12 Lake Road, Pune 411001"; // copy 1
protected string _classTeacher = "Mrs. Iyer";
}
class SectionSevenB : Section
{
protected string _schoolAddress = "12 Lake Road, Pune 411001"; // copy 2
protected string _classTeacher = "Mr. Bose";
}field pull up করার পরে:
// AFTER: one protected field in the base class
abstract class Section
{
protected string _schoolAddress = "12 Lake Road, Pune 411001"; // declared once
public string EnvelopeLabel() => $"{SectionName}, {_schoolAddress}";
protected abstract string SectionName { get; }
}
class SectionSevenA : Section
{
protected string _classTeacher = "Mrs. Iyer";
protected override string SectionName => "Class 7A";
}
class SectionSevenB : Section
{
protected string _classTeacher = "Mr. Bose";
protected override string SectionName => "Class 7B";
}দুটো C#-নির্দিষ্ট নোট:
- Visibility: subclass field-গুলো
privateছিল? তাহলে base-class versionprotectedহতে হবে (অথবা তার চারপাশেprotectedproperty সহprivate)। অনেক C# team raw field-এর বদলেprotectedproperty expose করতে পছন্দ করে —protected string SchoolAddress { get; }— কারণ property পরে validation যোগ করতে বা storage বদলাতে পারে subclass না ভেঙেই। - Initialization: subclass constructor-গুলো parameter থেকে এই field set করত? তাহলে duplicate assignment line-গুলো পরের target। ঠিক এটাই Pull Up Constructor Body handle করে, এখান থেকে দুটো post পরে।
IDE সাপোর্ট
ভালো খবর: তোমাকে এটা হাতে করতে হবে না।
- IntelliJ IDEA, Rider, PhpStorm (JetBrains পরিবার): field select করো, তারপর Refactor → Pull Members Up... dialog খোলো। class-এর member-গুলো checkbox সহ list হবে। field tick করো, destination superclass choose করো, আর IDE declaration সরিয়ে subclass কপি মুছে দেয়। একই dialog বিপরীত, Push Members Down চালায়।
- Visual Studio (C#): member-এ cursor রাখো, Ctrl+. চাপো Quick Actions খুলতে, আর Pull members up to base type choose করো। কোন member উপরে যাবে আর কোন base type-এ সেটা dialog-এ pick করা যায়। "Pull member(s) up to new base class"-ও আছে — superclass না থাকলে সেটা তৈরি করে দেয়।
- ReSharper: conflict checking সহ Refactor → Pull Members Up — moved member base-এ থাকা কিছুর সাথে clash করলে warn করে।
- Eclipse (Java): Refactor → Pull Up... একই member-selection dialog দেয়।
একটা সৎ সতর্কতা: tool mechanically declaration সরায়, কিন্তু অর্থ বিচার করতে পারে না। IDE খুশি হয়ে schoolAddress ("স্কুলের postal address") একই নামের কিন্তু ভিন্ন অর্থের field-এর সাথে merge করবে। আমাদের নিরাপদ recipe-এর ১-৩ ধাপ — অর্থ নিশ্চিত করা, নাম একীভূত করা, drifted value ঠিক করা — এগুলো মানুষেরই কাজ। Tool হল জনাব সালামের মই আর পিন। কোন নোটিশ কোন বোর্ডে যাবে সেটা decide করা এখনও ড. ফাতেমা বেগমের কাজ।
সুবিধা আর ঝুঁকি
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
| N-এর বদলে একটাই declaration — field-এর নাম, type, বা default পরিবর্তন এক লাইন স্পর্শ করে | field-গুলো শুধু একইরকম দেখাত কিন্তু ভিন্ন অর্থ বহন করত? merge করলে মিথ্যা abstraction তৈরি হয় |
| কপি আর আলাদা হতে পারে না (৭C-তে stale পোস্টাল কোড আর নেই) | private subclass field সাধারণত protected হয়ে যায়, visibility বাড়িয়ে দেয় |
| superclass shared behaviour host করার জন্য দরকারি data পায় — Pull Up Method unlock করে | শুধু কিছু subclass field ব্যবহার করলে, pull up করলে Refused Bequest smell তৈরি হয় |
| hierarchy সৎভাবে পড়া যায়: shared জিনিস উপরে, special জিনিস নিচে | subclass-গুলো parent-এর data layout-এর সাথে সামান্য বেশি coupled হয় (accessor দিয়ে নরম করো) |
| drifted কপিতে লুকানো bug merge-এর সময় আবিষ্কার হয় | অন্যথায় প্রায় কোনো ঝুঁকি নেই — এটা catalog-এর সবচেয়ে কম-ঝুঁকির refactoring-গুলোর একটা |
একটু ভাবো direction প্রশ্নটা নিয়ে। Pull Up Field আর Push Down Field exact inverse। দুটোই আছে কারণ দুটো ভুলই বাস্তব codebase-এ ঘটে। compass একটা ছোট table-এ:
| কে field ব্যবহার করে? | কোথায় থাকা উচিত | কোন refactoring করতে হবে |
|---|---|---|
| সব (বা প্রায় সব) subclass | superclass — অফিস বোর্ড | Pull Up Field |
| ঠিক একটা subclass | সেই subclass — তার নিজের section বোর্ড | Push Down Field |
| subclass-গুলোর একটা natural group | সেই group-এর জন্য একটা intermediate parent | Extract Subclass / intermediate class |
| আর কেউ না | কোথাও না — মুছে দাও | Remove Dead Code |
দুটো refactoring প্রতিদ্বন্দ্বী নয়। এগুলো বিপরীত দিকে প্রয়োগ করা একই পরিপাটি করার প্রবণতা। একটা সুস্থ hierarchy তার জীবনকালে দুটোই ব্যবহার করে — যেভাবে স্কুল কখনো একটা নোটিশ অফিস বোর্ডে তোলে, কখনো একটা section-এর বোর্ডে নামায়।
একটু গভীরে: compass-এর নিচে একটা শান্ত principle আছে — Liskov Substitution Principle (LSP)। superclass-এ একটা field হল একটা structural promise: যেকোনো Section object, যে concrete subclass-ই হোক, এই data অর্থপূর্ণভাবে আছে। parent type-এর বিপরীতে লেখা code সেই promise-এর উপর নির্ভর করে। এমন একটা field pull up করো যেটা কিছু children honor করে না, আর parent-typed code এমন object handle করা শুরু করে যার inherited state অর্থহীন — substitutability technically intact কিন্তু semantically ভাঙা। তাই "প্রতিটা subclass কি সত্যিকার অর্থে এই field শেয়ার করে?" প্রশ্নটা আসলে "আমি কি parent-এর contract শক্তিশালী করতে পারব কোনো child সম্পর্কে মিথ্যা না বলে?"
কোন smell ঠিক করে?
| Smell | Pull Up Field কীভাবে সাহায্য করে |
|---|---|
| Duplicate Code | প্রাথমিক সমাধান — N অভিন্ন field declaration একটায় সংকুচিত হয় |
| Shotgun Surgery | shared fact-এ পরিবর্তন এখন প্রতিটা subclass-এর বদলে এক class স্পর্শ করে |
| Refused Bequest | পরোক্ষ: কখন pull up না করতে হবে জানা (only-some-use-it field) এই smell তৈরি করা রোধ করে |
| Blocked generalization | নামকরণ করা smell নয়, কিন্তু প্রাত্যহিক পরিস্থিতি যেখানে duplicate method-গুলো উপরে যেতে পারে না কারণ তাদের data নিচে আটকে আছে — Pull Up Field Pull Up Method unblock করে |
দ্রুত revision box
+--------------------------------------------------------------+
| PULL UP FIELD — REVISION |
+--------------------------------------------------------------+
| Story : School address copied on 3 section boards; |
| 7B's copy went stale -> letters bounced. |
| Fix: write it ONCE on the main office board. |
| |
| Move : Field duplicated in subclasses ---> superclass |
| |
| Do it when : EVERY subclass shares the same-meaning field |
| Don't when : only SOME subclasses use it (-> Refused |
| Bequest; consider Push Down Field instead) |
| |
| Safe steps : same meaning? -> same name -> fix drift -> |
| declare in parent (protected) -> delete from |
| subclasses ONE AT A TIME, testing each time |
| |
| Inverse : Push Down Field (shared->up, special->down) |
| Unlocks : Pull Up Method, Pull Up Constructor Body |
| Cures : Duplicate Code (in state declarations) |
+--------------------------------------------------------------+অনুশীলন
এখন তুমিই প্রধান শিক্ষিকা। এখানে স্কুলের library software-এর একটা ছোট hierarchy:
abstract class LibraryMember {}
class StudentMember extends LibraryMember {
protected libraryName = "Sunrise School Library";
protected maxBooks = 2;
protected finePerDay = 1; // rupees
}
class TeacherMember extends LibraryMember {
protected libName = "Sunrise School Library"; // different name!
protected maxBooks = 6;
protected finePerDay = 1;
}
class GuestMember extends LibraryMember {
protected libraryName = "Sunrise Public School Library"; // drifted!
protected maxBooks = 1;
protected finePerDay = 5; // guests pay more
}তোমার কাজ:
- Decide করো কোন field-গুলো pull up করা উচিত আর কোনগুলো নিচে থাকতে হবে। (Hint:
maxBooksmember type অনুযায়ী ইচ্ছাকৃতভাবে আলাদা — এটা কি সত্যিকার shared state নাকি per-class policy? আরfinePerDayদুটো class-এ মেলে কিন্তু তৃতীয়টায় নয় — direction compass কী বলে?) TeacherMember-এlibName-এর কোনো move-এর আগে কোন preparation step দরকার?GuestMember-এlibraryNamedrift করেছে। এটা কি bug নাকি সত্যিকার পার্থক্য? merge করার আগে তুমি কী check করবে লিখে রাখো। (Sunrise School-এ কাকে জিজ্ঞেস করবে? সম্ভবত জনাব সালামকে — তিনি official records রাখেন।)- নিরাপদ ক্রমে refactoring করো: rename, drift ঠিক করো,
LibraryMember-এ declare করো, তারপর একে একে একটা subclass থেকে মুছো, প্রতিটা ধাপের মাঝে "test চালাও"। - Bonus: pull-up-এর পরে superclass-এ একটা
membershipCardLabel()method লিখো যেটা shared field ব্যবহার করে — আর লক্ষ্য করো যে গতকাল এই method-এর থাকার কোনো জায়গাই ছিল না। তুমি এইমাত্র অনুভব করলে কেন Pull Up Field-কে enabling refactoring বলা হয়।
যখন তুমি একজন বন্ধুকে কেন libraryName উপরে যায় কিন্তু maxBooks নিচে থাকে সেটা ব্যাখ্যা করতে পারবে, তুমি Pull Up Field বুঝে গেছ। আর তুমি তখন এর বড় ভাই, Pull Up Method-এর জন্য তৈরি।
সচরাচর জিজ্ঞাসা
- Pull Up Field আসলে কী করে?
- যখন দুই বা তার বেশি subclass-এ একই field বারবার declare হয় — একই তথ্য, একই অর্থ — তখন Pull Up Field সেই কপিগুলো মুছে দেয় আর field-টা একবারই common superclass-এ declare করে। সব subclass তখন সেই একটা shared কপি inherit করে। ফলে পড়ার জায়গা একটা, পরিবর্তনের জায়গাও একটা।
- duplicate field-গুলোর নাম আলাদা আলাদা হলে কী করব?
- আগে rename করো। প্রতিটা কপিকে একই, সবচেয়ে স্পষ্ট নাম দাও। সব reference আপডেট করো, প্রতিটা rename-এর পর test চালাও। শুধু যখন field-গুলো স্পষ্টভাবে একই অর্থ বহন করে আর একই নাম নিয়েছে, তখনই superclass-এ নিয়ে যাও।
- Pull Up করার পর কেন private subclass field সাধারণত protected হয়ে যায়?
- superclass-এ private field থাকলে subclass সেটা দেখতেই পাবে না, ফলে তাদের কোড ভেঙে যাবে। protected করলে subclass সেটা ব্যবহার করতে পারে। এটা visibility একটু বাড়িয়ে দেয়, তাই অনেক team সরাসরি access-এর বদলে protected getter বা property ব্যবহার করে।
- পাঁচটা subclass-এর মধ্যে মাত্র দুটো একটা field ব্যবহার করলে কি pull up করা উচিত?
- না। Pull up করলে বাকি তিনটা subclass এমন তথ্য বহন করতে বাধ্য হবে যা তারা কখনো ব্যবহার করে না — এটাই Refused Bequest smell। শুধু সেই field-গুলোই pull up করো যেগুলো সত্যিকার অর্থে প্রতিটা subclass শেয়ার করে। যখন parent field শুধু কিছু children ব্যবহার করে, তখন বিপরীত refactoring Push Down Field প্রয়োগ করো।
- Pull Up Field আর Pull Up Method-এর মধ্যে সম্পর্ক কী?
- Pull Up Field সাধারণত প্রথম ধাপ — এটা পরেরটার দরজা খুলে দেয়। যে data-কে method স্পর্শ করে সেটা subclass-এ থাকতে থাকলে method superclass-এ যেতে পারে না। shared field টা parent-এ চলে আসলে, সেটা ব্যবহারকারী duplicate method-গুলো Pull Up Method দিয়ে অনুসরণ করতে পারে।
আরো দেখো
সম্পর্কিত পাঠ
Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা
বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।
Refused Bequest: যে ছেলে মিষ্টির দোকানের রেসিপি নিতে চায়নি
Refused Bequest কোড স্মেল শেখো একটা পারিবারিক মিষ্টির দোকানের গল্পের মাধ্যমে — TypeScript ও C#-এ Liskov লঙ্ঘন আর delegation দিয়ে সমাধান ধাপে ধাপে।
Pull Up Method: পুরো স্কুলের জন্য একটাই নির্দেশিকা
Pull Up Method refactoring শেখো স্কুলের ছুটির আবেদনের গল্পের মাধ্যমে — subclass-এ duplicate হয়ে যাওয়া method-গুলো superclass-এ তুলে আনো, TypeScript আর C#-এ safe steps সহ, IDE dialog আর কখন Form Template Method বেছে নেবে সেটাসহ।
Pull Up Constructor Body: একটাই সকালের রুটিন, তারপর নিজের কাজ
Pull Up Constructor Body refactoring শেখো school-এর সকালের রুটিনের গল্প দিয়ে — সব subclass constructor-এর শুরুতে যে duplicate initialization আছে, সেটা superclass constructor-এ তুলে নাও আর super/base দিয়ে call করো।