Law of Demeter (LoD) — juga disebut "Principle of Least Knowledge" — adalah aturan desain: sebuah objek hanya boleh berbicara dengan teman dekatnya, bukan teman dari temannya.
Dirumuskan Ian Holland di Northeastern University (1987), dinamai sesuai proyek tempat prinsip ini ditemukan.
Aturan praktis:
Sebuah method M di class C hanya boleh memanggil method dari:
- Diri sendiri (
this) - Parameter yang dioper ke
M - Objek yang dibuat di dalam
M - Field langsung milik
C
Tidak boleh: method dari objek yang didapat dari salah satu di atas.
Contoh pelanggaran (train wreck):
// Rantai panjang — train wreck!
function getPaperBoyAmount(customer) {
return customer.getWallet().getMoney().getAmount();
}
// Masalah:
// 1. Fungsi tahu struktur internal Customer (punya wallet)
// 2. Tahu struktur Wallet (punya money)
// 3. Tahu struktur Money (punya amount)
// Kalau Wallet diganti jadi BankAccount — 10 file harus diedit (Shotgun Surgery)
Perbaikan: minta, jangan ambil
// Biarkan Customer yang tahu internal-nya
class Customer {
getAvailableAmount() {
return this.wallet.getMoney().getAmount();
}
}
function getPaperBoyAmount(customer) {
return customer.getAvailableAmount();
}
// Sekarang fungsi cuma "bicara" dengan Customer — tidak peduli Wallet diganti apa
Ini juga sering disebut "Tell, Don't Ask":
// ASK: tarik data keluar, putuskan di pemanggil
if (user.getAccount().getStatus() === "active" && user.getAccount().getBalance() > 0) {
user.getAccount().withdraw(100);
}
// TELL: suruh objek melakukan sesuatu
if (user.canWithdraw(100)) {
user.withdraw(100);
}
Real-world: Eloquent chain — kapan OK, kapan tidak?
Di Laravel, pola ini sangat umum:
$user->posts()->where("published", true)->latest()->first();
Ini bukan pelanggaran LoD — karena posts(), where(), latest(), first() semua mengembalikan tipe yang sama (Query Builder). Mereka bukan objek berbeda, cuma fluent API.
Bandingkan dengan:
// INI pelanggaran LoD
$user->getProfile()->getAddress()->getCity()->getCountry();
// User → Profile → Address → City → Country — 4 tipe berbeda
Aturan kepala: hitung tipe, bukan titik
Rantai . yang menyalurkan fluent API pada tipe sama (Query Builder, Stream, Promise) umumnya aman:
users
.filter(u => u.isActive)
.map(u => u.email)
.sort();
// Semua Array — aman
Rantai . yang melompat antar tipe berbeda = bau LoD:
order.getCustomer().getBillingAddress().getStreet();
// Order → Customer → Address → Street — train wreck
Kapan LoD tidak berlaku: Data Structures
Jika objek memang Data Transfer Object (DTO) atau struktur data murni — bukan objek dengan behavior — LoD lebih longgar. Kamu memang harus akses field-field di dalamnya.
// DTO murni — akses langsung OK
const response = await api.fetchUser(id);
const city = response.data.address.city; // OK — ini data, bukan behavior
// Objek dengan behavior — akses via method
class User {
getCity() { return this.address.city; }
}
user.getCity(); // hormati LoD
Kriteria: apakah objek ini punya perilaku yang bisa dilanggar kontrasinya?
- Ya → hormati LoD, sembunyikan struktur
- Tidak (cuma data) → akses langsung boleh
Hide Delegate: refactoring pattern untuk LoD
// Sebelum
class Person {
getDepartment() { return this.department; }
}
class Department {
getManager() { return this.manager; }
}
// Client code
const manager = person.getDepartment().getManager();
// Sesudah — sembunyikan delegasi
class Person {
getManager() { return this.department.getManager(); }
}
// Client code
const manager = person.getManager();
Trade-off: Person kini tahu tentang manager. Tapi client code tidak perlu tahu soal Department sama sekali.
Batas wajar: jangan paranoid
Menerapkan LoD ekstrem bisa melahirkan Middle Man smell — class yang isinya cuma delegasi:
class OrderAdapter {
getCustomerName() { return this.order.getCustomer().getName(); }
getCustomerEmail() { return this.order.getCustomer().getEmail(); }
getCustomerPhone() { return this.order.getCustomer().getPhone(); }
// ... 20 method delegasi lagi
}
Ini lebih buruk dari pelanggaran LoD. Kriteria akhir: pilih yang lebih mudah diubah besok.
Rangkuman:
- Panggil method teman dekat, bukan teman dari teman
- Train wreck (
a.b().c().d()) dengan tipe berbeda = smell - Fluent API dengan tipe sama (Query Builder) = aman
- DTO / data struktur murni = LoD tidak berlaku penuh
- Hindari Middle Man — jangan over-engineer delegasi
🎭 Analogi sehari-hari
Law of Demeter itu kayak aturan minta tolong. Misal kamu butuh kunci kantor. Cara hormat: minta sama temenmu Budi (person.getKey()). Cara gak hormat (LoD violation): minta Budi mention nama bos-nya, ke security HR-nya, ke kepala building, baru dapet kunci (person.getBoss().getHR().getSecurity().getKey()). Selain ribet, kamu sekarang TAU strukturnya — kalo struktur bos-HR-security berubah, kamu kena. Hormati LoD = JANGAN tau struktur internal teman, minta langsung. Plus awas: terlalu paranoid LoD = adapter class yang isinya 50% delegate method (Middle Man smell). Balance.
⚠️ Jebakan yang sering ditemui
- Train wreck
a.b().c().d().e()dengan tipe beda — smell. Refactor pakai Hide Delegate. - Apply LoD di DTO/data struct — overkill. Data murni boleh akses field langsung.
- Apply LoD di fluent API (
query.where().orderBy().limit()) — false positive. Tipe sama = OK. - Over-correct dengan Middle Man — adapter class yang isinya 50+ delegate methods = lebih buruk dari pelanggaran LoD.
- Pikir LoD = "no nested call" — bukan. LoD = "jangan akses STRUKTUR INTERNAL teman lewat method dia".
- Optional chaining (
a?.b?.c) untuk hide null tapi tetap train wreck — null safety beda dari LoD.
Kapan LoD Berlaku Ketat vs Longgar
Ketat:
- Class dengan BEHAVIOR (methods yang ngubah state)
- Public API library/framework
- Domain object dengan invariant
Longgar:
- DTO / response API JSON
- Data struktur murni (record/struct)
- Fluent API yang return tipe sama
Hide Delegate Pattern
// SEBELUM — train wreck
const managerName = order.getCustomer().getDepartment().getManager().getName()
// SESUDAH — Hide Delegate (Order tau cara sampai ke manager name)
class Order {
getManagerName() {
return this.customer.getDepartment().getManager().getName()
}
}
const managerName = order.getManagerName()
// Client code gak tau struktur Customer/Department/Manager
🎯 Ini smell LoD atau bukan?
a.b().c().d()antar class beda dengan behavior → smell ✓- Fluent API tipe sama (
query.where().orderBy()) → aman, gak smell- Akses DTO field (
response.data.user.name) → aman- Method chain di array/string (
arr.filter().map().sort()) → aman- Adapter class dengan 30 delegate method → Middle Man smell (over-correction)
- Optional chaining null safety (
a?.b?.c) → BUKAN LoD issue, tapi nullable handlingAturan: kalo train wreck-nya nyebrang antar tipe berbeda yang punya behavior, refactor. Sisanya, gunakan judgment.
TL;DR: Law of Demeter (LoD) = "Principle of Least Knowledge". Object cuma boleh ngomong sama teman dekat, bukan teman-dari-teman. Train wreck a.b().c().d() antar tipe beda = smell, fix pakai Hide Delegate. Tidak berlaku untuk DTO, fluent API tipe sama, method chain di array. Over-correct = Middle Man smell (class adapter yang isinya delegate semua). Balance: hindari train wreck tapi jangan paranoid bikin adapter berlebihan.