Ada satu hal dalam C ++ yang telah membuat saya merasa tidak nyaman untuk waktu yang cukup lama, karena saya sejujurnya tidak tahu bagaimana melakukannya, meskipun kedengarannya sederhana:
Bagaimana cara menerapkan Metode Pabrik di C ++ dengan benar?
Sasaran: untuk memungkinkan klien mengizinkan instantiate beberapa objek menggunakan metode pabrik alih-alih konstruktor objek, tanpa konsekuensi yang tidak dapat diterima dan hit kinerja.
Dengan "Pola metode pabrik", maksud saya adalah metode pabrik statis di dalam objek atau metode yang didefinisikan dalam kelas lain, atau fungsi global. Secara umum "konsep pengalihan cara normal instance dari kelas X ke tempat lain selain konstruktor".
Biarkan saya membaca beberapa jawaban yang mungkin saya pikirkan.
0) Jangan membuat pabrik, buat konstruktor.
Ini kedengarannya bagus (dan memang sering merupakan solusi terbaik), tetapi bukan obat umum. Pertama-tama, ada kasus-kasus ketika konstruksi objek adalah tugas yang cukup kompleks untuk membenarkan ekstraksi ke kelas lain. Tetapi bahkan mengesampingkan fakta itu, bahkan untuk benda-benda sederhana menggunakan konstruktor saja seringkali tidak akan berhasil.
Contoh paling sederhana yang saya tahu adalah kelas 2-D Vector. Sangat sederhana, namun rumit. Saya ingin dapat membuatnya baik dari koordinat Cartesian dan kutub. Jelas, saya tidak bisa melakukan:
struct Vec2 {
Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!
// ...
};
Cara berpikir alami saya adalah:
struct Vec2 {
static Vec2 fromLinear(float x, float y);
static Vec2 fromPolar(float angle, float magnitude);
// ...
};
Yang, alih-alih konstruktor, mengarahkan saya ke penggunaan metode pabrik statis ... yang pada dasarnya berarti bahwa saya menerapkan pola pabrik, dalam beberapa cara ("kelas menjadi pabrik sendiri"). Ini terlihat bagus (dan akan cocok dengan kasus khusus ini), tetapi gagal dalam beberapa kasus, yang akan saya uraikan pada poin 2. Baca terus.
kasus lain: mencoba untuk membebani oleh dua typedefs buram dari beberapa API (seperti GUIDs dari domain yang tidak terkait, atau GUID dan bitfield), jenis yang secara semantis berbeda (pada teori - overload yang valid) tetapi sebenarnya berubah menjadi hal yang sama - seperti int unsigned atau batal pointer.
1) Jalan Jawa
Java memilikinya sederhana, karena kita hanya punya objek dinamis yang dialokasikan. Membuat pabrik sepele seperti:
class FooFactory {
public Foo createFooInSomeWay() {
// can be a static method as well,
// if we don't need the factory to provide its own object semantics
// and just serve as a group of methods
return new Foo(some, args);
}
}
Dalam C ++, ini diterjemahkan menjadi:
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
};
Keren? Memang sering. Tapi lalu- ini memaksa pengguna untuk hanya menggunakan alokasi dinamis. Alokasi statis adalah apa yang membuat C ++ kompleks, tetapi juga yang sering membuatnya kuat. Juga, saya percaya bahwa ada beberapa target (kata kunci: tertanam) yang tidak memungkinkan untuk alokasi dinamis. Dan itu tidak menyiratkan bahwa pengguna platform tersebut suka menulis OOP bersih.
Pokoknya, filosofi samping: Dalam kasus umum, saya tidak ingin memaksa para pengguna pabrik untuk dibatasi ke alokasi dinamis.
2) Pengembalian berdasarkan nilai
OK, jadi kita tahu bahwa 1) keren ketika kita menginginkan alokasi dinamis. Mengapa kita tidak menambahkan alokasi statis di atasnya?
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooInSomeWay() {
return Foo(some, args);
}
};
Apa? Kami tidak dapat membebani berdasarkan jenis pengembalian? Oh, tentu saja kita tidak bisa. Jadi mari kita ubah nama metode untuk mencerminkan itu. Dan ya, saya telah menulis contoh kode tidak valid di atas hanya untuk menekankan betapa saya tidak suka perlunya mengubah nama metode, misalnya karena kita tidak dapat mengimplementasikan desain pabrik agnostik bahasa dengan benar sekarang, karena kita harus mengganti nama - dan setiap pengguna kode ini perlu mengingat perbedaan implementasi dari spesifikasi.
class FooFactory {
public:
Foo* createDynamicFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooObjectInSomeWay() {
return Foo(some, args);
}
};
OKE ... begitulah. Ini jelek, karena kita perlu mengubah nama metode. Itu tidak sempurna, karena kita perlu menulis kode yang sama dua kali. Tapi begitu selesai, itu berhasil. Baik?
Ya biasanya. Tapi terkadang tidak. Saat membuat Foo, kita sebenarnya bergantung pada kompiler untuk melakukan optimasi nilai balik untuk kita, karena standar C ++ cukup baik bagi vendor kompiler untuk tidak menentukan kapan objek akan dibuat di tempat dan kapan akan disalin ketika mengembalikan sebuah objek sementara dengan nilai dalam C ++. Jadi, jika Foo mahal untuk disalin, pendekatan ini berisiko.
Dan bagaimana jika Foo sama sekali tidak bisa disalin? Baiklah, doh. ( Perhatikan bahwa dalam C ++ 17 dengan elisi salinan yang dijamin, tidak dapat disalin tidak lagi menjadi masalah untuk kode di atas )
Kesimpulan: Membuat pabrik dengan mengembalikan objek memang merupakan solusi untuk beberapa kasus (seperti vektor 2-D yang disebutkan sebelumnya), tetapi masih bukan pengganti umum untuk konstruktor.
3) Konstruksi dua fase
Hal lain yang mungkin akan muncul adalah memisahkan masalah alokasi objek dan inisialisasi. Ini biasanya menghasilkan kode seperti ini:
class Foo {
public:
Foo() {
// empty or almost empty
}
// ...
};
class FooFactory {
public:
void createFooInSomeWay(Foo& foo, some, args);
};
void clientCode() {
Foo staticFoo;
auto_ptr<Foo> dynamicFoo = new Foo();
FooFactory factory;
factory.createFooInSomeWay(&staticFoo);
factory.createFooInSomeWay(&dynamicFoo.get());
// ...
}
Orang mungkin berpikir itu bekerja seperti pesona. Satu-satunya harga yang kami bayar dalam kode kami ...
Karena saya sudah menulis semua ini dan meninggalkan ini sebagai yang terakhir, saya juga harus tidak menyukainya. :) Kenapa?
Pertama-tama ... Saya sungguh-sungguh tidak menyukai konsep konstruksi dua fase dan saya merasa bersalah ketika menggunakannya. Jika saya mendesain objek saya dengan pernyataan bahwa "jika ada, itu dalam keadaan valid", saya merasa bahwa kode saya lebih aman dan lebih rentan kesalahan. Saya suka seperti itu.
Harus membatalkan konvensi itu DAN mengubah desain objek saya hanya untuk tujuan membuat pabrik itu .. yah, berat.
Saya tahu bahwa hal di atas tidak akan meyakinkan banyak orang, jadi mari saya berikan argumen yang lebih kuat. Menggunakan konstruksi dua fase, Anda tidak dapat:
- inisialisasi
const
atau variabel anggota referensi, - meneruskan argumen ke konstruktor kelas dasar dan konstruktor objek anggota.
Dan mungkin ada beberapa kelemahan yang tidak dapat saya pikirkan saat ini, dan saya bahkan tidak merasa wajib karena poin-poin di atas sudah meyakinkan saya.
Jadi: bahkan tidak dekat dengan solusi umum yang baik untuk mengimplementasikan pabrik.
Kesimpulan:
Kami ingin memiliki cara instantiasi objek yang akan:
- memungkinkan untuk instantiasi seragam terlepas dari alokasi,
- memberikan nama yang berbeda dan bermakna untuk metode konstruksi (sehingga tidak bergantung pada kelebihan argumen),
- tidak memperkenalkan hit kinerja yang signifikan dan, lebih disukai, hit kode signifikan, terutama di sisi klien
- bersifat umum, seperti pada: mungkin untuk diperkenalkan untuk kelas apa pun.
Saya percaya saya telah membuktikan bahwa cara yang saya sebutkan tidak memenuhi persyaratan itu.
Ada petunjuk? Tolong berikan saya solusi, saya tidak ingin berpikir bahwa bahasa ini tidak akan memungkinkan saya untuk menerapkan konsep sepele seperti itu.
delete
. Metode semacam ini baik-baik saja, asalkan "didokumentasikan" (kode sumber adalah dokumentasi ;-)) sehingga penelepon memiliki kepemilikan pointer (baca: bertanggung jawab untuk menghapusnya jika perlu).unique_ptr<T>
bukanT*
.Jawaban:
Saya percaya poin ini salah. Kompleksitasnya tidak terlalu penting. Relevansinya adalah apa. Jika suatu objek dapat dibangun dalam satu langkah (tidak seperti dalam pola pembangun), konstruktor adalah tempat yang tepat untuk melakukannya. Jika Anda benar-benar membutuhkan kelas lain untuk melakukan pekerjaan itu, maka itu seharusnya kelas pembantu yang digunakan dari konstruktor.
Ada solusi mudah untuk ini:
Satu-satunya kelemahan adalah tampilannya agak bertele-tele:
Tetapi hal baiknya adalah Anda dapat langsung melihat jenis koordinat apa yang Anda gunakan, dan pada saat yang sama Anda tidak perlu khawatir tentang penyalinan. Jika Anda ingin menyalin, dan itu mahal (seperti dibuktikan dengan profil, tentu saja), Anda mungkin ingin menggunakan sesuatu seperti kelas bersama Qt untuk menghindari menyalin overhead.
Adapun jenis alokasi, alasan utama untuk menggunakan pola pabrik biasanya polimorfisme. Konstruktor tidak bisa virtual, dan bahkan jika mereka bisa, itu tidak masuk akal. Saat menggunakan alokasi statis atau tumpukan, Anda tidak dapat membuat objek dengan cara polimorfik karena kompiler perlu mengetahui ukuran pastinya. Jadi itu hanya berfungsi dengan pointer dan referensi. Dan mengembalikan referensi dari pabrik tidak berfungsi juga, karena sementara objek secara teknis dapat dihapus dengan referensi, itu bisa agak membingungkan dan rawan bug, lihat Apakah praktik mengembalikan variabel referensi C ++, jahat?sebagai contoh. Jadi pointer adalah satu-satunya yang tersisa, dan itu termasuk pointer pintar juga. Dengan kata lain, pabrik paling berguna ketika digunakan dengan alokasi dinamis, sehingga Anda dapat melakukan hal-hal seperti ini:
Dalam kasus lain, pabrik hanya membantu menyelesaikan masalah kecil seperti yang kelebihan Anda sebutkan. Akan lebih baik jika memungkinkan untuk menggunakan mereka dengan cara yang seragam, tetapi tidak ada salahnya bahwa itu tidak mungkin.
sumber
Contoh Pabrik Sederhana:
sumber
unique_ptr
dalam contoh itu tidak memiliki overhead kinerja. Mengelola sumber daya, termasuk memori, adalah salah satu keunggulan tertinggi C ++ daripada bahasa lain karena Anda dapat melakukannya tanpa penalti kinerja dan deterministik, tanpa kehilangan kendali, tetapi Anda mengatakan sebaliknya. Beberapa orang tidak suka hal-hal yang dilakukan C ++ secara implisit, seperti manajemen memori melalui smart pointer, tetapi jika yang Anda inginkan adalah segala sesuatu harus eksplisit secara wajib, gunakan C; tradeoffnya adalah pesanan yang besarnya lebih sedikit masalah. Saya pikir itu tidak adil Anda memberikan rekomendasi yang baik.boost::ptr_vector<>
sedikit lebih efisien karena mengerti ia memiliki pointer daripada mendelegasikan pekerjaan ke subkelas. TETAPI keuntungan utamaboost::ptr_vector<>
adalah bahwa ia mengekspos anggotanya dengan referensi (bukan pointer) sehingga sangat mudah digunakan dengan algoritma di perpustakaan standar.Pernahkah Anda berpikir untuk tidak menggunakan pabrik sama sekali, dan sebagai gantinya memanfaatkan sistem jenis ini? Saya dapat memikirkan dua pendekatan berbeda yang melakukan hal semacam ini:
Pilihan 1:
Yang memungkinkan Anda menulis hal-hal seperti:
Pilihan 2:
Anda dapat menggunakan "tag" seperti yang dilakukan STL dengan iterator dan semacamnya. Sebagai contoh:
Pendekatan kedua ini memungkinkan Anda menulis kode yang terlihat seperti ini:
yang juga bagus dan ekspresif sambil memungkinkan Anda memiliki prototipe unik untuk setiap konstruktor.
sumber
Anda dapat membaca solusi yang sangat baik di: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus
Solusi terbaik adalah pada "komentar dan diskusi", lihat "Tidak perlu metode Buat statis".
Dari ide ini, saya sudah melakukan pabrik. Perhatikan bahwa saya menggunakan Qt, tetapi Anda dapat mengubah QMap dan QString untuk setara std.
Penggunaan sampel:
sumber
Saya sebagian besar setuju dengan jawaban yang diterima, tetapi ada opsi C ++ 11 yang belum tercakup dalam jawaban yang ada:
Contoh:
Kemudian Anda bisa membuat objek di stack:
Sebagai sub proyek dari hal-hal lain:
Atau dialokasikan secara dinamis:
Kapan saya bisa menggunakan ini?
Jika, pada konstruktor publik, tidak mungkin untuk memberikan inisialisasi yang berarti bagi semua anggota kelas tanpa perhitungan awal, maka saya dapat mengubah konstruktor itu menjadi metode statis. Metode statis melakukan perhitungan awal, kemudian mengembalikan hasil nilai melalui konstruktor pribadi yang hanya melakukan inisialisasi anggota-bijaksana.
Saya katakan ' mungkin ' karena itu tergantung pada pendekatan mana yang memberikan kode paling jelas tanpa menjadi tidak efisien.
sumber
Loki memiliki Metode Pabrik dan Pabrik Abstrak . Keduanya didokumentasikan (luas) dalam Desain C ++ Modern , oleh Andei Alexandrescu. Metode pabrik mungkin lebih dekat dengan apa yang tampaknya setelah Anda, meskipun masih sedikit berbeda (setidaknya jika ingatanku, itu mengharuskan Anda untuk mendaftarkan suatu jenis sebelum pabrik dapat membuat objek dari jenis itu).
sumber
Function
dan manipulasi jenis dapat diganti denganstd::function
dan<type_traits>
dan sementara lambdas, threading, nilai referensi memiliki implikasi yang mungkin memerlukan beberapa penyesuaian kecil, tidak ada pengganti standar untuk lajang pabrik saat ia menggambarkannya.Saya tidak mencoba menjawab semua pertanyaan saya, karena saya percaya itu terlalu luas. Hanya beberapa catatan:
Kelas itu sebenarnya adalah Builder , bukan Factory.
Maka Anda bisa membuat pabrik Anda merangkumnya dalam smart pointer. Saya percaya dengan cara ini Anda dapat memiliki kue dan memakannya juga.
Ini juga menghilangkan masalah yang terkait dengan return-by-value.
Memang. Semua pola desain memiliki kendala dan kekurangan (khusus bahasa) mereka. Disarankan untuk menggunakannya hanya ketika mereka membantu Anda memecahkan masalah Anda, bukan untuk kepentingan mereka sendiri.
Jika Anda setelah implementasi pabrik "sempurna", well, semoga sukses.
sumber
Ini adalah solusi gaya c ++ 11 saya. parameter 'base' adalah untuk kelas dasar dari semua sub-kelas. pencipta, adalah std :: function object untuk membuat instance sub-class, mungkin mengikat ke sub-class 'fungsi anggota statis' create (some args) '. Ini mungkin tidak sempurna tetapi bekerja untuk saya. Dan ini merupakan solusi 'umum'.
Contoh penggunaan.
sumber
Pola Pabrik
Dan jika Anda kompiler tidak mendukung Pengembalian Nilai Pengembalian, parit, mungkin tidak mengandung banyak optimasi sama sekali ...
sumber
Factory
adalah generik dan mencakup banyak alasan; pabrik dapat menambahkan argumen (tergantung pada lingkungan / pengaturan) atau memberikan caching (terkait dengan Flyweight / Pools) misalnya, tetapi kasus ini hanya masuk akal dalam beberapa situasi.Saya tahu pertanyaan ini telah dijawab 3 tahun yang lalu, tetapi mungkin ini yang Anda cari.
Google telah merilis beberapa minggu yang lalu perpustakaan yang memungkinkan alokasi objek dinamis yang mudah dan fleksibel. Ini dia: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html
sumber