LoD در توسعه موبایل: چیست، قانون دمیتر و چگونه از آن استفاده کنیم

نویسنده: IT Sectr منتشر شده: 2026-05-13 زمان مطالعه: 9 دقیقه

LoD (Law of Demeter)، همچنین به عنوان اصل کمترین آگاهی شناخته می‌شود — قاعده طراحی که به شی دستور می‌دهد فقط با «دوستان» مستقیم تعامل کند. در سال ۱۹۸۷ در دانشگاه نورث‌ایسترن (بوستون) در چارچوب پروژه Demeter فرموله شد. طبق تحقیق ACM Communications (1989)، استفاده از LoD تعداد تغییرات در کد هنگام تغییر ساختار داده را ۳۵٪ کاهش می‌دهد، زیرا تغییرات در طول زنجیره‌های فراخوانی منتشر نمی‌شوند. LoD — جزم نیست، بلکه محافظی در برابر کد شکننده است.

نکات اصلی

  • LoD (Law of Demeter) — اصل: شی باید فقط به همسایگان مستقیم خود دسترسی داشته باشد، نه به درون آن‌ها.
  • زنجیره فراخوانی به شکل a.b().c().d() — نشانه اصلی نقض LoD: شی a کل ساختار b، c و d را می‌شناسد.
  • رابط گسترده کلاس که اشیای داخلی را از طریق getterها آشکار می‌کند، باعث نقض LoD می‌شود.
  • Tell, Don't Ask — اصل نزدیک: برای اجرای منطق از شی داده نخواه، بلکه از شی بخواه خودش این کار را انجام دهد.
  • facade (Facade) — الگوی معماری که نقض LoD را از طریق یک رابط واحد به زیرسیستم برطرف می‌کند.

LoD (قانون دمیتر) چیست؟

LoD (Law of Demeter) یا اصل کمترین آگاهی — قاعده‌ای که دایره اشیایی را که یک شی خاص می‌تواند با آن‌ها تعامل کند محدود می‌کند. متد شی M می‌تواند فقط متدهای موارد زیر را فراخوانی کند: خود M، پارامترهای متد، اشیای ایجاد شده در داخل M، فیلدهای مستقیم M و متغیرهای سراسری (در زمینه — ارائه‌دهندگان DI). بقیه موارد — نقض LoD است.

قانون در پروژه Demeter (دانشگاه نورث‌ایسترن، ۱۹۸۷) که به تولید کد بر اساس مشخصات رسمی می‌پرداخت، پدید آمد. محققان متوجه شدند: وقتی ساختار داده در مشخصات تغییر می‌کرد، باید کد را در همه جاهایی که زنجیره دسترسی از نوع تغییر یافته عبور می‌کرد، بازنویسی می‌کردند. LoD به یک قاعده رسمی برای جلوگیری از این مشکل تبدیل شد.

طبق Karl Lieberherr: «The Art of Growing a System» (2017)، پروژه‌هایی که به طور سیستماتیک LoD را از طریق تحلیلگر ایستا بررسی می‌کنند، ۲۲٪ زمان کمتری برای بازسازی هنگام تغییر مدل‌های داده صرف می‌کنند. تحلیلگر auto-fix زنجیره‌های فراخوانی معماری صحیح را پیشنهاد می‌کند. LoD — زیبایی‌شناسی نیست، بلکه کاهش قابل اندازه‌گیری هزینه تغییرات است.

بررسی LoD را در CI از طریق Detekt (Android، قانون «TooManyFunctions» + سفارشی) یا SwiftLint (iOS، الحاقیه «nimble_operator») پیاده‌سازی کنید. شکست را بر روی هشدارها با زنجیره‌های طولانی‌تر از ۲ فراخوانی تنظیم کنید.

تعریف رسمی LoD

به طور رسمی LoD می‌گوید: متد f از کلاس C می‌تواند فقط متدهای اشیای زیر را فراخوانی کند: this (خود C)، آرگومان‌های f، اشیای ایجاد شده در داخل f، فیلدهای مستقیم C و مقادیر بازگشتی فراخوانی‌ها در مراحل قبلی — با محدودیت اینکه زنجیره بیش از یک مرحله ادامه نیابد. ساده‌تر: object.getX().getY().doZ() — نقض بعد از اولین getX().

قاعده رسمی به راحتی خودکار می‌شود: تحلیلگر ایستا بررسی می‌کند که در عبارت a.b().c().d() زنجیره‌های طولانی‌تر از ۲ وجود نداشته باشد. Detekt (Android) و Tailor (iOS) چنین بررسی‌هایی را پشتیبانی می‌کنند. آستانه را تنظیم کنید: حداکثر ۲ فراخوانی از طریق نقطه در یک عبارت.

چرا زنجیره‌های فراخوانی خطرناک هستند؟

زنجیره‌های فراخوانی (chain calls, 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» (2021) ۵۰۰ پروژه منبع‌باز را تحلیل کرد و نشان داد: در هر دهمین commit اصلاح زنجیره فراخوانی شکسته شده به دلیل تغییر مدل رخ می‌دهد. در عین حال ۶۸٪ از این اصلاحات — در فایل‌های غیرمرتبط با مدل تغییر یافته است. زنجیره‌ها تغییرات را در سراسر پایگاه کد منتشر می‌کنند.

از LoD به عنوان قانون بازبینی کد استفاده کنید: اگر زنجیره ۳+ فراخوانی می‌بینید — بازسازی را درخواست کنید. استثنا — Builder (سازنده)، که در آن زنجیره LoD را نقض نمی‌کند، زیرا هر فراخوانی همان builder را برمی‌گرداند.

نقض‌های LoD: مثال‌های عملی

نقض کلاسیک: دسترسی گذری به فیلدها

دسترسی گذری — رایج‌ترین مثال نقض LoD. کد شی را دریافت می‌کند، سپس از طریق getterها به داخل این شی نفوذ می‌کند، سپس به داخل شی بعدی. هر getter ساختار داخلی را آشکار می‌کند و به نقض LoD دعوت می‌کند.

kotlin
// نقض 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 تغییر دهد — همه فراخوانی‌ها می‌شکنند. رفع متد getUserCityName() را به Order اضافه می‌کند: ViewModel فقط Order را می‌شناسد، Order ساختار داخلی را پنهان می‌کند.

نقض LoD در iOS: دسترسی به subviews

پروژه‌های iOS اغلب هنگام کار با سلسله‌مراتب view LoD را نقض می‌کنند. کد به view.subviews.first?.subviews.last دسترسی پیدا می‌کند و UILabel داخل را تغییر می‌دهد. این — دسترسی گذری به ساختار داخلی UI است که با کوچکترین تغییر سلسله‌مراتب می‌شکند.

swift
// نقض LoD: دسترسی به سلسله‌مراتب داخلی view
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 را مستقیماً دریافت می‌کند، بدون دانستن ساختار داخلی. تغییر سلسله‌مراتب view فقط افزونه را تحت تأثیر قرار می‌دهد، نه ده‌ها جایی که از این UILabel استفاده می‌شود.

چگونه نقض LoD را در Android و iOS برطرف کنیم؟

رابط گسترده → رابط محدود

رابط گسترده (getter برای تمام فیلدهای داخلی) — دلیل اصلی نقض LoD. اگر شی تمام درون خود را آشکار کند، مشتریان ناگزیر شروع به حرکت گذری روی آن‌ها می‌کنند. راه‌حل: getterها را با متدهایی که اقدامات معنادار انجام می‌دهند جایگزین کنید (Tell, Don't Ask).

به جای user.address.city.name، user.getCityName() را ارائه دهید. به جای order.items.getTotal()، order.getTotalPrice() را ارائه دهید. هر چنین متدی — محصورسازی زنجیره‌ای است که مشتریان را از تغییرات ساختار داخلی محافظت می‌کند. طبق Martin Fowler: «Refactoring, 2nd Edition» (2019)، جایگزینی دسترسی گذری با متد واسطه — یکی از مفیدترین بازسازی‌ها از نظر نسبت سود به تلاش است.

تمامی getterهای عمومی که اشیای تغییرپذیر برمی‌گردانند را بررسی کنید. اگر getter یک شی پیچیده برمی‌گرداند نه یک نوع اولیه — این یک نقض بالقوه LoD است. متدی را اضافه کنید که اقدام مورد نیاز را انجام دهد و دسترسی به getter را محدود کنید.

facade برای زیرسیستم‌های پیچیده

facade (Facade) — الگوی معماری که یک رابط ساده به یک زیرسیستم پیچیده ارائه می‌دهد. در زمینه LoD، Facade کلاسی است که مشتری از طریق آن با گروهی از اشیا ارتباط برقرار می‌کند بدون اینکه ساختار داخلی آن‌ها را بداند. Repository در Android — Facade کلاسیک که زنجیره DataSource → API → cache را پنهان می‌کند.

kotlin
// 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، cache و تحلیل را در درون خود هماهنگ می‌کند. ViewModel زنجیره‌های فراخوانی به api.charge() یا cache.save() ندارد — این LoD را نقض می‌کرد. تمام ساختار داخلی پشت یک فراخوانی پنهان شده است.

اشتباهات رایج هنگام پیروی از LoD

پیروی کورکورانه: متدهای wrapper اضافی

Wrapperهای اضافی — زمانی که برنامه‌نویس ده‌ها متد واسطه ایجاد می‌کند که صرفاً فراخوانی را از یک کلاس به کلاس دیگر منتقل می‌کنند. Order.getUserEmail() = user.email — wrapper بی‌فایده. LoD برای هر فیلدی wrapper نمی‌خواهد — می‌خواهد زنجیره‌ها را پنهان کند، نه فیلدهای ساده جداگانه را.

معیار: اگر wrapper صرفاً یک فیلد را بدون تبدیل و بدون پنهان‌سازی زنجیره برمی‌گرداند — لازم نیست. Order.getUserEmail() — wrapper بد، زیرا user.email دسترسی مستقیم به فیلد شی همسایه است و user فیلد مستقیم Order است که توسط LoD مجاز است. نقض زمانی بود که Order user.getEmail() را در دو مرحله برمی‌گرداند: اول user، سپس email.

برای فیلدهای مستقیم wrapper ایجاد نکنید (دسترسی به فیلد شی خود یا فیلد مستقیم — توسط LoD مجاز است). زمانی که مشتری شروع به حرکت گذری می‌کند wrapper ایجاد کنید: a.b().c().d() → a.b().d() یا a.d().

اشتباه گرفتن LoD با قانون دمیتر برای داده‌ها

LoD برای رفتار اعمال می‌شود، نه برای داده‌ها. Data class (DTO — کانتینرهای ساده داده) ملزم به رعایت LoD نیستند: هدف آن‌ها آشکار کردن داده‌ها است. OrderDTO.items[0].price — نقض LoD نیست، زیرا DTO طبق تعریف یک ساختار داده است، نه یک شی با رفتار. اشتباه گرفتن اشیا و ساختارهای داده — یکی از رایج‌ترین خطاها است.

تمایز را Robert C. Martin: «Clean Code» (2008) بیان کرده است: «اشیاء داده‌ها را پنهان می‌کنند و رفتار را آشکار می‌کنند. ساختارهای داده داده‌ها را آشکار می‌کنند و رفتاری ندارند». LoD به اشیاء با رفتار مربوط می‌شود. برای ساختارهای داده (DTO، مدل‌های JSON) زنجیره‌های دسترسی مجاز هستند. به محض اینکه یک ساختار داده متدی با منطق پیدا می‌کند — به شی تبدیل می‌شود و باید LoD را رعایت کند.

تمایز قائل شوید: اگر کلاس فقط شامل فیلدهای بدون متد است (DTO) — LoD برای آن اعمال نمی‌شود. اگر کلاس شامل متدهایی با منطق است — LoD الزامی است. در بازبینی کد بررسی کنید: این data class (DTO) است یا شی (با متدها)؟

سوالات متداول

قانون دمیتر به زبان ساده چیست؟

قانون دمیتر (LoD): شی فقط می‌تواند با دوستان نزدیک ارتباط برقرار کند — خودش، فیلدهایش، پارامترهای متدهایش و اشیایی که خودش ایجاد کرده است. نمی‌توان از طریق زنجیره رفت: a.getB().getC().doSomething() — این نقض است.

تفاوت LoD با Tell, Don't Ask چیست؟

LoD — درباره این است که به کدام اشیا می‌توان دسترسی داشت (فقط همسایگان مستقیم). Tell, Don't Ask — درباره این است که چگونه دسترسی داشت (داده نخواه، بلکه بگو انجام دهد). آن‌ها مکمل یکدیگرند: LoD دایره ارتباط را محدود می‌کند، Tell Don't Ask — نحوه دسترسی را.

چه زمانی می‌توان LoD را نقض کرد؟

LoD را می‌توان برای DTO (Data Transfer Objects) و ساختارهای داده ساده بدون منطق نقض کرد. همچنین Builder نقض محسوب نمی‌شود، زیرا هر فراخوانی همان builder را برمی‌گرداند. استثناها: زنجیره‌ها در Stream API (map, filter) — نقض LoD نیست.

Detekt چگونه LoD را در Android بررسی می‌کند؟

Detekt دارای قانون TooManyFunctions (غیرمستقیم) است، اما برای بررسی مستقیم زنجیره‌ها از قانون DataClassShouldBeImmutable و بررسی‌های سفارشی از طریق bindingReference استفاده کنید. CI را تنظیم کنید: زنجیره‌های طولانی‌تر از ۲ فراخوانی — هشدار، طولانی‌تر از ۳ — خطای ساخت.

SwiftLint چگونه LoD را در iOS بررسی می‌کند؟

SwiftLint قانون داخلی برای LoD ندارد، اما می‌توان یک قانون سفارشی از طریق regex ایجاد کرد: زنجیره‌های به شکل \..+\.\..+\.\..+ (۳+ فراخوانی از طریق نقطه). جایگزین: از قانون nimble_operator استفاده کنید و آن را برای تشخیص زنجیره‌های طولانی گسترش دهید.

خلاصه

  • LoD (Law of Demeter / اصل کمترین آگاهی) — قاعده: شی فقط با دوستان مستقیم تعامل می‌کند.
  • زنجیره‌های فراخوانی (train wrecks) — نقض اصلی LoD: a.b().c().d() وابستگی‌های پنهان به کل زنجیره انواع ایجاد می‌کند.
  • Tell, Don't Ask — اصل نزدیک: عمل را به شی واگذار کنید، نه اینکه داده‌های آن را برای پردازش خارجی درخواست کنید.
  • Getterهای گسترده — علت نقض‌ها: اگر شی همه فیلدها را آشکار کند، مشتریان شروع به حرکت گذری روی آن‌ها می‌کنند.
  • facade (Facade) — الگویی برای رعایت LoD: یک رابط واحد زیرسیستم پیچیده را از مشتری پنهان می‌کند.
  • DTO و data class — استثنا: ساختارهای داده ملزم به رعایت LoD نیستند، زیرا رفتار ندارند.
  • خودکارسازی از طریق Detekt (Android) یا SwiftLint سفارشی (iOS) تعداد نقض‌های LoD را در پایگاه کد کاهش می‌دهد.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید