Masukkan Password
39 Factory Constructor in Dart | Dart factory constructor | Dart Tutorials #38
Bagian ini memang lebih sulit daripada constructor biasa, karena factory constructor berkaitan dengan cara Dart menentukan object mana yang harus diberikan kepada pemanggil.
Saya akan jelaskan pelan-pelan, termasuk beberapa bagian transcript yang kurang tepat secara teknis.
1. Gambaran besar: apa itu factory constructor?
Secara sederhana:
Factory constructor adalah constructor yang tidak harus membuat object baru. Ia bertugas menentukan dan mengembalikan object yang akan digunakan.
Sintaksnya:
class Person {
factory Person() {
return Person._internal();
}
Person._internal();
}
Perhatikan keyword:
factory
Itulah yang membuat constructor tersebut menjadi factory constructor.
2. Bedakan dulu: generative vs factory constructor
Di video-video sebelumnya kita sudah mengenal constructor biasa.
Contoh:
class Person {
String name;
Person(this.name);
}
Constructor:
Person(this.name);
adalah generative constructor.
Kenapa?
Karena constructor tersebut benar-benar digunakan untuk menginisialisasi instance baru.
Sedangkan:
factory Person() {
return Person("Sam");
}
adalah factory constructor.
Factory tidak secara langsung menginisialisasi instance melalui initializer list seperti constructor generative.
Sebaliknya, factory dapat melakukan logika terlebih dahulu, kemudian:
return ...
mengembalikan object.
3. Analogi paling mudah
Bayangkan ada sebuah restoran.
Generative constructor
Kamu bilang:
“Tolong buatkan satu pizza.”
Restoran langsung membuat pizza baru.
Generative constructor
↓
buat object
↓
inisialisasi object
↓
object selesai
Factory constructor
Kamu bilang:
“Saya mau pizza.”
Kasir kemudian berpikir:
“Apakah sudah ada pizza yang cocok?”
Kalau ada:
kembalikan pizza yang sudah ada
Kalau belum:
buat pizza baru
kembalikan pizza tersebut
Jadi factory lebih seperti pengambil keputusan.
Factory constructor
↓
lakukan pengecekan / logika
↓
tentukan object
↓
return object
Inilah inti dari factory.
4. Factory wajib mengembalikan object
Ini salah satu karakteristik paling penting.
Contoh:
class Person {
Person._internal();
factory Person() {
return Person._internal();
}
}
Di sini:
return Person._internal();
sangat penting.
Kalau kita menulis:
factory Person() {
}
maka akan error karena factory constructor harus menghasilkan instance yang sesuai.
Jadi secara sederhana:
Factory constructor harus mengembalikan sebuah instance.
Instance tersebut bisa berupa:
- instance class itu sendiri
- instance subclass
- object yang sudah pernah dibuat
- object dari cache
- object yang dipilih berdasarkan kondisi tertentu
5. Factory tidak selalu membuat object baru
Ini justru salah satu alasan utama kenapa factory berguna.
Misalnya:
class Person {
Person._internal();
static final Person _instance = Person._internal();
factory Person() {
return _instance;
}
}
Sekarang:
Person p1 = Person();
Person p2 = Person();
p1 dan p2 bisa menunjuk ke object yang sama.
p1 ─────┐
↓
Person Object
↑
p2 ─────┘
Jadi:
Person()
tidak berarti:
“Selalu buat object baru.”
Khusus factory constructor, maknanya lebih dekat ke:
“Berikan saya instance
Personyang sesuai.”
6. Kenapa factory mirip static method?
Transcript mengatakan factory mirip dengan static method.
Ada alasannya.
Misalnya static method:
class Person {
static Person create() {
return Person._internal();
}
Person._internal();
}
Kita bisa memanggil:
Person.create();
Static method tidak membutuhkan object terlebih dahulu.
Factory juga bisa menentukan object yang akan dikembalikan tanpa membutuhkan this.
Contoh:
class Person {
Person._internal();
factory Person() {
return Person._internal();
}
}
Factory tidak bekerja berdasarkan instance this yang sudah ada.
Namun, factory constructor bukan static method. Ini hanya analogi untuk membantu memahami perilakunya.
7. Factory tidak boleh menggunakan this
Ini sangat penting.
Dalam constructor generative:
class Person {
String name;
Person(this.name);
}
kita bisa menggunakan:
this.name
karena constructor generative bekerja pada instance yang sedang dibangun.
Tetapi di factory:
class Person {
String name;
factory Person() {
// this.name = "Sam"; // ❌
return Person._internal();
}
Person._internal();
}
tidak boleh menggunakan:
this
Factory tidak memiliki akses ke instance this seperti constructor generative.
8. Kenapa factory tidak bisa menggunakan this?
Karena factory belum tentu sedang bekerja dengan object baru.
Ingat:
factory Person() {
return objectLama;
}
atau:
factory Person() {
return PersonSubclass();
}
Factory bahkan bisa mengembalikan object yang sudah ada atau object dari class lain/subclass.
Jadi tidak masuk akal kalau factory memiliki this yang merepresentasikan object baru tertentu.
9. Factory tidak boleh mengakses instance member
Misalnya:
class Person {
String name = "Sam";
void sayHello() {
print("Hello");
}
factory Person() {
// print(name); // ❌
// sayHello(); // ❌
return Person();
}
}
name adalah instance variable.
String name;
sayHello() adalah instance method.
void sayHello() {}
Factory tidak boleh mengakses keduanya secara langsung.
10. Tetapi factory boleh mengakses static member
Misalnya:
class Person {
static int count = 0;
factory Person() {
count++;
return Person._internal();
}
Person._internal();
}
Ini diperbolehkan karena:
count
adalah static member.
Static member dimiliki oleh class, bukan object tertentu.
Secara sederhana:
Instance member
↓
milik setiap object
↓
butuh this
Static member
↓
milik class
↓
tidak butuh this
Karena factory tidak punya this, static member tetap bisa digunakan.
11. Factory bisa mempunyai parameter
Sama seperti constructor biasa, factory dapat mempunyai:
Positional parameter
factory Person(String name) {
return Person._internal(name);
}
Named parameter
factory Person({required String name}) {
return Person._internal(name);
}
Kombinasi
factory Person(
String name, {
required int age,
}) {
return Person._internal(name, age);
}
Jadi factory tidak menentukan jenis parameter.
factory hanya menentukan jenis constructornya.
12. Factory bisa unnamed
Contoh:
class Person {
factory Person(String name) {
return Person._internal(name);
}
Person._internal(String name);
}
Ini adalah:
unnamed factory constructor
Pemanggilannya:
Person("Budi");
13. Factory juga bisa named
Contoh:
class Person {
factory Person.fromJson(Map<String, dynamic> json) {
return Person._internal(json["name"]);
}
Person._internal(String name);
}
Ini adalah named factory constructor:
Person.fromJson(...)
Dipanggil:
Person person = Person.fromJson({
"name": "Budi",
});
Jadi kita bisa memiliki:
factory unnamed
↓
Person(...)
factory named
↓
Person.fromJson(...)
Person.fromCache(...)
Person.fromDatabase(...)
14. Factory dan generative constructor bisa berada dalam satu class
Contoh:
class Person {
String name;
Person(this.name);
factory Person.fromGuest() {
return Person("Guest");
}
}
Di sini ada dua constructor:
Person(this.name)
→ generative constructor.
Dan:
factory Person.fromGuest()
→ factory constructor.
Factory kemudian menggunakan generative constructor:
return Person("Guest");
15. Tapi nama factory dan generative tidak boleh sama
Tidak boleh:
class Person {
Person();
factory Person() {
return Person();
}
}
Karena keduanya memiliki nama:
Person
Dart tidak menganggap keduanya sebagai dua constructor yang berbeda.
Keduanya menggunakan nama constructor yang sama.
16. Tetapi bisa punya banyak factory
Bisa:
class Person {
Person(String name);
factory Person.fromJson(...) {
// ...
}
factory Person.fromCache(...) {
// ...
}
factory Person.fromDatabase(...) {
// ...
}
}
Karena nama factory berbeda:
fromJson
fromCache
fromDatabase
Jadi ini diperbolehkan.
17. Factory tidak harus mengembalikan class yang sama persis
Ini bagian yang sangat menarik.
Misalnya:
abstract class Shape {
factory Shape(String type) {
if (type == "circle") {
return Circle();
}
return Rectangle();
}
}
Kemudian:
class Circle implements Shape {
@override
void draw() {
print("Circle");
}
}
class Rectangle implements Shape {
@override
void draw() {
print("Rectangle");
}
}
Sekarang:
Shape shape = Shape("circle");
Walaupun kita menulis:
Shape(...)
object sebenarnya bisa berupa:
Circle object
bukan object Shape biasa.
18. Ini adalah salah satu penggunaan paling penting factory
Bayangkan:
Shape("circle")
Factory melihat:
type == circle?
Kalau iya:
return Circle();
Kalau tidak:
return Rectangle();
Jadi:
Shape("circle")
│
↓
factory constructor
│
cek nilai "circle"
│
↓
Circle()
Sedangkan:
Shape("rectangle")
│
↓
factory constructor
│
↓
Rectangle()
Pemanggil tidak perlu tahu detail pemilihannya.
19. Inilah konsep Factory Pattern
Transcript menyebut Factory Design Pattern.
Secara sederhana:
Factory Pattern adalah pola desain di mana proses pemilihan object diserahkan kepada sebuah factory.
Misalnya:
Input
↓
"circle"
↓
Factory
↓
Circle object
atau:
Input
↓
"rectangle"
↓
Factory
↓
Rectangle object
Pemanggil cukup menulis:
Shape("circle");
Tidak perlu:
if (type == "circle") {
Circle();
} else {
Rectangle();
}
Logika pemilihan diletakkan di factory.
20. Factory bisa digunakan untuk Singleton
Ini contoh yang sangat terkenal.
Singleton adalah pola desain yang memastikan kita menggunakan satu instance tertentu.
Contohnya:
class Database {
Database._internal();
static final Database _instance = Database._internal();
factory Database() {
return _instance;
}
}
Kemudian:
void main() {
Database db1 = Database();
Database db2 = Database();
print(identical(db1, db2));
}
Hasil:
true
Artinya:
db1 ─────┐
↓
Database
↑
db2 ─────┘
Keduanya menunjuk ke object yang sama.
21. Kenapa menggunakan static?
Perhatikan:
static final Database _instance =
Database._internal();
Kenapa static?
Karena factory tidak boleh mengakses instance member.
Kalau kita membuat:
final Database _instance;
itu adalah instance member.
Factory tidak bisa menggunakannya secara langsung.
Dengan:
static final
instance tersebut menjadi milik class.
22. Kenapa final?
static final Database _instance =
Database._internal();
final berarti reference tersebut tidak boleh diganti setelah diinisialisasi.
Jadi kita ingin:
_instance → Database object #1
dan tidak ingin kemudian:
_instance → Database object #2
23. Kenapa constructor _internal dibuat private?
Database._internal();
Ada _ di depan internal.
Artinya constructor tersebut private terhadap library.
Tujuannya:
Kita tidak ingin kode dari luar library membuat object secara langsung menggunakan constructor internal tersebut.
Secara konsep:
Database._internal()
↑
constructor internal/private
Kemudian public API yang digunakan adalah:
Database()
yang merupakan factory.
24. Bagaimana Singleton bekerja?
Ketika pertama kali:
Database db1 = Database();
factory melakukan:
return _instance;
dan _instance sudah menunjuk ke:
Database object #1
Kemudian:
Database db2 = Database();
factory lagi-lagi:
return _instance;
Masih object yang sama.
Jadi:
Database() → _instance → Object #1
Database() → _instance → Object #1
Database() → _instance → Object #1
Bukan:
Database() → Object #1
Database() → Object #2
Database() → Object #3
25. Catatan penting tentang hashCode
Transcript menggunakan hashCode untuk menunjukkan object yang sama.
Untuk belajar dasar, contoh tersebut membantu, tetapi secara teknis jangan menyimpulkan bahwa hashCode adalah alamat memory atau bukti mutlak object yang sama.
Untuk mengecek apakah dua reference menunjuk ke object yang sama, lebih tepat menggunakan:
identical(a, b)
Contoh:
print(identical(db1, db2));
Jika:
true
berarti keduanya menunjuk ke object yang sama.
Jadi untuk Singleton, lebih baik:
identical(db1, db2)
daripada hanya mengandalkan hashCode.
26. Penggunaan factory untuk melakukan perhitungan sebelum object dibuat
Ini juga contoh yang sangat berguna.
Misalnya kita punya:
class ResetPassword {
final String username;
final String newPassword;
ResetPassword(this.username, this.newPassword);
}
Masalahnya:
username
diketahui dari user.
Tetapi:
newPassword
harus dibuat oleh program.
Misalnya:
String generateRandomPassword() {
return "A8xP92kL";
}
Kita tidak ingin user memberikan:
ResetPassword(
"budi",
"A8xP92kL",
);
karena password seharusnya dibuat program.
27. Factory bisa menyelesaikan masalah tersebut
Kita bisa membuat:
class ResetPassword {
final String username;
final String newPassword;
ResetPassword(this.username, this.newPassword);
factory ResetPassword.forUser(String username) {
final password = generateRandomPassword();
return ResetPassword(
username,
password,
);
}
}
Sekarang pemakai cukup:
ResetPassword reset =
ResetPassword.forUser("budi");
Factory melakukan:
username
↓
factory
↓
generateRandomPassword()
↓
password dibuat
↓
ResetPassword(username, password)
↓
return object
28. Kenapa tidak langsung menggunakan constructor biasa?
Karena factory memungkinkan kita melakukan logic sebelum menentukan object yang dikembalikan.
Misalnya:
factory ResetPassword.forUser(String username) {
final password = generateRandomPassword();
// bisa ada logic lain
return ResetPassword(
username,
password,
);
}
Factory menjadi semacam gerbang sebelum object diberikan kepada pemanggil.
29. Factory untuk cache
Ini juga sangat penting.
Misalnya kita ingin:
Kalau object dengan nama tertentu sudah pernah dibuat, gunakan object lama.
Kalau belum ada:
Buat object baru dan simpan.
Contohnya:
class Person {
final String name;
static final Map<String, Person> _cache = {};
Person._internal(this.name);
factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
final person = Person._internal(name);
_cache[name] = person;
return person;
}
}
30. Bagaimana cache tersebut bekerja?
Pertama:
Person p1 = Person("Mark");
Belum ada "Mark" di cache.
Maka:
cache
{}
Factory membuat:
Person #1
name = Mark
Kemudian menyimpan:
cache
{
"Mark" → Person #1
}
31. Ketika membuat Mark lagi
Sekarang:
Person p2 = Person("Mark");
Factory melihat:
"Mark" sudah ada?
Jawab:
YA
Maka factory tidak membuat object baru.
Dia langsung:
return _cache["Mark"]!;
Jadi:
p1 ─────┐
↓
Person #1
↑
p2 ─────┘
Dan:
identical(p1, p2)
akan menghasilkan:
true
32. Kalau namanya berbeda?
Misalnya:
Person p1 = Person("Mark");
Person p2 = Person("John");
Person p3 = Person("Mark");
Hasil konsepnya:
Mark
↓
Person #1
John
↓
Person #2
Mark
↓
Person #1
Jadi:
p1 == object Mark #1
p2 == object John #2
p3 == object Mark #1
p1 dan p3 mendapatkan instance yang sama.
33. Ini disebut caching
Konsep tersebut disebut cache.
Cache adalah tempat menyimpan sesuatu yang sudah pernah dibuat/diperoleh supaya nanti bisa digunakan kembali.
Secara sederhana:
Request object
↓
Ada di cache?
↙ ↘
YA TIDAK
↓ ↓
return buat baru
lama ↓
simpan
↓
return
Factory sangat cocok untuk pola seperti ini.
34. Bagian “late initialization” dari transcript perlu sedikit diluruskan
Transcript mengatakan factory digunakan untuk “late initialization of final/non-nullable variables”.
Ide besarnya benar, tetapi istilahnya agak membingungkan.
Misalnya:
class ResetPassword {
final String username;
final String newPassword;
ResetPassword(this.username, this.newPassword);
}
Kita ingin newPassword dihitung terlebih dahulu.
Factory dapat melakukan:
factory ResetPassword.forUser(String username) {
final password = generateRandomPassword();
return ResetPassword(
username,
password,
);
}
Jadi final newPassword tetap bisa diinisialisasi melalui constructor generative setelah factory selesai melakukan perhitungan.
Ini bukan berarti final diubah menjadi late.
Justru kita bisa menghindari late dengan cara tersebut.
35. Factory dan this(...)
Ada perbedaan penting dengan constructor generative.
Constructor generative bisa melakukan constructor redirection:
class Person {
Person(this.name);
Person.guest()
: this("Guest");
final String name;
}
Di sini:
this("Guest")
mengarah ke constructor lain dalam class yang sama.
Factory tidak bisa menggunakan this dengan cara tersebut.
Kalau factory ingin menggunakan constructor lain, biasanya cukup:
factory Person.guest() {
return Person("Guest");
}
Jadi:
Generative:
: this(...)
Factory:
return Person(...)
36. Factory dan super
Ini juga dibahas dalam transcript.
Constructor generative subclass bisa memanggil constructor superclass:
class Father {
Father(int age);
}
class Son extends Father {
Son() : super(50);
}
Tetapi factory constructor tidak menggunakan initializer list seperti itu.
Jadi jangan mencoba:
factory Son() : super(50); // ❌
Factory bukan tempat untuk melakukan initialization chain superclass seperti constructor generative.
37. Factory di subclass juga tidak dipanggil menggunakan super(...)
Misalnya:
class Father {
factory Father.demo() {
return Father._internal();
}
Father._internal();
}
Subclass tidak melakukan:
Son() : super.demo(); // ❌
Constructor superclass yang dipanggil melalui initializer super(...) harus merupakan generative constructor, bukan factory constructor.
38. Jadi sebenarnya apa pekerjaan factory?
Ini cara paling mudah untuk memahaminya:
Constructor generative:
“Saya akan membangun dan menginisialisasi object.”
Factory constructor:
“Saya akan menentukan object mana yang harus dikembalikan.”
Itu inti terpentingnya.
39. Perbandingan visual
GENERATIVE CONSTRUCTOR
Person("Budi")
│
↓
buat instance baru
│
↓
inisialisasi field
│
↓
Person object
Sedangkan:
FACTORY CONSTRUCTOR
Person("Budi")
│
↓
factory
│
├── object sudah ada?
│ ↓
│ return object lama
│
└── belum ada?
↓
buat object
↓
return object
Atau:
Shape("circle")
↓
factory
↓
Circle()
Jadi factory bisa menjadi pengambil keputusan.
40. 10 karakteristik penting factory constructor
Dari transcript, bisa kita rangkum menjadi berikut.
1. Menggunakan keyword factory
factory Person() {
return Person._internal();
}
2. Harus mengembalikan instance
factory Person() {
return Person._internal();
}
3. Bisa mengembalikan instance class yang sama
return Person._internal();
4. Bisa mengembalikan instance subclass
return Circle();
jika tipe return-nya memungkinkan.
5. Tidak memiliki akses ke this
factory Person() {
// this.name = "Budi"; // ❌
}
6. Tidak bisa mengakses instance member
name
sayHello()
secara langsung.
7. Bisa mengakses static member
static final Person _instance = ...;
Factory bisa menggunakan _instance.
8. Bisa mempunyai parameter
factory Person(String name)
atau:
factory Person({required String name})
9. Bisa unnamed atau named
factory Person(...)
atau:
factory Person.fromJson(...)
10. Tidak harus membuat object baru
Factory bisa mengembalikan:
object baru
object lama
object dari cache
object singleton
object subclass
41. Kapan kita menggunakan factory?
Ini bagian yang paling penting secara praktis.
Use case 1 — Factory Pattern
Ketika kita harus memilih jenis object berdasarkan kondisi.
Contoh:
Shape("circle")
bisa menghasilkan:
Circle
sedangkan:
Shape("rectangle")
menghasilkan:
Rectangle
Use case 2 — Singleton
Ketika kita ingin satu instance tertentu digunakan berulang kali.
Contoh:
Database()
selalu mengembalikan instance yang sama.
Use case 3 — Melakukan logic sebelum object dibuat
Misalnya:
factory ResetPassword.forUser(String username) {
final password = generateRandomPassword();
return ResetPassword(
username,
password,
);
}
Factory melakukan perhitungan terlebih dahulu.
Use case 4 — Cache
Kalau object sudah pernah dibuat:
return object lama
Kalau belum:
buat object baru
simpan ke cache
return object
42. Contoh paling sederhana untuk mengingat factory
Kalau constructor biasa seperti:
Person("Budi")
berarti:
“Buatkan saya Person dengan data Budi.”
Sedangkan factory:
factory Person(String name) {
// tentukan object yang tepat
return ...;
}
berarti:
“Kalau ada permintaan Person dengan data ini, tentukan dulu object mana yang seharusnya diberikan.”
43. Hubungan dengan materi constructor sebelumnya
Sekarang semua materi constructor yang sudah kamu pelajari bisa disatukan:
CONSTRUCTOR
│
├── Generative Constructor
│ │
│ ├── Unnamed
│ │ └── Person(...)
│ │
│ └── Named
│ └── Person.fromJson(...)
│
└── Factory Constructor
│
├── Unnamed
│ └── factory Person(...)
│
└── Named
└── factory Person.fromJson(...)
Bedanya yang paling fundamental:
Generative
↓
membangun/menginisialisasi instance
Factory
↓
memilih dan mengembalikan instance
44. Hal yang sering membuat pemula salah paham
❌ Salah:
Factory constructor selalu membuat object baru.
✅ Benar:
Factory tidak menjamin object baru.
❌ Salah:
Factory harus selalu memanggil constructor lain.
✅ Lebih tepat:
Factory harus mengembalikan instance, tetapi instance tersebut tidak harus berasal dari constructor lain yang ada di class yang sama. Bisa berupa object cache, singleton, subclass, dan sebagainya.
❌ Salah:
Factory adalah static method.
✅ Benar:
Factory bukan static method, tetapi punya beberapa sifat yang mirip dalam hal tidak memiliki this.
❌ Salah:
hashCodesama berarti pasti object yang sama.
✅ Benar:
Untuk mengecek identity object gunakan:
identical(a, b)
❌ Salah:
Private constructor berarti hanya bisa digunakan oleh class itu sendiri.
✅ Benar:
Dalam Dart, _ berarti private terhadap library, bukan strictly terhadap class.
45. Contoh final yang menggabungkan semuanya
class User {
final String name;
static final Map<String, User> _cache = {};
User._internal(this.name);
factory User(String name) {
// Kalau sudah ada, gunakan object lama.
if (_cache.containsKey(name)) {
return _cache[name]!;
}
// Kalau belum ada, buat object baru.
final user = User._internal(name);
// Simpan ke cache.
_cache[name] = user;
return user;
}
}
Penggunaan:
void main() {
final user1 = User("Budi");
final user2 = User("Andi");
final user3 = User("Budi");
print(user1.name);
print(user2.name);
print(user3.name);
print(identical(user1, user3));
print(identical(user1, user2));
}
Output:
Budi
Andi
Budi
true
false
Kenapa?
User("Budi")
↓
belum ada
↓
buat User #1
↓
cache["Budi"] = User #1
User("Andi")
↓
belum ada
↓
buat User #2
↓
cache["Andi"] = User #2
User("Budi")
↓
sudah ada!
↓
return User #1
Sehingga:
user1 ─────┐
↓
User #1
↑
user3 ─────┘
user2 ─────→ User #2
46. Kesimpulan paling sederhana
Kalau kamu masih merasa factory membingungkan, jangan hafalkan 10 karakteristiknya dulu. Pegang satu kalimat ini:
Generative constructor membuat/menginisialisasi object, sedangkan factory constructor menentukan object mana yang akan dikembalikan.
Kemudian ingat 4 kemampuan utama factory:
FACTORY
│
├── bisa memilih subclass
│
├── bisa mengembalikan object lama
│
├── bisa menggunakan cache / Singleton
│
└── bisa melakukan logic sebelum object dikembalikan
Contoh paling sederhana:
class Person {
Person._internal();
factory Person() {
return Person._internal();
}
}
Sedangkan contoh yang menunjukkan kekuatan sebenarnya:
factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
final person = Person._internal(name);
_cache[name] = person;
return person;
}
Di sinilah factory constructor mulai terasa berbeda dari constructor biasa: pemanggil tetap menulis Person(...), tetapi di balik layar factory bebas menentukan object mana yang sebenarnya diberikan.