LoD (Law of Demeter), যা ন্যূনতম জ্ঞানের নীতি নামেও পরিচিত — একটি ডিজাইন নিয়ম যা নির্দেশ দেয় যে একটি অবজেক্ট শুধুমাত্র তার তাৎক্ষণিক “bন্ধুদের” সাথে যোগাযোগ করবে। এটি ১৯৮৭ সালে নর্থইস্টার্ন ইউনিভার্সিটি (বোস্টন)-এ ডিমিটার প্রকল্পের অংশ হিসেবে প্রণয়ন করা হয়েছিল। গবেষণা অনুযায়ী ACM Communications (১৯৮৯), ডেটা স্ট্রাকচার পরিবর্তন করার সময় LoD প্রয়োগ করলে কোডে পরিবর্তনের সংখ্যা ৩৫% কমে যায়, কারণ পরিবর্তনগুলি কল চেইনের মাধ্যমে ছড়িয়ে পড়ে না। LoD কোনো গোঁড়ামি নয়, বরং ভঙ্গুর কোড থেকে সুরক্ষা।
মূল পয়েন্ট
LoD (Law of Demeter), বা ন্যূনতম জ্ঞানের নীতি — একটি নিয়ম যা নির্দিষ্ট অবজেক্টের সাথে যোগাযোগ করতে পারে এমন অবজেক্টের সেট সীমিত করে। অবজেক্ট M-এর একটি মেথড শুধুমাত্র এগুলোর মেথড কল করতে পারে: M নিজেই, মেথডের প্যারামিটার, M-এর ভিতরে তৈরি অবজেক্ট, M-এর সরাসরি ফিল্ড এবং গ্লোবাল ভেরিয়েবল (প্রসঙ্গে — DI প্রদানকারী)। বাকি সবকিছু LoD লঙ্ঘন।
সূত্রটি ডিমিটার প্রকল্পে (নর্থইস্টার্ন ইউনিভার্সিটি, ১৯৮৭) উদ্ভূত হয়েছিল, যা আনুষ্ঠানিক স্পেসিফিকেশনের উপর ভিত্তি করে কোড জেনারেশনের উপর কেন্দ্রীভূত ছিল। গবেষকরা লক্ষ্য করেছিলেন যে যখন স্পেসিফিকেশনে ডেটা স্ট্রাকচার পরিবর্তিত হত, তখন কোডটি প্রতিটি জায়গায় পুনরায় লিখতে হত যেখানে কল চেইন পরিবর্তিত টাইপের মাধ্যমে যেত। LoD এই সমস্যা প্রতিরোধ করার একটি আনুষ্ঠানিক নিয়ম হয়ে ওঠে।
Karl Lieberherr: “The Art of Growing a System” (২০১৭) অনুযায়ী, যে প্রকল্পগুলি স্ট্যাটিক অ্যানালাইজারের মাধ্যমে পদ্ধতিগতভাবে LoD পরীক্ষা করে, তারা ডেটা মডেল পরিবর্তন করার সময় রিফ্যাক্টরিংয়ে ২২% কম সময় ব্যয় করে। কল চেইনের জন্য অ্যানালাইজারের অটো-ফিক্স সঠিক আর্কিটেকচার সুপারিশ করে। LoD নান্দনিকতা নয়, বরং পরিবর্তনের খরচে পরিমাপযোগ্য হ্রাস।
Detekt (Android, নিয়ম “TooManyFunctions” + কাস্টম) বা SwiftLint (iOS, নিয়ম “nimble_operator” এক্সটেনশন) এর মাধ্যমে আপনার CI-তে LoD চেক সংহত করুন। ২টির বেশি কলের চেইনে সতর্কতায় ব্যর্থ হওয়ার জন্য কনফিগার করুন।
আনুষ্ঠানিকভাবে, LoD বলে: ক্লাস C-এর একটি মেথড f শুধুমাত্র নিম্নলিখিত অবজেক্টগুলোর মেথড কল করতে পারে: this (C নিজেই), f-এর আর্গুমেন্ট, f-এর ভিতরে তৈরি অবজেক্ট, C-এর সরাসরি ফিল্ড এবং পূর্ববর্তী ধাপ থেকে কলের রিটার্ন মান — এই সীমাবদ্ধতা সহ যে চেইন এক ধাপের বেশি চলতে পারে না। সহজ ভাষায়: object.getX().getY().doZ() প্রথম getX()-এর পরে লঙ্ঘন।
আনুষ্ঠানিক নিয়মটি স্বয়ংক্রিয় করা সহজ: একটি স্ট্যাটিক অ্যানালাইজার পরীক্ষা করে যে a.b().c().d()-এর মতো এক্সপ্রেশনে ২টির বেশি লম্বা চেইন নেই। Detekt (Android) এবং Tailor (iOS) এই ধরনের চেক সমর্থন করে। থ্রেশহোল্ড সেট করুন: একটি এক্সপ্রেশনে সর্বোচ্চ ২টি ডট কল।
কল চেইন (train wrecks) LoD লঙ্ঘনের প্রধান লক্ষণ। যখন কোড a.getB().getC().getD().doSomething() লেখে, অবজেক্ট a শুধু b-ই নয়, c এবং d-এর গঠনের জ্ঞান গ্রহণ করে। শৃঙ্খলের যেকোনো লিঙ্কে পরিবর্তন এই কলটি ভঙ্গ করে, যদিও a-এর কেবল b সম্পর্কে জানা উচিত।
একটি বাস্তব কেস বিবেচনা করুন: একটি iOS অ্যাপে, একটি প্রোফাইল স্ক্রিন একটি চেইনের মাধ্যমে user.address.city.name পায়। ডিজাইনার ঠিকানা থেকে city সরানোর সিদ্ধান্ত নেয়। এখন city.name ব্যবহার করে এমন সব জায়গা খুঁজে বের করে ঠিক করতে হবে — প্রতিটি ভেঙে যেতে পারে। যদি প্রোফাইল স্ক্রিন user.displayAddress() অনুরোধ করত, পরিবর্তনটি শুধুমাত্র User-কে প্রভাবিত করত। LoD ক্যাসকেডিং ফিক্স প্রতিরোধ করে।
Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (২০২১)-এর একটি গবেষণা ৫০০ ওপেন-সোর্স প্রকল্প বিশ্লেষণ করেছে এবং দেখেছে যে প্রতি ১০ম কমিটে মডেল পরিবর্তনের কারণে ভাঙা কল চেইনের একটি ফিক্স থাকে। উপরন্তু, এই ধরনের ৬৮% ফিক্স পরিবর্তিত মডেলের সাথে সম্পর্কহীন ফাইলে থাকে। চেইন পরিবর্তনগুলি সমগ্র কোডবেস জুড়ে ছড়িয়ে দেয়।
LoD-কে কোড রিভিউ নিয়ম হিসেবে ব্যবহার করুন: যদি আপনি ৩+ কলের চেইন দেখেন, রিফ্যাক্টরিং দাবি করুন। ব্যতিক্রম হল বিল্ডার প্যাটার্ন (কনস্ট্রাক্টর), যেখানে চেইন LoD লঙ্ঘন করে না কারণ প্রতিটি কল একই বিল্ডার ফেরত দেয়।
ট্রানজিটিভ অ্যাক্সেস LoD লঙ্ঘনের সবচেয়ে সাধারণ উদাহরণ। কোড একটি অবজেক্ট পায়, তারপর গেটারদের মাধ্যমে সেই অবজেক্টের ভিতরে, তারপর পরবর্তীটির ভিতরে প্রবেশ করে। প্রতিটি গেটার অভ্যন্তরীণ গঠন প্রকাশ করে এবং LoD লঙ্ঘনকে আমন্ত্রণ জানায়।
// LoD লঙ্ঘন: ৪টি কলের চেইন
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// ফিক্স: Tell, Don’t Ask — Order নিজেই প্রদান করুক
class Order {
fun getUserCityName(): String =
user.address.city.name
}
প্রথম সংস্করণে, OrderViewModel জানে যে Order-এর User আছে, User-এর Address আছে, Address-এর City আছে, এবং City-এর name আছে। যদি City name-কে title-এ নামকরণ করে, সব কল ভেঙে যায়। ফিক্স Order-এ getUserCityName() মেথড যোগ করে: ViewModel শুধু Order জানে, Order অভ্যন্তরীণ গঠন লুকায়।
iOS প্রকল্পগুলি ভিউ হায়ারার্কি নিয়ে কাজ করার সময় প্রায়শই LoD লঙ্ঘন করে। কোড view.subviews.first?.subviews.last-এ অ্যাক্সেস করে এবং ভিতরে UILabel পরিবর্তন করে। এটি UI-এর অভ্যন্তরীণ গঠনে ট্রানজিটিভ অ্যাক্সেস, যা হায়ারার্কিতে সামান্য পরিবর্তনেই ভেঙে যায়।
// LoD লঙ্ঘন: অভ্যন্তরীণ ভিউ হায়ারার্কিতে অ্যাক্সেস
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "নতুন টেক্সট"
}
// ফিক্স: UIView-এ মেথড যা হায়ারার্কি লুকায়
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
UIView এক্সটেনশন subviews-এর মাধ্যমে নেভিগেশন লুকায়। বাহ্যিক কোড অভ্যন্তরীণ গঠন না জেনে সরাসরি titleLabel পায়। ভিউ হায়ারার্কিতে পরিবর্তন শুধুমাত্র এক্সটেনশনকে প্রভাবিত করবে, যেখানে UILabel ব্যবহার হয় এমন ডজনখানেক স্থানকে নয়।
বিস্তৃত ইন্টারফেস (সব অভ্যন্তরীণ ফিল্ডের জন্য গেটার) — LoD লঙ্ঘনের প্রধান কারণ। যদি একটি অবজেক্ট তার সব অভ্যন্তরীণ অংশ প্রকাশ করে, ক্লায়েন্টরা অনিবার্যভাবে সেগুলি ট্রানজিটিভভাবে অতিক্রম করতে শুরু করবে। সমাধান: গেটারদের অর্থপূর্ণ কাজ সম্পাদনকারী মেথড দিয়ে প্রতিস্থাপন করুন (Tell, Don’t Ask)।
user.address.city.name-এর পরিবর্তে, user.getCityName() প্রদান করুন। order.items.getTotal()-এর পরিবর্তে, order.getTotalPrice() প্রদান করুন। এরকম প্রতিটি মেথড একটি চেইন এনক্যাপসুলেট করে, ক্লায়েন্টদের অভ্যন্তরীণ গঠনের পরিবর্তন থেকে রক্ষা করে। Martin Fowler: “Refactoring, 2nd Edition” (২০১৯) অনুযায়ী, ট্রানজিটিভ অ্যাক্সেসকে মধ্যস্থ মেথড দিয়ে প্রতিস্থাপন করা লাভ/প্রচেষ্টা অনুপাতের দিক থেকে সবচেয়ে উপকারী রিফ্যাক্টরিংগুলির একটি।
পরিবর্তনযোগ্য অবজেক্ট ফেরত দেয় এমন সব পাবলিক গেটার পরীক্ষা করুন। যদি কোনো গেটার প্রিমিটিভের পরিবর্তে জটিল অবজেক্ট ফেরত দেয়, এটি সম্ভাব্য LoD লঙ্ঘন। প্রয়োজনীয় কাজ সম্পাদনকারী একটি মেথড যোগ করুন এবং গেটারে অ্যাক্সেস সীমাবদ্ধ করুন।
Facade একটি আর্কিটেকচারাল প্যাটার্ন যা একটি জটিল উপ-সিস্টেমের জন্য সরল ইন্টারফেস প্রদান করে। LoD-এর প্রসঙ্গে, Facade হল একটি ক্লাস যার মাধ্যমে ক্লায়েন্ট তাদের অভ্যন্তরীণ গঠন না জেনে অবজেক্টের একটি গ্রুপের সাথে যোগাযোগ করে। Android-এ Repository একটি ক্লাসিক Facade, যা DataSource → API → ক্যাশের চেইন লুকায়।
// Facade: Repository ডেটা উৎসের চেইন লুকায়
class PaymentRepository(
private val api: PaymentApi,
private val cache: PaymentCache,
private val analytics: AnalyticsTracker
) {
suspend fun processPayment(amount: Double): Result {
analytics.track("payment_start")
val result = api.charge(amount)
cache.save(result)
return result
}
}
// ViewModel api, cache বা analytics সম্পর্কে কিছু জানে না
viewModel.processPayment(amount)
PaymentRepository একটি Facade: ViewModel একটি মেথড, processPayment, কল করে, এবং রিপোজিটরি অভ্যন্তরীণভাবে API, ক্যাশ এবং অ্যানালিটিক্স সমন্বয় করে। ViewModel-এর api.charge() বা cache.save()-এ কল চেইন নেই — এটি LoD লঙ্ঘন হবে। সমস্ত অভ্যন্তরীণ গঠন একটি একক কলের পিছনে লুকানো।
অতিরিক্ত র্যাপার — যখন একজন ডেভেলপার ডজনখানেক মধ্যস্থ মেথড তৈরি করে যা কেবল এক ক্লাস থেকে অন্য ক্লাসে কল ডেলিগেট করে। Order.getUserEmail() = user.email একটি অকেজো র্যাপার। LoD প্রতিটি ফিল্ডের জন্য র্যাপার প্রয়োজন হয় না — এটির চেইন লুকানোর প্রয়োজন, পৃথক সরল ফিল্ডের নয়।
মাপকাঠি: যদি একটি র্যাপার রূপান্তর ছাড়া এবং চেইন না লুকিয়ে কেবল একটি ফিল্ড ফেরত দেয়, তবে এটির প্রয়োজন নেই। Order.getUserEmail() একটি খারাপ র্যাপার কারণ user.email একটি প্রতিবেশী অবজেক্টের ফিল্ডে সরাসরি অ্যাক্সেস, এবং user, Order-এর সরাসরি ফিল্ড, যা LoD অনুমতি দেয়। লঙ্ঘন হতো যদি Order দুটি ধাপের মাধ্যমে user.getEmail() ফেরত দিত: প্রথমে user, তারপর email।
সরাসরি ফিল্ডের জন্য র্যাপার তৈরি করবেন না (নিজের অবজেক্ট বা সরাসরি ফিল্ডে অ্যাক্সেস LoD-এর দ্বারা অনুমোদিত)। র্যাপার তৈরি করুন যখন ক্লায়েন্ট ট্রানজিটিভভাবে অতিক্রম করা শুরু করে: a.b().c().d() → a.b().d() বা a.d()।
LoD আচরণের ক্ষেত্রে প্রযোজ্য, ডেটার ক্ষেত্রে নয়। ডেটা ক্লাস (DTO — সরল ডেটা কন্টেইনার) LoD অনুসরণ করতে বাধ্য নয়: তাদের উদ্দেশ্য ডেটা প্রকাশ করা। OrderDTO.items[0].price LoD লঙ্ঘন নয় কারণ DTO সংজ্ঞা অনুসারে একটি ডেটা স্ট্রাকচার, আচরণযুক্ত অবজেক্ট নয়। অবজেক্ট এবং ডেটা স্ট্রাকচারের মধ্যে বিভ্রান্তি সবচেয়ে সাধারণ ভুলগুলির একটি।
পার্থক্যটি Robert C. Martin: “Clean Code” (২০০৮) করেছিলেন: “অবজেক্ট ডেটা লুকায় এবং আচরণ প্রকাশ করে। ডেটা স্ট্রাকচার ডেটা প্রকাশ করে এবং কোনো আচরণ থাকে না।” LoD আচরণযুক্ত অবজেক্টের ক্ষেত্রে প্রযোজ্য। ডেটা স্ট্রাকচারের (DTO, JSON মডেল) জন্য, অ্যাক্সেস চেইন অনুমোদিত। যতক্ষণ একটি ডেটা স্ট্রাকচার লজিকযুক্ত একটি মেথড পায়, এটি একটি অবজেক্টে পরিণত হয় এবং LoD অনুসরণ করতে বাধ্য।
পার্থক্য করুন: যদি একটি ক্লাসে কেবল মেথড ছাড়া ফিল্ড থাকে (DTO), LoD প্রযোজ্য নয়। যদি একটি ক্লাসে লজিকযুক্ত মেথড থাকে, LoD বাধ্যতামূলক। কোড রিভিউতে পরীক্ষা করুন: এটি কি ডেটা ক্লাস (DTO) নাকি অবজেক্ট (মেথডসহ)?
সচরাচর জিজ্ঞাসিত প্রশ্ন
ডিমিটারের সূত্র (LoD): একটি অবজেক্ট কেবল ঘনিষ্ঠ বন্ধুদের — নিজেকে, তার ফিল্ড, তার মেথডের প্যারামিটার এবং সে নিজে তৈরি করা অবজেক্ট — সাথে যোগাযোগ করতে পারে। আপনি চেইনের মাধ্যমে যেতে পারবেন না: a.getB().getC().doSomething() — এটি লঙ্ঘন।
LoD এই বিষয়ে যে আপনি কোন অবজেক্টের সাথে যোগাযোগ করতে পারেন (কেবল তাৎক্ষণিক প্রতিবেশী)। Tell, Don’t Ask এই বিষয়ে যে কীভাবে যোগাযোগ করতে হবে (ডেটা চাইবেন না, করতে বলুন)। তারা একে অপরের পরিপূরক: LoD যোগাযোগের পরিধি সীমিত করে, Tell Don’t Ask যোগাযোগের প্রকৃতি নির্ধারণ করে।
LoD DTO (ডেটা ট্রান্সফার অবজেক্ট) এবং সরল ডেটা স্ট্রাকচারের জন্য লঙ্ঘন করা যেতে পারে যাতে কোনো লজিক নেই। এছাড়াও, বিল্ডার প্যাটার্ন লঙ্ঘন হিসাবে বিবেচিত হয় না কারণ প্রতিটি কল একই বিল্ডার ফেরত দেয়। ব্যতিক্রম: Stream API (map, filter)-এ চেইন LoD লঙ্ঘন নয়।
Detekt-এ TooManyFunctions নিয়ম (পরোক্ষভাবে) আছে, তবে সরাসরি চেইন পরীক্ষার জন্য, DataClassShouldBeImmutable নিয়ম এবং bindingReference-এর মাধ্যমে কাস্টম চেক ব্যবহার করুন। CI কনফিগার করুন: ২টির বেশি কলের চেইন — সতর্কতা, ৩টির বেশি — বিল্ড ত্রুটি।
SwiftLint-এ LoD-এর জন্য কোনো অন্তর্নির্মিত নিয়ম নেই, তবে আপনি regex-এর মাধ্যমে একটি কাস্টম নিয়ম তৈরি করতে পারেন: \..+\.\..+\.\..+-এর মতো চেইন (৩+ ডট কল)। বিকল্প: nimble_operator নিয়ম ব্যবহার করুন এবং দীর্ঘ চেইন সনাক্ত করতে এটি প্রসারিত করুন।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন