Fungsi konstruktor vs fungsi Pabrik

150

Adakah yang bisa menjelaskan perbedaan antara fungsi konstruktor dan fungsi pabrik di Javascript.

Kapan harus menggunakan salah satu dari yang lain?

Sinan
sumber

Jawaban:

149

Perbedaan dasarnya adalah bahwa fungsi konstruktor digunakan dengan newkata kunci (yang menyebabkan JavaScript secara otomatis membuat objek baru, mengatur thisdalam fungsi ke objek itu, dan mengembalikan objek):

var objFromConstructor = new ConstructorFunction();

Fungsi pabrik disebut seperti fungsi "biasa":

var objFromFactory = factoryFunction();

Tetapi untuk itu dianggap sebagai "pabrik" itu perlu mengembalikan contoh baru dari beberapa objek: Anda tidak akan menyebutnya sebagai "pabrik" fungsi jika itu hanya mengembalikan boolean atau sesuatu. Ini tidak terjadi secara otomatis seperti dengan new, tetapi memang memungkinkan lebih banyak fleksibilitas untuk beberapa kasus.

Dalam contoh yang sangat sederhana, fungsi yang dirujuk di atas mungkin terlihat seperti ini:

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

Tentu saja Anda dapat membuat fungsi pabrik jauh lebih rumit daripada contoh sederhana itu.

Salah satu keuntungan fungsi pabrik adalah ketika objek yang akan dikembalikan bisa dari beberapa jenis berbeda tergantung pada beberapa parameter.

nnnnnn
sumber
14
"(EDIT: dan ini bisa menjadi masalah karena tanpa fungsi baru akan tetap berjalan tetapi tidak seperti yang diharapkan)." Ini hanya masalah jika Anda mencoba memanggil fungsi pabrik dengan "baru" atau Anda mencoba menggunakan kata kunci "ini" untuk menetapkan instance. Jika tidak, Anda cukup membuat objek baru yang sewenang-wenang, dan mengembalikannya. Tidak ada masalah, hanya cara yang berbeda dan lebih fleksibel untuk melakukan sesuatu, dengan pelat yang lebih sedikit, dan tanpa membocorkan detail instantiasi ke dalam API.
Eric Elliott
6
Saya ingin menunjukkan bahwa contoh untuk kedua kasus (fungsi konstruktor vs fungsi pabrik) harus konsisten. Contoh untuk fungsi pabrik tidak termasuk someMethoduntuk objek yang dikembalikan oleh pabrik, dan di sanalah ia menjadi sedikit berkabut. Di dalam fungsi pabrik, jika ada begitu saja var obj = { ... , someMethod: function() {}, ... }, itu akan menyebabkan setiap objek kembali memegang salinan berbeda someMethodyang merupakan sesuatu yang mungkin tidak kita inginkan. Di situlah menggunakan newdan prototypedi dalam fungsi pabrik akan membantu.
Bharat Khatri
3
Seperti yang telah Anda sebutkan bahwa beberapa orang mencoba menggunakan fungsi pabrik hanya karena mereka tidak berniat meninggalkan bug di mana orang lupa untuk menggunakan newfungsi konstruktor; Saya pikir di situlah orang mungkin perlu melihat cara mengganti konstruktor dengan contoh fungsi pabrik dan di situlah saya pikir konsistensi dalam contoh diperlukan. Bagaimanapun, jawabannya cukup informatif. Ini hanya poin yang ingin saya sampaikan, bukan karena saya menurunkan kualitas jawaban dengan cara apa pun.
Bharat Khatri
4
Bagi saya keuntungan terbesar dari fungsi Factory adalah Anda mendapatkan enkapsulasi dan penyembunyian data yang lebih baik yang mungkin berguna dalam beberapa aplikasi. Jika tidak ada masalah dalam membuat setiap instance properti dan metode publik dan mudah dimodifikasi oleh pengguna, maka saya rasa fungsi Constructor lebih tepat kecuali jika Anda tidak menyukai kata kunci "baru" seperti yang dilakukan sebagian orang.
devius
1
@Federico - metode pabrik tidak harus mengembalikan hanya objek biasa. Mereka dapat menggunakan secara newinternal, atau menggunakan Object.create()untuk membuat objek dengan prototipe tertentu.
nnnnnn
109

Manfaat menggunakan konstruktor

  • Sebagian besar buku mengajarkan Anda untuk menggunakan konstruktor dan new

  • this merujuk ke objek baru

  • Beberapa orang suka cara var myFoo = new Foo();membaca.

Kekurangannya

  • Detail Instansiasi bocor ke API panggilan (melalui newpersyaratan), sehingga semua penelepon sangat erat dengan implementasi konstruktor. Jika Anda membutuhkan fleksibilitas tambahan dari pabrik, Anda harus memperbaiki semua penelepon (tentu saja kasus yang luar biasa, bukan aturan).

  • Lupa newadalah bug yang umum, Anda harus sangat mempertimbangkan menambahkan cek boilerplate untuk memastikan bahwa konstruktor dipanggil dengan benar ( if (!(this instanceof Foo)) { return new Foo() }). EDIT: Sejak ES6 (ES2015) Anda tidak bisa melupakan newdengan classkonstruktor, atau konstruktor akan melempar kesalahan.

  • Jika Anda melakukan instanceofpemeriksaan, itu meninggalkan ambiguitas apakah newdiperlukan atau tidak . Menurut saya, seharusnya tidak. Anda secara efektif menghubung pendek newpersyaratan, yang berarti Anda dapat menghapus kekurangan # 1. Tapi kemudian Anda baru saja mendapatkan fungsi pabrik di semua kecuali nama , dengan pelat tambahan, huruf kapital, dan thiskonteks yang kurang fleksibel .

Konstruktor melanggar Prinsip Terbuka / Tertutup

Tetapi perhatian utama saya adalah bahwa itu melanggar prinsip terbuka / tertutup. Anda mulai mengekspor konstruktor, pengguna mulai menggunakan konstruktor, kemudian di ujung jalan Anda menyadari bahwa Anda memerlukan fleksibilitas pabrik, sebagai gantinya (misalnya, untuk mengubah implementasi untuk menggunakan kumpulan objek, atau untuk instantiate di seluruh konteks eksekusi, atau untuk memiliki fleksibilitas pewarisan lebih banyak menggunakan O prototypal).

Kamu terjebak. Anda tidak dapat melakukan perubahan tanpa melanggar semua kode yang memanggil konstruktor Anda new. Anda tidak dapat beralih menggunakan kumpulan objek untuk keuntungan kinerja, misalnya.

Selain itu, menggunakan konstruktor memberi Anda tipuan instanceofyang tidak berfungsi di seluruh konteks eksekusi, dan tidak berfungsi jika prototipe konstruktor Anda diganti. Ini juga akan gagal jika Anda mulai kembali thisdari konstruktor Anda, dan kemudian beralih ke mengekspor objek sewenang-wenang, yang harus Anda lakukan untuk mengaktifkan perilaku seperti pabrik di konstruktor Anda.

Manfaat menggunakan pabrik

  • Lebih sedikit kode - tidak perlu boilerplate.

  • Anda dapat mengembalikan objek arbitrer apa pun, dan menggunakan prototipe arbitrer apa pun - memberi Anda lebih banyak fleksibilitas untuk membuat berbagai jenis objek yang mengimplementasikan API yang sama. Misalnya, pemutar media yang dapat membuat instance HTML5 dan flash player, atau pustaka acara yang dapat memancarkan acara DOM atau acara soket web. Pabrik juga dapat membuat instance objek di seluruh konteks eksekusi, mengambil keuntungan dari kumpulan objek, dan memungkinkan model pewarisan prototypal yang lebih fleksibel.

  • Anda tidak akan pernah perlu mengubah dari pabrik ke konstruktor, jadi refactoring tidak akan pernah menjadi masalah.

  • Tidak ada ambiguitas tentang penggunaan new. Jangan. (Ini akan membuat thisberperilaku buruk, lihat poin berikutnya).

  • thisberperilaku seperti biasa - sehingga Anda dapat menggunakannya untuk mengakses objek induk (misalnya, di dalam player.create(), thismengacu pada player, sama seperti metode doa lainnya akan, calldan applyjuga menugaskan kembali this, seperti yang diharapkan. Jika Anda menyimpan prototipe pada objek induk, itu bisa menjadi cara yang bagus untuk menukar fungsionalitas secara dinamis, dan memungkinkan polimorfisme yang sangat fleksibel untuk instantiasi objek Anda.

  • Tidak ada ambiguitas tentang apakah akan memanfaatkan atau tidak. Jangan. Alat serat akan mengeluh, dan kemudian Anda akan tergoda untuk mencoba menggunakannya new, dan kemudian Anda akan membatalkan manfaat yang dijelaskan di atas.

  • Beberapa orang suka caranya var myFoo = foo();atau var myFoo = foo.create();membaca.

Kekurangannya

  • newtidak berperilaku seperti yang diharapkan (lihat di atas). Solusi: jangan gunakan itu.

  • thistidak merujuk ke objek baru (sebagai gantinya, jika konstruktor dipanggil dengan notasi titik atau notasi braket persegi, misalnya foo.bar () - thismengacu pada foo- sama seperti setiap metode JavaScript lainnya - lihat manfaatnya).

Eric Elliott
sumber
2
Dalam arti apa yang Anda maksud bahwa konstruktor membuat penelepon sangat erat dengan implementasi mereka? Sejauh argumen konstruktor diperhatikan, mereka akan perlu diteruskan bahkan ke fungsi pabrik untuk menggunakannya dan memanggil konstruktor yang sesuai di dalam.
Bharat Khatri
4
Mengenai pelanggaran Open / Closed: bukankah ini semua tentang injeksi ketergantungan? Jika A membutuhkan B, apakah A memanggil B baru () atau Panggilan BFactory.create (), keduanya memperkenalkan kopling. Jika di sisi lain Anda memberikan A contoh B di root komposisi, A tidak perlu tahu sama sekali tentang bagaimana B dipakai. Saya merasa baik konstruktor maupun pabrik memiliki kegunaannya; konstruktor adalah untuk instantiasi sederhana, pabrik untuk instantiasi yang lebih kompleks. Namun dalam kedua kasus tersebut, menyuntikkan dependensi Anda adalah tindakan yang bijaksana.
Stefan Billiet
1
DI baik untuk menyuntikkan keadaan: konfigurasi, objek domain, dan sebagainya. Ini berlebihan untuk yang lainnya.
Eric Elliott
1
Keributannya adalah bahwa keharusan newmelanggar prinsip terbuka / tertutup. Lihat medium.com/javascript-scene/… untuk diskusi yang jauh lebih besar daripada komentar ini.
Eric Elliott
3
Karena fungsi apa pun dapat mengembalikan objek baru dalam JavaScript, dan cukup banyak dari mereka melakukannya tanpa newkata kunci, saya tidak percaya bahwa newkata kunci sebenarnya memberikan keterbacaan tambahan. IMO, rasanya konyol untuk melompat melalui lingkaran untuk memungkinkan penelepon mengetik lebih banyak.
Eric Elliott
39

Konstruktor mengembalikan sebuah instance dari kelas tempat Anda menyebutnya. Fungsi pabrik dapat mengembalikan apa pun. Anda akan menggunakan fungsi pabrik saat Anda harus mengembalikan nilai arbitrer atau ketika sebuah kelas memiliki proses pengaturan yang besar.

Ignacio Vazquez-Abrams
sumber
5

Contoh fungsi Konstruktor

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newmembuat objek yang di-prototipe User.prototypedan memanggil Userdengan objek yang dibuat sebagai thisnilainya.

  • new memperlakukan ekspresi argumen untuk operan sebagai opsional:

         let user = new User;

    akan menyebabkan newpanggilan Usertanpa argumen.

  • newmengembalikan objek yang dibuatnya, kecuali konstruktor mengembalikan nilai objek , yang dikembalikan sebagai gantinya. Ini adalah kasus tepi yang sebagian besar dapat diabaikan.

Pro dan kontra

Objek yang dibuat oleh fungsi konstruktor mewarisi properti dari properti konstruktor prototype, dan mengembalikan true menggunakan instanceOfoperator pada fungsi konstruktor.

Perilaku di atas dapat gagal jika Anda mengubah nilai prototypeproperti konstruktor secara dinamis setelah menggunakan konstruktor. Melakukannya jarang , dan itu tidak dapat diubah jika konstruktor dibuat menggunakan classkata kunci.

Fungsi konstruktor dapat diperluas menggunakan extendskata kunci.

Fungsi konstruktor tidak dapat kembali nullsebagai nilai kesalahan. Karena ini bukan tipe data objek, diabaikan oleh new.

Contoh fungsi Pabrik

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

Di sini fungsi pabrik dipanggil tanpa new. Fungsi ini sepenuhnya bertanggung jawab atas penggunaan langsung atau tidak langsung jika argumennya dan jenis objek yang dikembalikannya. Dalam contoh ini ia mengembalikan [Objek objek] sederhana dengan beberapa properti diatur dari argumen.

Pro dan kontra

Mudah menyembunyikan kerumitan implementasi penciptaan objek dari pemanggil. Ini sangat berguna untuk fungsi kode asli di browser.

Fungsi pabrik tidak harus selalu mengembalikan objek dengan tipe yang sama, dan bahkan dapat kembali nullsebagai indikator kesalahan.

Dalam kasus sederhana, fungsi pabrik dapat sederhana dalam struktur dan makna.

Objek yang dikembalikan pada umumnya tidak mewarisi dari prototypeproperti fungsi pabrik , dan kembali falsedari instanceOf factoryFunction.

Fungsi pabrik tidak dapat diperpanjang dengan aman menggunakan extendskata kunci karena objek yang diperluas akan mewarisi dari prototypeproperti fungsi pabrik alih-alih dari prototypeproperti konstruktor yang digunakan oleh fungsi pabrik.

traktor53
sumber
1
Ini adalah jawaban yang terlambat yang dikirim untuk menjawab pertanyaan ini pada subjek yang sama,
traktor53
bukan hanya "null" tetapi "baru" juga akan mengabaikan datatype premitive apa pun yang dikembalikan oleh fungsi konstruktor.
Vishal
2

Pabrik "selalu" lebih baik. Saat menggunakan bahasa yang berorientasi objek, maka

  1. memutuskan kontrak (metode dan apa yang akan mereka lakukan)
  2. Buat antarmuka yang mengekspos metode-metode itu (dalam javascript Anda tidak memiliki antarmuka sehingga Anda perlu menemukan beberapa cara untuk memeriksa implementasi)
  3. Buat pabrik yang mengembalikan implementasi dari setiap antarmuka yang diperlukan.

Implementasi (objek aktual yang dibuat dengan yang baru) tidak terkena pengguna pabrik / konsumen. Ini berarti bahwa pengembang pabrik dapat memperluas dan membuat implementasi baru selama dia tidak melanggar kontrak ... dan itu memungkinkan bagi konsumen pabrik untuk mendapatkan manfaat dari API baru tanpa harus mengubah kode mereka ... jika mereka menggunakan implementasi baru dan "baru", maka mereka harus pergi dan mengubah setiap baris yang menggunakan "baru" untuk menggunakan implementasi "baru" ... dengan pabrik kode mereka tidak berubah ...

Pabrik - lebih baik daripada yang lainnya - kerangka pegas sepenuhnya dibangun di sekitar ide ini.

Colin Saxton
sumber
Bagaimana cara pabrik mengatasi masalah ini karena harus mengubah setiap lini?
Nama Kode Jack
0

Pabrik adalah lapisan abstraksi, dan seperti semua abstraksi mereka memiliki kompleksitas. Ketika menemukan API berbasis pabrik mencari tahu apa itu pabrik untuk API yang diberikan dapat menjadi tantangan bagi konsumen API. Dengan konstruktor, mudah ditemukan adalah sepele.

Saat memutuskan antara ctors dan pabrik, Anda perlu memutuskan apakah kerumitan itu dibenarkan oleh manfaatnya.

Layak dicatat bahwa konstruktor Javascript dapat menjadi pabrik sewenang-wenang dengan mengembalikan sesuatu selain ini atau tidak ditentukan. Jadi di js, Anda bisa mendapatkan yang terbaik dari kedua dunia - API yang dapat ditemukan dan pengumpulan / penyatuan objek.

PeterH
sumber
5
Dalam JavaScript, biaya menggunakan konstruktor lebih tinggi daripada biaya menggunakan pabrik karena fungsi apa pun di JS dapat mengembalikan objek baru. Konstruktor menambahkan kompleksitas dengan: Membutuhkan new, Mengubah perilaku this, Mengubah nilai kembali, Menghubungkan referensi prototipe, Mengaktifkan instanceof(yang terletak dan tidak boleh digunakan untuk tujuan ini). Seolah-olah, semua itu adalah "fitur". Dalam praktiknya, mereka merusak kualitas kode Anda.
Eric Elliott
0

Untuk perbedaannya, Eric Elliott mengklarifikasi dengan sangat baik,

Tetapi untuk pertanyaan kedua:

Kapan harus menggunakan salah satu dari yang lain?

Jika Anda berasal dari latar belakang berorientasi objek, fungsi konstruktor terlihat lebih alami bagi Anda. dengan cara ini Anda jangan lupa untuk menggunakan newkata kunci.

Mostafa
sumber