Cara menerapkan pola metode pabrik di C ++ dengan benar

329

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 constatau 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.

Kos
sumber
7
@ Zak, meskipun judulnya sangat mirip, pertanyaan sebenarnya IMHO berbeda.
Péter Török
2
Bagus duplikat tetapi teks dari pertanyaan ini sangat berharga.
dmckee --- ex-moderator kitten
7
Dua tahun setelah menanyakan hal ini, saya punya beberapa poin untuk ditambahkan: 1) Pertanyaan ini relevan dengan beberapa pola desain (pabrik [abstrak], pembangun, sebut saja, saya tidak suka mempelajari taksonomi mereka). 2) Masalah aktual yang dibahas di sini adalah "bagaimana cara bersih memisahkan alokasi penyimpanan objek dari konstruksi objek?".
Kos
1
@ Dennis: hanya jika Anda tidak melakukannya 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).
Boris Dalstein
1
@Boris @Dennis Anda juga bisa membuatnya sangat eksplisit dengan mengembalikan unique_ptr<T>bukan T*.
Kos

Jawaban:

107

Pertama-tama, ada kasus-kasus ketika konstruksi objek adalah tugas yang cukup kompleks untuk membenarkan ekstraksi ke kelas lain.

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.

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

Ada solusi mudah untuk ini:

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

Satu-satunya kelemahan adalah tampilannya agak bertele-tele:

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

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:

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

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.

Sergei Tachenov
sumber
21
+1 untuk struktur Cartesian dan Polar. Biasanya terbaik untuk membuat kelas dan struct yang secara langsung mewakili data yang dimaksudkan (bukan lawan dari struct Vec umum). Pabrik Anda juga merupakan contoh yang baik, tetapi contoh Anda tidak menggambarkan siapa yang memiliki penunjuk 'a'. Jika Pabrik 'f' memilikinya, maka mungkin akan hancur ketika 'f' meninggalkan ruang lingkup, tetapi jika 'f' tidak memilikinya, penting bagi pengembang untuk mengingat untuk membebaskan memori itu atau jika tidak, kebocoran memori dapat terjadi.
David Peterson
1
Tentu saja suatu objek dapat dihapus dengan referensi! Lihat stackoverflow.com/a/752699/404734 Tentu saja menimbulkan pertanyaan apakah bijaksana untuk mengembalikan memori dinamis dengan referensi, karena masalah berpotensi menetapkan nilai kembali dengan menyalin (penelepon tentu saja juga dapat melakukan sesuatu seperti int a = * returnsAPoninterToInt () dan kemudian akan menghadapi masalah yang sama, jika memori allcoated dinamis dikembalikan, seperti untuk referensi, tetapi dalam versi pointer pengguna harus secara dereferensi secara eksplisit, bukan hanya lupa referensi secara eksplisit, menjadi salah) .
Kaiserludi
1
@Kaiserludi, poin yang bagus. Saya tidak memikirkan itu, tetapi itu masih merupakan cara "jahat" untuk melakukan sesuatu. Mengedit jawaban saya untuk mencerminkan hal itu.
Sergei Tachenov
Bagaimana dengan menciptakan kelas-kelas non-polimorfik berbeda yang tidak berubah? Apakah pola pabrik sesuai untuk digunakan dalam C ++?
daaxix
@daaxix, mengapa Anda membutuhkan pabrik untuk membuat instance dari kelas non-polimorfik? Saya tidak melihat apa kaitannya dengan ketidakberdayaan dengan semua ini.
Sergei Tachenov
49

Contoh Pabrik Sederhana:

// Factory returns object and ownership
// Caller responsible for deletion.
#include <memory>
class FactoryReleaseOwnership{
  public:
    std::unique_ptr<Foo> createFooInSomeWay(){
      return std::unique_ptr<Foo>(new Foo(some, args));
    }
};

// Factory retains object ownership
// Thus returning a reference.
#include <boost/ptr_container/ptr_vector.hpp>
class FactoryRetainOwnership{
  boost::ptr_vector<Foo>  myFoo;
  public:
    Foo& createFooInSomeWay(){
      // Must take care that factory last longer than all references.
      // Could make myFoo static so it last as long as the application.
      myFoo.push_back(new Foo(some, args));
      return myFoo.back();
    }
};
Martin York
sumber
2
@LokiAstari Karena penggunaan pointer pintar adalah cara paling sederhana untuk kehilangan kontrol atas memori. Kontrol di mana bahasa C / C ++ dikenal sebagai yang tertinggi dibandingkan dengan bahasa lain, dan dari mana mereka mendapatkan keuntungan terbesar. Tidak menyebutkan fakta bahwa pointer pintar menghasilkan overhead memori mirip dengan bahasa yang dikelola lainnya. Jika Anda menginginkan kemudahan manajemen memori otomatis, mulai pemrograman di Java atau C # tetapi jangan masukkan kekacauan itu ke C / C ++.
luke1985
45
@ lukasz1985 unique_ptrdalam 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.
TheCppZoo
1
@ Edaster: Saya tidak menjawab sebelumnya karena dia jelas-jelas troll. Tolong jangan memberi makan troll.
Martin York
17
@LokiAstari dia mungkin troll, tapi apa yang dia katakan mungkin membingungkan orang
TheCppZoo
1
@ Yuu: Ya. Tetapi: boost::ptr_vector<>sedikit lebih efisien karena mengerti ia memiliki pointer daripada mendelegasikan pekerjaan ke subkelas. TETAPI keuntungan utama boost::ptr_vector<>adalah bahwa ia mengekspos anggotanya dengan referensi (bukan pointer) sehingga sangat mudah digunakan dengan algoritma di perpustakaan standar.
Martin York
41

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:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

Yang memungkinkan Anda menulis hal-hal seperti:

Vec2 v(linear(1.0, 2.0));

Pilihan 2:

Anda dapat menggunakan "tag" seperti yang dilakukan STL dengan iterator dan semacamnya. Sebagai contoh:

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

Pendekatan kedua ini memungkinkan Anda menulis kode yang terlihat seperti ini:

Vec2 v(1.0, 2.0, linear_coord);

yang juga bagus dan ekspresif sambil memungkinkan Anda memiliki prototipe unik untuk setiap konstruktor.

Evan Teran
sumber
29

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.

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

Penggunaan sampel:

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");
mabg
sumber
17

Saya sebagian besar setuju dengan jawaban yang diterima, tetapi ada opsi C ++ 11 yang belum tercakup dalam jawaban yang ada:

  • Kembalikan hasil metode pabrik berdasarkan nilai , dan
  • Berikan konstruktor bergerak murah .

Contoh:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

Kemudian Anda bisa membuat objek di stack:

sandwich mine{sandwich::ham()};

Sebagai sub proyek dari hal-hal lain:

auto lunch = std::make_pair(sandwich::spam(), apple{});

Atau dialokasikan secara dinamis:

auto ptr = std::make_shared<sandwich>(sandwich::ham());

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.

mbrcknl
sumber
1
Saya menggunakan ini secara ekstensif ketika membungkus sumber daya OpenGL. Konstruktor salinan yang dihapus dan tugas salin yang memaksa penggunaan semantik bergerak. Saya kemudian membuat banyak metode pabrik statis untuk membuat setiap jenis sumber daya. Ini jauh lebih mudah dibaca daripada pengiriman runtime berbasis enum OpenGL yang sering memiliki banyak parameter fungsi yang berlebihan tergantung pada enum yang disahkan. Ini pola yang sangat berguna, terkejut jawaban ini tidak lebih tinggi.
Fibbles
11

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).

Jerry Coffin
sumber
1
Bahkan jika itu sudah ketinggalan zaman (yang saya perselisihkan), itu masih bisa diperbaiki. Saya masih menggunakan Pabrik berdasarkan MC ++ D dalam proyek C ++ 14 baru untuk efek yang luar biasa! Terlebih lagi, pola Factory dan Singleton mungkin merupakan bagian yang paling ketinggalan jaman. Sementara potongan-potongan seperti Loki Functiondan manipulasi jenis dapat diganti dengan std::functiondan <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.
logam
5

Saya tidak mencoba menjawab semua pertanyaan saya, karena saya percaya itu terlalu luas. Hanya beberapa catatan:

ada kasus-kasus ketika konstruksi objek adalah tugas yang cukup kompleks untuk membenarkan ekstraksi ke kelas lain.

Kelas itu sebenarnya adalah Builder , bukan Factory.

Dalam kasus umum, saya tidak ingin memaksa pengguna pabrik dibatasi ke alokasi dinamis.

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.

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.

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.

Péter Török
sumber
Terima kasih atas jawabannya! Tetapi bisakah Anda menjelaskan bagaimana menggunakan pointer pintar akan melepaskan pembatasan alokasi dinamis? Saya tidak mendapatkan bagian ini.
Kos
@ Ko, dengan smart pointer Anda dapat menyembunyikan alokasi / deallokasi objek aktual dari pengguna Anda. Mereka hanya melihat smart pointer enkapsulasi, yang bagi dunia luar berperilaku seperti objek yang dialokasikan secara statis.
Péter Török
@ Ko, tidak dalam arti yang ketat, AFAIR. Anda melewatkan objek yang akan dibungkus, yang mungkin telah Anda alokasikan secara dinamis di beberapa titik. Kemudian smart pointer mengambil kepemilikan itu dan memastikan bahwa itu benar dihancurkan ketika tidak diperlukan lagi (waktu yang diputuskan berbeda untuk berbagai jenis smart pointer).
Péter Török
3

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'.

template <class base, class... params> class factory {
public:
  factory() {}
  factory(const factory &) = delete;
  factory &operator=(const factory &) = delete;

  auto create(const std::string name, params... args) {
    auto key = your_hash_func(name.c_str(), name.size());
    return std::move(create(key, args...));
  }

  auto create(key_t key, params... args) {
    std::unique_ptr<base> obj{creators_[key](args...)};
    return obj;
  }

  void register_creator(const std::string name,
                        std::function<base *(params...)> &&creator) {
    auto key = your_hash_func(name.c_str(), name.size());
    creators_[key] = std::move(creator);
  }

protected:
  std::unordered_map<key_t, std::function<base *(params...)>> creators_;
};

Contoh penggunaan.

class base {
public:
  base(int val) : val_(val) {}

  virtual ~base() { std::cout << "base destroyed\n"; }

protected:
  int val_ = 0;
};

class foo : public base {
public:
  foo(int val) : base(val) { std::cout << "foo " << val << " \n"; }

  static foo *create(int val) { return new foo(val); }

  virtual ~foo() { std::cout << "foo destroyed\n"; }
};

class bar : public base {
public:
  bar(int val) : base(val) { std::cout << "bar " << val << "\n"; }

  static bar *create(int val) { return new bar(val); }

  virtual ~bar() { std::cout << "bar destroyed\n"; }
};

int main() {
  common::factory<base, int> factory;

  auto foo_creator = std::bind(&foo::create, std::placeholders::_1);
  auto bar_creator = std::bind(&bar::create, std::placeholders::_1);

  factory.register_creator("foo", foo_creator);
  factory.register_creator("bar", bar_creator);

  {
    auto foo_obj = std::move(factory.create("foo", 80));
    foo_obj.reset();
  }

  {
    auto bar_obj = std::move(factory.create("bar", 90));
    bar_obj.reset();
  }
}
DAG
sumber
Terlihat bagus untukku. Bagaimana Anda menerapkan pendaftaran statis (mungkin beberapa sihir makro)? Bayangkan saja kelas dasar menjadi beberapa kelas servis untuk objek. Kelas turunan menyediakan jenis layanan khusus untuk objek-objek tersebut. Dan Anda ingin secara progresif menambahkan berbagai jenis layanan dengan menambahkan kelas yang berasal dari basis untuk masing-masing jenis layanan tersebut.
St0fF
2

Pola Pabrik

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

Dan jika Anda kompiler tidak mendukung Pengembalian Nilai Pengembalian, parit, mungkin tidak mengandung banyak optimasi sama sekali ...

Matthieu M.
sumber
Bisakah ini benar-benar dianggap sebagai implementasi dari pola pabrik?
Dennis
1
@ Dennis: Sebagai kasus yang merosot, saya akan berpikir begitu. Masalahnya Factoryadalah 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.
Matthieu M.
Jika hanya mengubah kompiler akan semudah yang Anda buat :)
rozina
@rozina: :) Ini bekerja dengan baik di Linux (gcc / dentang sangat kompatibel); Saya akui Windows masih relatif tertutup, meskipun seharusnya menjadi lebih baik pada platform 64-bit (kurang paten di jalan, jika saya ingat dengan benar).
Matthieu M.
Dan kemudian Anda memiliki seluruh dunia tertanam dengan beberapa kompiler di bawah standar .. :) Saya bekerja dengan yang seperti itu yang tidak memiliki optimasi nilai balik. Saya berharap itu terjadi. Sayangnya beralih itu bukan pilihan saat ini. Semoga di masa depan itu akan diperbarui atau kami akan beralih untuk yang lain :)
rozina