Masukkan Password
80 Mengembalikan Kelas dari Decorator
Lecture ini sangat penting karena mulai masuk ke bagian “decorator bukan cuma bisa membaca atau menambahkan behavior, tetapi juga bisa mengubah sesuatu dan mengembalikannya.”
Fokus utamanya ada pada pertanyaan:
Apa yang bisa dikembalikan (
return) oleh masing-masing jenis decorator?
Dan bagian paling penting adalah class decorator bisa mengembalikan class baru yang menggantikan class asli.
1. Kita mulai dari konsep paling dasar
Sebelumnya kita sudah belajar:
Decorator pada dasarnya adalah sebuah function.
Misalnya:
function Logger(target: any) {
console.log("Logger called");
}
Lalu digunakan:
@Logger
class Person {
}
Karena decorator adalah function, secara logika kita bisa berpikir:
function sesuatu() {
return sesuatu;
}
Artinya decorator juga bisa mengembalikan sesuatu.
Tetapi pertanyaannya:
Apa yang boleh/berguna untuk dikembalikan dari setiap jenis decorator?
Nah, itu yang dibahas lecture ini.
2. Gambaran besarnya
Ada beberapa decorator yang sudah kita pelajari:
Class Decorator
Property Decorator
Accessor Decorator
Method Decorator
Parameter Decorator
Masing-masing punya kemampuan return yang berbeda.
Untuk legacy TypeScript decorators yang digunakan dalam lecture ini, gambaran sederhananya:
| Decorator | Yang bisa dikembalikan |
|---|---|
| Class | Class/constructor baru |
| Method | PropertyDescriptor yang sudah dimodifikasi |
| Accessor | PropertyDescriptor yang sudah dimodifikasi |
| Property | Umumnya tidak mengembalikan sesuatu yang digunakan |
| Parameter | Umumnya tidak mengembalikan sesuatu yang digunakan |
Yang paling spesial adalah:
Class decorator bisa mengganti class asli dengan class baru.
3. Mengingat kembali decorator pada accessor
Sebelumnya kita punya accessor:
class Product {
private _price: number;
get price() {
return this._price;
}
set price(value: number) {
this._price = value;
}
}
Accessor decorator mendapatkan:
function AccessLogger(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
}
Ada:
target
name
descriptor
Nah, descriptor bisa kita modifikasi.
Misalnya:
function AccessLogger(
target: any,
name: string,
descriptor: PropertyDescriptor
) {
// modify descriptor
return descriptor;
}
Jadi:
descriptor asli
↓
decorator
↓
descriptor dimodifikasi
↓
return descriptor
Ini memungkinkan kita mengubah behavior getter/setter.
4. Method decorator juga sama
Untuk method:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
}
Sekali lagi kita mendapatkan:
target
methodName
descriptor
Dan kita bisa melakukan:
descriptor.value = function () {
// behavior baru
};
Kemudian:
return descriptor;
Jadi method decorator bisa:
mengambil descriptor → memodifikasinya → mengembalikan descriptor baru.
5. Property decorator sedikit berbeda
Property decorator:
function Capitalize(
target: any,
propertyKey: string
) {
}
Kita hanya mendapatkan:
target
propertyKey
Tidak mendapatkan PropertyDescriptor seperti method/accessor decorator.
Di lecture sebelumnya kita melihat contoh yang mengembalikan object getter/setter.
Tetapi ada catatan penting:
Dalam legacy TypeScript property decorators, return value dari property decorator pada dasarnya tidak digunakan oleh TypeScript untuk mengganti property tersebut.
Jadi secara praktik, property decorator biasanya digunakan untuk melakukan sesuatu terhadap target/metadata, bukan untuk mengganti property dengan return value.
6. Parameter decorator juga biasanya tidak return sesuatu
Parameter decorator:
function Param(
target: any,
methodName: string,
index: number
) {
}
Biasanya digunakan untuk:
- membaca informasi parameter
- menyimpan metadata
- melakukan registrasi metadata
- membantu framework mengetahui informasi parameter
Tetapi return value-nya biasanya tidak digunakan.
Jadi untuk mental model:
Property decorator
→ biasanya tidak return sesuatu yang menggantikan property
Parameter decorator
→ biasanya tidak return sesuatu
7. Sekarang bagian paling menarik: Class Decorator
Misalnya kita punya:
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
Kemudian kita buat:
function Logger(target: any) {
}
Dan:
@Logger
class Person {
}
Untuk class decorator, target adalah constructor/class.
Secara konsep:
@Logger
↓
Person
↓
target
↓
Person class / constructor
Jadi decorator mempunyai akses ke class tersebut.
8. Class di JavaScript sebenarnya adalah constructor function
Lecture mengatakan:
Behind the scenes, a class is basically a constructor function.
Untuk memahami decorator legacy, mental model ini sangat membantu.
Misalnya:
class Person {
constructor(name: string) {
this.name = name;
}
}
Kita bisa membayangkan ada sebuah constructor function:
Person
↓
constructor function
Sehingga ketika:
function Logger(target: any) {
}
dipanggil oleh decorator, target berisi referensi ke constructor/class tersebut.
9. Nah, kita bisa membuat class baru
Lecture kemudian membuat:
function Logger(target: any): any {
class LoggingClass extends target {
}
return LoggingClass;
}
Ini konsep yang sangat penting.
Mari kita pecah.
10. class LoggingClass extends target
Biasanya kita menulis:
class Student extends Person {
}
Artinya:
Student
↓ extends
Person
Tetapi di sini kita tidak ingin hard-code Person.
Karena decorator Logger mungkin digunakan pada class lain juga.
Maka:
class LoggingClass extends target {
}
Artinya:
Buat class baru yang mewarisi class yang diberikan kepada decorator.
Kalau decorator dipakai:
@Logger
class Person {
}
maka:
target
↓
Person
LoggingClass extends Person
Kalau nanti:
@Logger
class Product {
}
maka:
target
↓
Product
LoggingClass extends Product
Jadi decorator-nya reusable.
11. Kenapa tidak langsung extends Person?
Kalau kita menulis:
function Logger(target: any) {
class LoggingClass extends Person {
}
}
decorator tersebut hanya cocok untuk Person.
Tetapi kalau:
class LoggingClass extends target {
}
maka class yang diberikan decorator bisa apa saja.
Misalnya:
@Logger
class Person {}
atau:
@Logger
class Product {}
atau:
@Logger
class Employee {}
Semuanya bisa.
Karena:
target = class yang sedang diberi decorator
12. Masalah dengan target: Function
Lecture menemukan error ketika menulis tipe seperti:
target: Function
Kenapa?
Karena:
Function
hanya mengatakan:
“Ini adalah sebuah function.”
Tidak berarti:
“Function ini pasti constructor yang bisa digunakan dengan
new.”
Padahal kita ingin melakukan:
extends target
dan membuat instance dari constructor tersebut.
Jadi lecture menggunakan bentuk constructor type.
Konsepnya kira-kira:
target: new (...args: any[]) => any
Artinya:
targetadalah sesuatu yang bisa dipanggil dengannew.
13. Apa maksud new (...args: any[]) => any?
Ini memang terlihat menyeramkan.
Mari pecah:
new (...args: any[]) => any
Artinya:
new
↓
constructor
↓
menerima sejumlah argument
↓
menghasilkan sebuah object
Contoh class:
class Person {
constructor(name: string) {
}
}
Constructor-nya menerima:
name
Tetapi decorator tidak boleh mengasumsikan semua class hanya punya satu parameter.
Bisa saja:
class Person {
constructor(name: string, age: number) {
}
}
atau:
class Product {
constructor(name: string, price: number, stock: number) {
}
}
Karena itu digunakan:
...args
untuk menerima argument sebanyak apa pun.
14. Lalu argument tersebut diteruskan ke parent
Misalnya:
class Person {
constructor(name: string) {
this.name = name;
}
}
Kemudian:
class LoggingClass extends target {
constructor(...args: any[]) {
super(...args);
}
}
Ini berarti:
new LoggingClass("John")
↓
LoggingClass constructor
↓
super("John")
↓
Person constructor
↓
name = "John"
Jadi constructor asli dari class parent tetap dipanggil.
15. Kenapa perlu super(...args)?
Karena:
class LoggingClass extends target {
}
berarti LoggingClass adalah child class dari target.
Kalau child class memiliki constructor sendiri:
constructor(...args: any[]) {
}
maka kita perlu memanggil:
super(...args);
untuk menjalankan constructor parent.
Dalam contoh:
LoggingClass
↓ extends
Person
Maka:
super(...args)
memanggil:
Person constructor
16. Mari lihat full contoh
Konsep lecture dapat disederhanakan menjadi:
function Logger(target: any): any {
class LoggingClass extends target {
constructor(...args: any[]) {
console.log("Creating a new LoggingClass instance");
super(...args);
}
}
return LoggingClass;
}
Kemudian:
@Logger
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
Perhatikan sesuatu yang sangat penting:
return LoggingClass;
17. Apa yang terjadi dengan Person?
Ini inti lecture.
Awalnya:
Person
↓
class Person
Decorator:
@Logger
menghasilkan:
LoggingClass
↓
extends Person
Kemudian:
return LoggingClass;
membuat hasil decorator menjadi class baru.
Secara mental model:
Person
↓
@Logger
↓
LoggingClass
↓
menggantikan Person
Jadi nama Person sekarang mengacu pada class hasil decorator.
18. Ini konsep yang sangat penting
Awalnya:
class Person {
}
Setelah decorator:
Person
↓
sebenarnya merujuk ke
↓
LoggingClass
Sedangkan:
LoggingClass
↓
extends
↓
original Person
Jadi kita mendapatkan semacam:
Person (nama yang digunakan dari luar)
│
▼
LoggingClass
│
▼ extends
Original Person
Ini seperti mengganti class dengan class pembungkus/subclass.
19. Analogi sederhana: mengganti kendaraan
Bayangkan ada:
Mobil asli
Kemudian decorator mengatakan:
“Saya akan membuat mobil baru yang berdasarkan mobil asli, tetapi saya tambahkan fitur tambahan.”
Jadi:
Mobil asli
↓
Decorator
↓
Mobil baru
├── semua kemampuan mobil asli
└── fitur tambahan
Kemudian mobil baru tersebut menggantikan mobil lama sebagai object yang digunakan.
Sama dengan:
Person
↓
Logger decorator
↓
LoggingClass extends Person
20. Kita tambahkan static property
Lecture kemudian membuat:
class LoggingClass extends target {
static company: string = "Google";
}
Karena ini static:
LoggingClass.company
akan menghasilkan:
Google
Dan yang penting:
Static property berada pada constructor/class, bukan pada instance.
Jadi lebih tepat membayangkannya:
LoggingClass
│
└── company = "Google" ← static
Bukan:
loggingInstance
└── company
Ini juga berarti kalau kamu melihat property company di prototype saat debugging, perlu hati-hati—static property secara konsep berada pada class/constructor, bukan prototype instance.
21. Apa yang terjadi ketika membuat instance?
Misalnya:
const p = new Person("John");
Ingat:
Person sekarang sudah digantikan oleh hasil decorator.
Secara konsep:
new Person("John")
↓
new LoggingClass("John")
↓
LoggingClass constructor
↓
console.log(...)
↓
super("John")
↓
Original Person constructor
Jadi output:
Creating a new LoggingClass instance
kemudian constructor Person juga berjalan melalui super().
22. Inilah bukti bahwa class sudah diganti
Lecture kemudian melakukan:
console.log(p);
dan object yang terlihat adalah instance dari LoggingClass.
Mengapa?
Karena:
Person
↓
@Logger
↓
LoggingClass
Kemudian:
new Person()
secara efektif membuat:
new LoggingClass()
Jadi object-nya adalah instance dari class pengganti tersebut.
23. Yang sebenarnya terjadi secara konsep
Sebelum decorator:
Person
│
├── constructor
├── name
└── prototype
Setelah decorator:
Person
│
▼
LoggingClass
│
├── constructor
├── static company
└── prototype
│
└── inherits from Person.prototype
Jadi LoggingClass memiliki hubungan inheritance dengan Person.
24. Kenapa ini berguna?
Karena kita bisa menambahkan behavior tanpa mengubah source class asli secara langsung.
Misalnya kita ingin setiap object yang dibuat dicatat:
function Logger(target: any): any {
class LoggingClass extends target {
constructor(...args: any[]) {
console.log("Creating object...");
super(...args);
}
}
return LoggingClass;
}
Kemudian:
@Logger
class Person {
constructor(name: string) {
console.log(`Person created: ${name}`);
}
}
Ketika:
const person = new Person("John");
kita mendapatkan behavior tambahan dari decorator.
25. Ini mirip “wrapper”
Kamu bisa membayangkan class decorator seperti wrapper:
Logger
│
▼
┌─────────────────┐
│ LoggingClass │
│ │
│ extra logic │
│ ↓ │
│ Person │
└─────────────────┘
Class asli tetap menjadi parent:
class LoggingClass extends target
tetapi dari luar kita menggunakan class baru.
26. Sekarang kita bandingkan jenis return
Ini bagian yang sangat penting untuk diingat.
A. Class decorator
Bisa mengembalikan:
class/constructor baru
Contoh konsep:
function Logger(target: any) {
class NewClass extends target {
}
return NewClass;
}
Kemudian:
Original Class
↓
Decorator
↓
New Class
↓
replace original
27. B. Method decorator
Method decorator menerima:
target
methodName
descriptor
Kita bisa mengubah:
descriptor.value
Misalnya:
function Logger(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("Before method");
const result = originalMethod.apply(this, args);
console.log("After method");
return result;
};
return descriptor;
}
Jadi:
original descriptor
↓
modify descriptor.value
↓
return descriptor
Ini adalah pola yang sangat umum.
28. C. Accessor decorator
Sama seperti method:
function Logger(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// modify descriptor
return descriptor;
}
Karena accessor juga memiliki descriptor:
descriptor
│
├── get
└── set
Kita bisa mengganti/membungkus getter dan setter.
29. D. Property decorator
Property decorator:
function Logger(
target: any,
propertyKey: string
) {
}
Biasanya tidak mengembalikan sesuatu yang digunakan untuk mengganti property.
Jadi lebih aman mengingat:
Property decorator
→ biasanya return nothing
30. E. Parameter decorator
Parameter decorator:
function Param(
target: any,
methodName: string,
index: number
) {
}
Biasanya juga:
return nothing
Decorator ini lebih sering digunakan untuk metadata/informasi parameter.
31. Cheat sheet lengkap
Sekarang gabungkan dengan lecture-lecture sebelumnya:
┌──────────────────────────────────────────┐
│ DECORATOR │
├──────────────┬───────────────────────────┤
│ Class │ target │
│ │ → bisa return class baru │
├──────────────┼───────────────────────────┤
│ Property │ target + propertyKey │
│ │ → biasanya no return │
├──────────────┼───────────────────────────┤
│ Method │ target + propertyKey │
│ │ + descriptor │
│ │ → return descriptor │
├──────────────┼───────────────────────────┤
│ Accessor │ target + propertyKey │
│ │ + descriptor │
│ │ → return descriptor │
├──────────────┼───────────────────────────┤
│ Parameter │ target + methodName │
│ │ + index │
│ │ → biasanya no return │
└──────────────┴───────────────────────────┘
32. Hubungan antara decorator dan descriptor
Ada pola yang sangat bagus untuk kamu ingat:
Method
method
↓
descriptor
↓
modify descriptor
↓
return descriptor
Accessor
getter/setter
↓
descriptor
↓
modify descriptor
↓
return descriptor
Sedangkan:
Class
class
↓
target
↓
create new class
↓
return new class
↓
replace original class
Jadi ada dua pola utama:
METHOD / ACCESSOR
↓
modify descriptor
↓
return descriptor
CLASS
↓
create new class
↓
return class
↓
replace original
33. Bagian extends target sangat penting
Kalau kamu melihat:
class LoggingClass extends target {
}
jangan berpikir:
“
targetadalah nama class tertentu.”
Tidak.
target adalah class yang sedang diberikan decorator.
Misalnya:
@Logger
class Person {}
maka:
target = Person
Sehingga:
class LoggingClass extends target
sama konsepnya dengan:
class LoggingClass extends Person
Tetapi lebih fleksibel.
34. Kenapa constructor menggunakan ...args?
Karena decorator tidak tahu constructor class yang dihias membutuhkan berapa parameter.
Bisa:
class Person {
constructor(name: string) {}
}
Bisa:
class Person {
constructor(name: string, age: number) {}
}
Bisa:
class Person {
constructor(
name: string,
age: number,
city: string
) {}
}
Jadi decorator menggunakan:
constructor(...args: any[]) {
super(...args);
}
Artinya:
Ambil semua argument yang diberikan ke constructor baru, lalu teruskan semuanya ke constructor asli.
35. Flow lengkap class replacement
Ini mungkin diagram yang paling penting dari lecture:
@Logger
│
▼
class Person
│
│ target
▼
Logger(target)
│
▼
class LoggingClass
extends target
│
├── constructor(...)
│ │
│ └── super(...args)
│
└── static company
│
▼
return LoggingClass
│
▼
LoggingClass menggantikan
Person sebagai hasil
Kemudian:
const p = new Person("John");
secara konsep menjadi:
new LoggingClass("John")
│
▼
LoggingClass constructor
│
▼
super("John")
│
▼
Original Person constructor
36. Hal yang harus benar-benar kamu pahami
Jangan hanya menghafal syntax:
return LoggingClass;
Pahami maknanya:
Class decorator dapat mengambil class asli, membuat class baru berdasarkan class tersebut, kemudian mengembalikan class baru itu sehingga class baru menjadi pengganti class asli.
Ini adalah konsep inti lecture.
37. Bedakan “memodifikasi” dan “mengganti”
Ada dua pola.
Method/accessor
Kita biasanya:
ambil descriptor
↓
ubah descriptor
↓
return descriptor
Jadi kita memodifikasi descriptor.
Class
Kita bisa:
ambil class asli
↓
buat class baru
↓
return class baru
Jadi kita mengganti class dengan class baru.
38. Analogi terakhir: CCTV di gedung
Misalnya ada gedung:
Person
Kita ingin menambahkan sistem keamanan.
Decorator:
@Logger
tidak harus membongkar gedung asli.
Dia bisa membuat gedung baru:
LoggingClass
yang:
extends Person
Artinya semua kemampuan gedung asli tetap ada.
Tetapi gedung baru punya fitur tambahan:
LoggingClass
├── semua kemampuan Person
├── logging
├── company
└── behavior tambahan lainnya
Kemudian gedung baru itulah yang digunakan.
39. Ringkasan final
Kalau lecture ini keluar dalam soal atau interview, kamu harus ingat inti berikut.
1. Decorator adalah function
Karena function bisa return value, decorator juga bisa return value.
2. Class decorator paling powerful dalam hal replacement
function Logger(target: any) {
class NewClass extends target {
}
return NewClass;
}
Class yang dikembalikan dapat menggantikan class asli.
3. Method decorator
Mendapatkan:
target
propertyKey
descriptor
dan biasanya:
modify descriptor
return descriptor
4. Accessor decorator
Mendapatkan:
target
propertyKey
descriptor
dan bisa:
modify descriptor
return descriptor
5. Property decorator
Biasanya tidak mengembalikan value yang digunakan untuk mengganti property.
6. Parameter decorator
Biasanya juga tidak mengembalikan value yang digunakan.
🧠 Mnemonic paling gampang
CLASS
→ return CLASS
→ bisa mengganti class
METHOD
→ return DESCRIPTOR
ACCESSOR
→ return DESCRIPTOR
PROPERTY
→ biasanya NO RETURN
PARAMETER
→ biasanya NO RETURN
Dan satu konsep yang menurut saya paling penting dari seluruh lecture:
CLASS DECORATOR
Original Person
│
▼
@Logger
│
▼
LoggingClass extends Person
│
▼
return LoggingClass
│
▼
Person digantikan oleh
LoggingClass
Jadi ketika kamu melihat:
@Logger
class Person {}
dan di dalam Logger ada:
return LoggingClass;
pahami sebagai:
“Class
Personyang tadinya dipakai sekarang digantikan olehLoggingClass, sedangkanLoggingClasssendiri mewarisiPerson.”
Itulah inti terbesar dari lecture ini.