Masukkan Password
77 Accessor Decorator
Lecture ini melanjutkan materi sebelumnya, tetapi sekarang decorator dipasang pada accessor, yaitu getter dan setter.
Kalau lecture sebelumnya fokus pada:
Property Decorator
lecture ini fokus pada:
Accessor Decorator → decorator pada
getdanset.
Bagian yang paling penting adalah memahami descriptor, karena berbeda dengan property decorator biasa.
1. Apa itu Accessor?
Accessor adalah mekanisme untuk mengontrol bagaimana sebuah property:
- dibaca →
getter - diubah →
setter
Contohnya:
class Product {
private _price: number;
get price() {
return this._price;
}
set price(value: number) {
this._price = value;
}
}
Kemudian kita bisa:
const p = new Product();
p.price = 2400;
console.log(p.price);
Ketika:
p.price = 2400;
yang dijalankan adalah:
set price(value) { ... }
Sedangkan ketika:
console.log(p.price);
yang dijalankan:
get price() { ... }
2. Kenapa menggunakan _price?
Di contoh lecture, property aslinya dibuat:
private _price: number;
Kemudian dibuat getter:
get price() {
return this._price;
}
dan setter:
set price(value: number) {
this._price = value;
}
Kenapa tidak langsung:
get price() {
return this.price;
}
?
Karena akan terjadi infinite recursion.
Bayangkan:
get price()
↓
return this.price
↓
memanggil get price()
↓
return this.price
↓
memanggil get price()
↓
...
Makanya kita membutuhkan property internal:
_price
untuk menyimpan nilai sebenarnya.
3. Setter digunakan untuk validasi
Lecture juga memberikan validasi:
set price(value: number) {
if (value > 0) {
this._price = value;
} else {
throw new Error("Price should be a value greater than zero");
}
}
Artinya kita tidak boleh melakukan:
p.price = -100;
Karena:
-100 > 0
↓
false
↓
throw Error
Sedangkan:
p.price = 3000;
akan:
3000 > 0
↓
true
↓
_price = 3000
Jadi accessor sangat berguna untuk mengontrol akses ke property.
4. Sekarang kita buat decorator
Lecture kemudian membuat:
function accessLogger(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
}
Kemudian digunakan pada accessor:
class Product {
@accessLogger
get price() {
return this._price;
}
set price(value: number) {
this._price = value;
}
}
Perhatikan:
@accessLogger
tidak menggunakan ().
Artinya accessLogger di sini adalah decorator function, bukan decorator factory.
5. Accessor decorator menerima 3 parameter
Ini perbedaan penting dengan property decorator.
Property decorator
Biasanya:
function decorator(
target: any,
propertyKey: string
) {
}
Ada 2 parameter.
Sedangkan accessor decorator:
function decorator(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
}
Ada 3 parameter:
1. target
2. name / propertyKey
3. descriptor
6. Parameter pertama: target
target konsepnya sama seperti property decorator sebelumnya.
Kalau accessor adalah instance accessor:
get price() { ... }
maka:
target → Prototype
Kalau accessor dibuat static:
static get price() { ... }
maka:
target → Constructor / Class
Jadi:
Instance accessor
↓
target
↓
Prototype
Static accessor
↓
target
↓
Constructor
7. Parameter kedua: name
name adalah nama accessor.
Contohnya:
get price()
maka:
name
berisi:
"price"
Jadi kalau:
console.log(name);
hasilnya:
price
8. Parameter ketiga: descriptor
Nah, ini bagian terpenting dari lecture.
descriptor adalah object yang berisi informasi tentang accessor/property tersebut.
Dalam kasus getter/setter:
descriptor
kurang lebih berisi:
{
get: function,
set: function,
enumerable: false,
configurable: true
}
Yang paling penting:
descriptor.get
descriptor.set
Karena:
descriptor.get → getter asli
descriptor.set → setter asli
9. Bayangkan descriptor seperti kotak
Supaya mudah, bayangkan:
descriptor
│
├── get → getter function
│
├── set → setter function
│
├── enumerable
│
└── configurable
Jadi ketika decorator mendapatkan:
descriptor
dia bisa melihat:
“Oh, ternyata accessor ini punya getter dan setter.”
10. Kenapa decorator membutuhkan descriptor?
Karena tujuan lecture bukan mengganti getter/setter sepenuhnya.
Tujuannya adalah:
Tambahkan logging sebelum getter/setter asli dijalankan.
Misalnya ketika:
console.log(p.price);
kita ingin output:
Accessing value for property price
2400
Dan ketika:
p.price = 3000;
kita ingin:
Setting value for property price
Tetapi setelah logging, getter/setter asli tetap harus bekerja.
Untuk melakukan itu kita membutuhkan:
descriptor.get
descriptor.set
11. Simpan getter asli terlebih dahulu
Di decorator dibuat:
const getter = descriptor.get;
Artinya:
Simpan getter asli ke variable
getter.
Misalnya sebelum decorator:
get price() {
return this._price;
}
Maka:
descriptor.get
menunjuk ke function tersebut.
Kita simpan:
getter → getter asli
12. Simpan setter asli
Begitu juga:
const setter = descriptor.set;
Sekarang:
getter → getter asli
setter → setter asli
Kenapa harus disimpan?
Karena sebentar lagi kita akan mengganti:
descriptor.get
dan:
descriptor.set
dengan function baru.
Kalau function lama tidak disimpan terlebih dahulu, kita bisa kehilangan akses ke getter/setter asli.
13. Mengganti getter
Kemudian:
descriptor.get = function () {
console.log(`Accessing value for property ${name}`);
if (getter) {
return getter.call(this);
}
return undefined;
};
Sekarang getter sudah “dibungkus”.
Konsepnya:
Getter asli
↓
kita simpan
↓
buat getter baru
↓
logging
↓
panggil getter asli
Ini sering disebut konsep wrapping.
14. Kenapa getter.call(this)?
Ini juga sangat penting.
Getter asli adalah:
get price() {
return this._price;
}
Dia membutuhkan this.
Misalnya:
const p = new Product();
p.price;
Di dalam getter:
this
harus menunjuk ke:
p
Karena itu decorator memanggil:
getter.call(this)
call() memungkinkan kita menentukan nilai this.
Jadi:
getter.call(this)
berarti:
Jalankan getter asli dan gunakan object saat ini sebagai
this.
15. Kenapa bukan getter() saja?
Misalnya kita menulis:
return getter();
Masalahnya adalah this bisa tidak menunjuk ke object yang benar.
Padahal getter asli:
get price() {
return this._price;
}
membutuhkan:
this._price
Karena itu kita menggunakan:
getter.call(this)
supaya konteks object tetap benar.
16. Bagaimana getter bekerja setelah decorator?
Misalnya:
console.log(p.price);
Alurnya:
p.price
↓
getter hasil decorator
↓
console.log("Accessing...")
↓
getter.call(this)
↓
getter asli
↓
return this._price
↓
2400
Jadi output:
Accessing value for property price
2400
17. Sekarang setter
Setter juga dibungkus.
Awalnya:
const setter = descriptor.set;
Kemudian:
descriptor.set = function (value: number) {
console.log(`Setting value for property ${name}`);
if (setter) {
setter.call(this, value);
}
};
Sekarang setter asli tetap disimpan, tetapi kita menambahkan logging sebelum menjalankannya.
18. Kenapa setter.call(this, value)?
Karena setter asli membutuhkan dua hal:
this
Untuk mengetahui object mana yang sedang diubah.
value
Nilai baru yang diberikan.
Misalnya:
p.price = 3000;
maka:
this → p
value → 3000
Sehingga:
setter.call(this, value);
kurang lebih berarti:
jalankan setter asli
dengan this = p
dan value = 3000
19. Alur setter secara lengkap
Ketika:
p.price = 3000;
terjadi:
p.price = 3000
↓
setter decorator
↓
"Setting value for property price"
↓
setter asli
↓
validasi value > 0
↓
this._price = 3000
Jadi decorator hanya menambahkan logging, sementara logic asli tetap berjalan.
20. Kenapa if (getter)?
Di kode terdapat:
if (getter) {
return getter.call(this);
}
return undefined;
Kenapa perlu dicek?
Karena sebuah accessor belum tentu mempunyai getter.
Misalnya secara konsep kita bisa memiliki hanya setter:
set price(value: number) {
this._price = value;
}
Dalam kondisi tersebut:
descriptor.get
bisa tidak tersedia.
Jadi kita harus memastikan:
if (getter)
sebelum memanggilnya.
Hal yang sama berlaku untuk setter:
if (setter) {
setter.call(this, value);
}
21. Kenapa decorator dipasang hanya pada getter?
Ini bagian yang cukup membingungkan dalam lecture.
Kode:
class Product {
@accessLogger
get price() {
return this._price;
}
set price(value: number) {
this._price = value;
}
}
Terlihat decorator hanya ditempelkan ke:
get price
bukan:
set price
Tetapi ketika:
p.price = 3000;
setter juga terkena efek decorator.
Kenapa?
Karena getter dan setter tersebut sebenarnya merupakan satu accessor/property descriptor dengan nama yang sama, yaitu:
price
22. Getter dan setter sebenarnya satu pasangan
Secara konsep:
get price() { ... }
set price(value) { ... }
bukan dianggap sebagai dua property yang benar-benar terpisah.
Mereka adalah pasangan accessor untuk property:
price
Descriptor-nya:
{
get: getterFunction,
set: setterFunction
}
Jadi ketika decorator bekerja terhadap descriptor price, descriptor tersebut memiliki:
get → getter
set → setter
Karena itu decorator dapat membungkus keduanya.
23. Analogi pintu 🚪
Bayangkan property price adalah sebuah pintu.
Ada dua aktivitas:
MASUKKAN sesuatu
↓
SETTER
AMBIL sesuatu
↓
GETTER
Decorator:
@accessLogger
seperti kamera/security di pintu tersebut.
Ketika seseorang mengambil nilai:
GET
↓
"Accessing value..."
↓
GETTER
Ketika seseorang memasukkan nilai:
SET
↓
"Setting value..."
↓
SETTER
Jadi satu decorator mengawasi dua arah akses.
24. Full code secara sederhana
Kurang lebih hasil akhirnya seperti ini:
function accessLogger(
target: any,
name: string,
descriptor: PropertyDescriptor
): any {
const getter = descriptor.get;
const setter = descriptor.set;
descriptor.get = function () {
console.log(
`Accessing value for property ${name}`
);
if (getter) {
return getter.call(this);
}
return undefined;
};
descriptor.set = function (value: number) {
console.log(
`Setting value for property ${name}`
);
if (setter) {
setter.call(this, value);
}
};
return descriptor;
}
Kemudian:
class Product {
private _price: number;
@accessLogger
get price() {
return this._price;
}
set price(value: number) {
if (value > 0) {
this._price = value;
} else {
throw new Error(
"Price should be a value greater than zero"
);
}
}
}
25. Contoh penggunaan
Misalnya:
const p = new Product();
p.price = 2400;
console.log(p.price);
Ketika:
p.price = 2400;
output:
Setting value for property price
Kemudian:
console.log(p.price);
output:
Accessing value for property price
2400
Jadi decorator tidak menghilangkan logic asli.
Decorator hanya menyisipkan logic tambahan.
26. Apa yang sebenarnya dilakukan decorator?
Tanpa decorator:
p.price
↓
getter asli
↓
_price
Dengan decorator:
p.price
↓
getter baru
↓
logging
↓
getter asli
↓
_price
Untuk setter:
p.price = 3000
↓
setter baru
↓
logging
↓
setter asli
↓
validasi
↓
_price = 3000
Ini adalah konsep yang sangat penting:
Decorator bisa digunakan untuk membungkus behavior yang sudah ada dan menambahkan behavior baru.
27. Bedanya Property Decorator dan Accessor Decorator
Ini bagus untuk dibandingkan dengan lecture sebelumnya.
| Property Decorator | Accessor Decorator | |
|---|---|---|
| Target | Property | Getter/Setter |
| Parameter | target, propertyKey | target, name, descriptor |
| Jumlah parameter | 2 | 3 |
| Descriptor | Tidak utama | Sangat penting |
| Bisa mengakses getter/setter asli | Tidak seperti contoh ini | Ya |
| Bisa membungkus behavior | Bisa, dengan teknik tertentu | Sangat natural melalui descriptor |
28. Apa fungsi descriptor sebenarnya?
Kalau target menjawab:
“Decorator ini diterapkan pada object/class mana?”
dan name menjawab:
“Nama accessor-nya apa?”
maka descriptor menjawab:
“Bagaimana accessor tersebut bekerja?”
Misalnya:
descriptor.get
→ getter
descriptor.set
→ setter
descriptor.enumerable
→ apakah property enumerable
descriptor.configurable
→ apakah descriptor dapat dikonfigurasi
Jadi descriptor adalah semacam metadata + behavior definition dari property/accessor tersebut.
29. Gambaran mental yang bagus
Coba ingat struktur ini:
@accessLogger
│
â–¼
Property "price"
│
descriptor
/ \
/ \
get set
│ │
getter asli setter asli
│ │
â–¼ â–¼
_price validasi
Decorator mengambil descriptor tersebut:
descriptor
│
├── get ──→ simpan sebagai getter
│
└── set ──→ simpan sebagai setter
Kemudian menggantinya:
descriptor.get = getter baru
descriptor.set = setter baru
Getter/setter baru melakukan:
LOG
↓
panggil getter/setter asli
30. Kenapa decorator ini disebut “logger”?
Karena decorator tersebut tidak mengubah tujuan utama property.
Tujuan property tetap:
price
Decorator hanya menambahkan:
"Siapa yang mengakses price?"
"Siapa yang mengubah price?"
Dalam contoh:
GET → Accessing value for property price
SET → Setting value for property price
Ini contoh sederhana dari penggunaan decorator untuk logging / monitoring.
Dalam aplikasi nyata, konsep seperti ini bisa dikembangkan untuk:
- logging
- debugging
- performance monitoring
- authorization
- validation
- tracking perubahan data
- caching
- dan sebagainya.
🧠Ringkasan paling penting
Kalau lecture ini terasa panjang, sebenarnya konsep utamanya hanya ini:
1. Accessor adalah getter dan setter
get price() { ... }
set price(value) { ... }
2. Accessor decorator menerima 3 parameter
function decorator(
target,
name,
descriptor
) {}
3. target
Instance accessor:
→ Prototype
Static accessor:
→ Constructor/Class
4. name
Nama accessor:
price
5. descriptor
Berisi getter dan setter:
descriptor.get
descriptor.set
6. Simpan function asli
const getter = descriptor.get;
const setter = descriptor.set;
7. Ganti dengan function baru
descriptor.get = function () {
// tambahan logic
return getter.call(this);
};
dan:
descriptor.set = function (value) {
// tambahan logic
setter.call(this, value);
};
8. call(this) menjaga konteks object
getter.call(this)
setter.call(this, value)
supaya this tetap mengarah ke object yang sedang menggunakan property tersebut.
9. Getter dan setter dengan nama yang sama adalah satu accessor
Itulah sebabnya decorator yang dipasang pada:
get price()
bisa memengaruhi:
set price()
karena keduanya berada dalam descriptor price.
🔥 Satu kalimat untuk mengingat semuanya
Accessor decorator bekerja dengan mengambil
descriptor, menyimpan getter/setter asli, menggantinya dengan function baru yang menambahkan behavior, lalu memanggil getter/setter asli menggunakancall(this).
Kalau dibuat super sederhana:
@accessLogger
↓
descriptor
↙ ↘
getter setter
↓ ↓
LOG GET LOG SET
↓ ↓
getter asli setter asli
↓ ↓
value value
Jadi kalau pada lecture sebelumnya property decorator terasa seperti “mengubah property”, pada lecture ini kamu bisa melihat decorator dengan cara yang lebih kuat: kita bisa masuk ke descriptor dan membungkus behavior getter/setter tanpa menghapus logic aslinya.