C ++ 'da fabrika yöntemi kalıbı doğru şekilde nasıl uygulanır


329

C ++ 'da beni oldukça uzun bir süre rahatsız hissettiren tek bir şey var, çünkü dürüstçe basit görünsem bile nasıl yapılacağını bilmiyorum:

Fabrika yöntemini C ++ 'da doğru bir şekilde nasıl uygularım?

Amaç: istemcinin, kabul edilemez sonuçlar ve bir performans isabeti olmadan, nesnenin yapıcıları yerine fabrika yöntemlerini kullanarak bir nesneyi başlatmasını mümkün kılmak.

"Fabrika yöntemi kalıbı" ile, bir nesnenin içindeki statik fabrika yöntemlerini veya başka bir sınıfta tanımlanan yöntemleri veya genel işlevleri kastediyorum. Sadece genel olarak "X sınıfının normal örnekleme yolunu kurucudan başka bir yere yönlendirme kavramı".

Düşündüğüm bazı olası cevapları gözden geçirmeme izin verin.


0) fabrikalar yapmayın, yapıcılar yapmak.

Bu kulağa hoş geliyor (ve çoğu zaman en iyi çözüm), ancak genel bir çözüm değildir. Her şeyden önce, nesne inşasının başka bir sınıfa çıkarılmasını haklı çıkaracak bir görev kompleksi olduğu durumlar vardır. Ancak bu gerçeği bir kenara bırakmak, sadece kurucuları kullanan basit nesneler için bile çoğu zaman yapmaz.

Bildiğim en basit örnek 2-D Vector sınıfıdır. Çok basit, ama zor. Hem Kartezyen hem de kutupsal koordinatlardan inşa edebilmek istiyorum. Açıkçası yapamam:

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

Benim doğal düşünme şeklim şu:

struct Vec2 {
    static Vec2 fromLinear(float x, float y);
    static Vec2 fromPolar(float angle, float magnitude);
    // ...
};

Bu, inşaatçılar yerine beni statik fabrika yöntemlerinin kullanımına götürüyor ... bu da esasen fabrika modelini bir şekilde uyguladığım anlamına geliyor ("sınıf kendi fabrikası oluyor"). Bu güzel görünüyor (ve bu özel duruma uygun olacaktır), ancak bazı durumlarda başarısız olur. 2. noktada açıklayacağım.

başka bir durum: bazı API'ların (ilgisiz alanların GUID'leri veya bir GUID ve bit alanı gibi) iki opak tip tanımı ile aşırı yüklemeye çalışmak, anlamsal olarak tamamen farklı türler (yani - teoride - geçerli aşırı yükler) aynı şey - imzasız ints veya geçersiz işaretçiler gibi.


1) Java Yolu

Yalnızca dinamik olarak ayrılmış nesnelerimiz olduğu için Java'nın basit bir özelliği var. Bir fabrika yapmak şu kadar önemsiz:

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);
    }
}

C ++ 'da bu şu anlama gelir:

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
};

Güzel? Genellikle. Ancak bu, kullanıcıyı yalnızca dinamik ayırmayı kullanmaya zorlar. Statik ayırma, C ++ 'ı karmaşık yapan şeydir, ancak aynı zamanda onu güçlü kılan şeydir. Ayrıca, dinamik ayırmaya izin vermeyen bazı hedefler (anahtar kelime: katıştırılmış) olduğuna inanıyorum. Ve bu, bu platformların kullanıcılarının temiz OOP yazmaktan hoşlandığı anlamına gelmez.

Her neyse, felsefe bir yana: Genel durumda, fabrika kullanıcılarını dinamik ayırmaya zorlanmaya zorlamak istemiyorum.


2) Değere göre dönüş

Tamam, bu yüzden dinamik ayırma istediğimizde 1) iyi olduğunu biliyoruz. Bunun üstüne neden statik ayırma eklemiyoruz?

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooInSomeWay() {
        return Foo(some, args);
    }
};

Ne? Dönüş tipine göre aşırı yüklenemiyoruz? Tabii ki yapamayız. Öyleyse bunu yansıtacak yöntem adlarını değiştirelim. Ve evet, yukarıdaki yöntem kodunu değiştirme ihtiyacını ne kadar sevmediğimi vurgulamak için yukarıdaki geçersiz kod örneğini yazdım, örneğin, adları değiştirmek zorunda olduğumuz için, şu anda bir dil agnostik fabrika tasarımını düzgün bir şekilde uygulayamıyoruz - ve bu kodun her kullanıcısı, uygulamanın şartname ile arasındaki farkı hatırlamalıdır.

class FooFactory {
public:
    Foo* createDynamicFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooObjectInSomeWay() {
        return Foo(some, args);
    }
};

Tamam ... işte burada. Yöntem adını değiştirmemiz gerektiğinden çirkin. Aynı kodu iki kez yazmamız gerektiğinden kusurlu. Ama bir kez yapıldığında, işe yarıyor. Sağ?

Genellikle. Ama bazen değil. Foo oluştururken, derleyicinin bizim için dönüş değeri optimizasyonunu yapmasına bağlıyız, çünkü C ++ standardı, derleyici satıcıları için nesnenin ne zaman yerinde oluşturulacağını ve ne zaman döndürülürken kopyalanacağını belirtmeyecek kadar yardımseverdir. C ++ 'da değere göre geçici nesne. Foo'nun kopyalanması pahalıysa, bu yaklaşım risklidir.

Ya Foo hiç kopyalanamazsa? Şey, doh. ( Garantili kopya seçimine sahip C ++ 17'de, kopyalanamaz olmanın yukarıdaki kod için artık sorun olmadığını unutmayın )

Sonuç: Bir nesneyi döndürerek bir fabrika yapmak gerçekten de bazı durumlarda (daha önce sözü edilen 2-D vektörü gibi) bir çözümdür, ancak yine de inşaatçılar için genel bir yedek değildir.


3) İki fazlı yapı

Birinin muhtemelen ortaya çıkaracağı başka bir şey, nesne tahsisi ve başlatılması konusunu ayırmaktır. Bu genellikle şöyle bir kodla sonuçlanır:

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());
    // ...
}

Bir cazibe gibi çalıştığını düşünebilirsiniz. Kodumuzda ödediğimiz tek fiyat ...

Tüm bunları yazdığım ve son olarak bıraktığım için, ben de sevmemeliyim. :) Neden?

Her şeyden önce ... İki aşamalı inşaat kavramını içtenlikle sevmiyorum ve kullandığımda kendimi suçlu hissediyorum. "Varsa, geçerli durumda" iddiası ile benim nesneleri tasarlarsanız, benim kod daha güvenli ve daha az hata eğilimli olduğunu hissediyorum. Bu şekilde beğendim.

Bu sözleşmeyi bırakmak ve sadece fabrikayı yapmak amacıyla nesnemin tasarımını değiştirmek zorunda kaldım .. şey, hantal.

Yukarıdakilerin pek çok insanı ikna edemeyeceğini biliyorum, bu yüzden biraz daha sağlam argümanlar vereyim. İki fazlı yapı kullanarak şunları yapamazsınız:

  • başlangıç constveya referans değişkenleri,
  • temel sınıf yapıcılarına ve üye nesne yapıcılarına argümanlar iletir.

Ve muhtemelen şu anda düşünemediğim bazı dezavantajlar olabilir ve yukarıdaki mermi noktaları beni zaten ikna ettiğinden özellikle mecbur hissetmiyorum bile.

Yani: bir fabrikayı uygulamak için iyi bir genel çözüme bile yakın değil.


Sonuç:

Aşağıdakileri yapacak bir nesne somutlaştırma yoluna sahip olmak istiyoruz:

  • dağıtımdan bağımsız olarak tekdüze örneklemeye izin vermek,
  • inşaat yöntemlerine farklı, anlamlı isimler verin (böylelikle by-argüman aşırı yüklenmesine dayanmayan),
  • özellikle müşteri tarafında önemli bir performans isabeti ve tercihen önemli bir kod bloat isabeti sunmamak,
  • Genel olarak, herhangi bir sınıf için tanıtılabilir.

Bahsettiğim yolların bu gereksinimleri karşılamadığını kanıtladığımı düşünüyorum.

İpucu var mı? Lütfen bana bir çözüm sağlayın, bu dilin bu kadar önemsiz bir konsepti doğru bir şekilde uygulamama izin vermeyeceğini düşünmek istemiyorum.


7
@Zac, başlık çok benzer olmasına rağmen gerçek sorular IMHO'dan farklı.
Péter Török

2
İyi kopya ama bu sorunun metni tek başına değerlidir.
dmckee --- eski moderatör yavru kedi

7
Bunu sorduktan iki yıl sonra, eklemem gereken bazı noktalar var: 1) Bu soru birkaç tasarım deseni ([soyut] fabrika, inşaatçı, adınız, taksonomilerini araştırmaktan hoşlanmıyorum) ile ilgilidir. 2) Burada tartışılan asıl mesele "nesne depolama tahsisinin nesne yapımından nasıl temiz bir şekilde ayrıştırılmasıdır?".
Kos

1
@Dennis: sadece yapmazsan delete. Bu tür yöntemler, çağıranın işaretçinin mülkiyetini aldığı (belgelendiğinde (kaynak kodu dokümantasyon ;-)) olduğu sürece (okundu: uygun olduğunda silmekten sorumludur) mükemmel bir şekilde uygundur.
Boris Dalstein

1
@Boris @Dennis unique_ptr<T>yerine bir döndürerek çok açık yapabilirsiniz T*.
Kos

Yanıtlar:


107

Her şeyden önce, nesne inşasının başka bir sınıfa çıkarılmasını haklı çıkaracak bir görev kompleksi olduğu durumlar vardır.

Bu noktanın yanlış olduğuna inanıyorum. Karmaşıklık gerçekten önemli değil. Alaka düzeyi ne? Bir nesne bir adımda oluşturulabilirse (oluşturucu deseninde olduğu gibi değil), yapıcı bunu yapmak için doğru yerdir. İşi gerçekleştirmek için gerçekten başka bir sınıfa ihtiyacınız varsa, yine de kurucudan kullanılan bir yardımcı sınıf olmalıdır.

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

Bunun için kolay bir çözüm var:

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

Tek dezavantajı, biraz ayrıntılı görünmesidir:

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

Ancak iyi olan şey, hangi koordinat türünü kullandığınızı hemen görebilmeniz ve aynı zamanda kopyalama konusunda endişelenmenize gerek olmamasıdır. Kopyalamak istiyorsanız ve pahalıysa (profilleme ile kanıtlandığı gibi), yükü kopyalamaktan kaçınmak için Qt'un paylaşılan sınıfları gibi bir şey kullanmak isteyebilirsiniz .

Tahsis tipine gelince, fabrika modelini kullanmanın ana nedeni genellikle polimorfizmdir. Yapıcılar sanal olamazlar ve yapabilseler bile pek mantıklı olmazdı. Statik veya yığın tahsisini kullanırken, derleyicinin tam boyutu bilmesi gerektiğinden, nesneleri polimorfik bir şekilde oluşturamazsınız. Bu yüzden sadece işaretçiler ve referanslarla çalışır. Bir nesne teknik olarak ise çünkü bir fabrikadan bir başvuru dönen, çok çalışmıyor olabilir referans olarak silinecek, bu bkz kafa karıştırıcı ve hata eğilimli ziyade olabilir bir C ++ referans değişkeni, kötülük dönen uygulaması var mı?Örneğin. Dolayısıyla, geriye kalan tek şey işaretçilerdir ve buna akıllı işaretçiler de dahildir. Başka bir deyişle, fabrikalar dinamik ayırma ile kullanıldığında en kullanışlıdır, bu nedenle böyle şeyler yapabilirsiniz:

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

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

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

Diğer durumlarda, fabrikalar bahsettiğiniz aşırı yüklenmeler gibi küçük sorunları çözmeye yardımcı olur. Onları düzgün bir şekilde kullanmak mümkün olsaydı güzel olurdu, ama muhtemelen imkansız olduğu çok fazla zarar vermez.


21
Kartezyen ve Kutupsal yapılar için +1. Genelde en iyisi, amaçlanan verileri doğrudan temsil eden sınıflar ve yapılar oluşturmaktır (genel bir Vec yapısının aksine). Fabrikanız da iyi bir örnektir, ancak örneğiniz 'a' işaretçisine kimin sahip olduğunu göstermez. Eğer 'f' Fabrikası ona sahipse, o zaman 'f' kapsamı terk ettiğinde muhtemelen imha edilir, ancak 'f' ona sahip değilse, geliştiricinin bu hafızayı boşaltmayı veya başka bir hafıza sızıntısını meydana gelir.
David Peterson

1
Tabii ki bir nesne referans ile silinebilir! Bkz. Stackoverflow.com/a/752699/404734 Elbette, dönüş değerinin kopya ile potansiyel olarak atanması sorunu nedeniyle, dinamik belleği referans olarak döndürmek akıllıca ise soruyu gündeme getirir (arayan elbette bir şeyler de yapabilir) gibi int a = * returnAPoninterToInt () ve daha sonra aynı sorunla karşılaşırsa, dinamik olarak allcoated bellek referanslar gibi döndürülürse, ancak işaretçi sürümünde kullanıcı sadece açıkça referans vermeyi unutmak yerine yanlış bir şekilde dereference kullanmak zorunda kalır) .
Kaiserludi

1
@Kaiserludi, güzel bir nokta. Bunu düşünmedim, ama yine de bir şeyler yapmanın "kötü" bir yolu. Cevabımı bunu yansıtacak şekilde düzenledi.
Sergei Tachenov

Değişmez polimorfik olmayan farklı sınıflar yaratmaya ne dersiniz? Bu durumda bir fabrika deseni C ++ 'da kullanmak için uygun mu?
daaxix

@daaxix, neden polimorfik olmayan bir sınıf örneği oluşturmak için bir fabrikaya ihtiyacınız var? Değişmezliğin bununla ne ilgisi olduğunu anlamıyorum.
Sergei Tachenov

49

Basit Fabrika Örneği:

// 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();
    }
};

2
@LokiAstari Çünkü akıllı işaretçilerin kullanımı hafıza üzerindeki kontrolü kaybetmenin en basit yoludur. Hangi C / C ++ dillerinin diğer dillere kıyasla üstün olduğu ve en büyük avantajı sağladığı kontrol. Akıllı işaretçilerin diğer yönetilen dillere benzer bellek yükü oluşturduğundan bahsetmiyorum bile. Otomatik bellek yönetiminin rahatlığını istiyorsanız, Java veya C # ile programlamaya başlayın, ancak bu karışıklığı C / C ++ 'a koymayın.
luke1985

45
@ lukasz1985 unique_ptrbu örnekte performans ek yükü yoktur. Bellek de dahil olmak üzere kaynakları yönetmek, C ++ 'ın diğer herhangi bir dile göre en üstün avantajlarından biridir, çünkü performansı kaybetmeden ve belirleyici olarak, kontrolü kaybetmeden yapabilirsiniz, ancak tam tersini söylersiniz. Bazı insanlar C ++ 'ın akıllı işaretçilerle bellek yönetimi gibi örtük olarak yaptığı şeylerden hoşlanmazlar, ancak istediğiniz her şeyin zorunlu olarak açık olması için C'yi kullanın; dengesizlik daha büyük problemlerin büyüklüğüdür. İyi bir öneriyi oylamak haksızlık olduğunu düşünüyorum.
TheCppZoo

1
@EdMaster: Açıkçası trol olduğu için daha önce cevap vermedim. Lütfen trolü beslemeyin.
Martin York

17
@LokiAstari o bir trol olabilir, ama söyledikleri insanları şaşırtabilir
TheCppZoo

1
@yau: Evet. Ancak: boost::ptr_vector<>çalışmayı bir alt sınıfa devretmek yerine işaretçinin sahibi olduğunu anladığı için biraz daha verimlidir. ANCAK ana avantajı boost::ptr_vector<>, üyelerini referans (işaretçi değil) ile ortaya çıkarmasıdır, bu nedenle standart kütüphanedeki algoritmalarla kullanımı gerçekten kolaydır.
Martin York

41

Hiç fabrika kullanmamayı ve bunun yerine tip sistemini iyi kullanmayı düşünmeyi düşündünüz mü? Bu tür şeyleri yapan iki farklı yaklaşım düşünebilirim:

Seçenek 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) { /* ... */ }
};

Hangi gibi şeyler yazmanıza izin verir:

Vec2 v(linear(1.0, 2.0));

Seçenek 2:

STL'nin yineleyicilerde olduğu gibi "etiketler" kullanabilirsiniz. Örneğin:

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 &) { /* ... */ }
};

Bu ikinci yaklaşım, şuna benzer bir kod yazmanıza olanak tanır:

Vec2 v(1.0, 2.0, linear_coord);

bu da her kurucu için benzersiz prototiplere sahip olmanızı sağlarken güzel ve etkileyici.


29

Çok iyi bir çözümü şu adreste okuyabilirsiniz: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

En iyi çözüm "yorumlar ve tartışmalar" üzerinedir, bkz. "Statik oluşturma yöntemlerine gerek yoktur".

Bu fikirden bir fabrika yaptım. Qt kullanıyorum, ancak std eşdeğerleri için QMap ve QString'i değiştirebilirsiniz.

#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

Örnek kullanım:

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");

17

Çoğunlukla kabul edilen cevaba katılıyorum, ancak mevcut cevaplarda yer almayan bir C ++ 11 seçeneği var:

  • Fabrika yöntem sonuçlarını değere göre döndürün ve
  • Ucuz bir hareket yapıcı sağlayın .

Misal:

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

Sonra yığın üzerinde nesneler oluşturabilirsiniz:

sandwich mine{sandwich::ham()};

Başka şeylerin alt nesneleri olarak:

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

Veya dinamik olarak ayrılmış:

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

Bunu ne zaman kullanabilirim?

Genel bir kurucuda, bazı ön hesaplamalar yapmadan tüm sınıf üyeleri için anlamlı başlatıcılar vermek mümkün değilse, o kurucuyu statik bir yönteme dönüştürebilirim. Statik yöntem, ön hesaplamaları gerçekleştirir, daha sonra özel bir kurucu aracılığıyla yalnızca üye olarak başlatma başlatan bir değer sonucu döndürür.

' Olabilir ' diyorum çünkü gereksiz yere verimsiz olmadan hangi yaklaşımın en net kodu verdiğine bağlı.


1
Bunu OpenGL kaynaklarını sararken yoğun olarak kullandım. Silme kopya kurucuları ve taşıma semantiği kullanımını zorlayan kopya atama. Daha sonra, her bir kaynak türünü oluşturmak için bir grup statik fabrika yöntemi oluşturdum. Bu, OpenGL'nin enum tabanlı çalışma zamanı gönderisinden çok daha okunabilirdi ve bu genellikle geçirilen numaralandırmaya bağlı olarak bir sürü yedek fonksiyon parametresine sahipti. Çok faydalı bir model, bu cevabın daha yüksek olmaması şaşırdı.
Fibbles

11

Loki'nin bir Fabrika Yöntemi ve bir Soyut Fabrikası vardır . Her ikisi de Andei Alexandrescu tarafından Modern C ++ Tasarımında belgelenmiştir. Fabrika yöntemi muhtemelen daha sonra göründüğünüze daha yakındır, ancak yine de biraz farklıdır (en azından bellek hizmet veriyorsa, fabrika bu türden nesneler oluşturabilmeniz için bir tür kaydetmenizi gerektirir).


1
Tarihli olsa bile (ki ben itiraz ediyorum), hala mükemmel servis edilebilir. Hala büyük bir etki için yeni bir C ++ 14 projesinde MC ++ D dayalı bir Fabrika kullanın! Ayrıca, Fabrika ve Singleton modelleri muhtemelen en az eskimiş parçalar. Loki gibi parçalar Functionve tip manipülasyonlar ile değiştirilebiliyor std::functionve <type_traits>lambdas, iplik geçirme, rvalue referanslarının bazı küçük ayarlamalar gerektirebilecek sonuçları olsa da, fabrikaların tanımladığı gibi tektonlar için standart bir yedek yoktur.
metal

5

Çok geniş olduğuna inandığım için tüm sorularıma cevap vermeye çalışmıyorum. Sadece birkaç not:

nesne inşasının başka bir sınıfa çıkarılmasını haklı kılacak bir görev kompleksi olduğu durumlar vardır.

Bu sınıf aslında bir Fabrikadan çok bir İnşaatçıdır .

Genel durumda, fabrika kullanıcılarını dinamik ayırmaya zorlanmaya zorlamak istemiyorum.

Daha sonra fabrikanızın akıllı bir işaretçide kapsüllenmesini sağlayabilirsiniz. Bu şekilde kekinizi yiyip yiyebileceğinize inanıyorum.

Bu aynı zamanda geri dönüş değeri ile ilgili sorunları da ortadan kaldırır.

Sonuç: Bir nesneyi döndürerek bir fabrika yapmak gerçekten de bazı durumlarda (daha önce sözü edilen 2-D vektörü gibi) bir çözümdür, ancak yine de inşaatçılar için genel bir yedek değildir.

Aslında. Tüm tasarım modellerinin (dile özgü) kısıtlamaları ve dezavantajları vardır. Bunları sadece kendi sorunlarınız için değil, probleminizi çözmenize yardımcı olduklarında kullanmanız önerilir.

"Mükemmel" fabrika uygulamasından sonra iseniz, iyi, iyi şanslar.


Cevap için teşekkürler! Ancak akıllı bir işaretçi kullanmanın dinamik ayırma kısıtlamasını nasıl serbest bırakacağını açıklayabilir misiniz? Bu kısmı tam olarak anlamadım.
Kos

@Kos, akıllı işaretçilerle gerçek nesnenin tahsisini / serbest bırakılmasını kullanıcılarınızdan gizleyebilirsiniz. Sadece dış dünyaya statik olarak tahsis edilmiş bir nesne gibi davranan kapsülleyici akıllı işaretçiyi görürler.
Péter Török

@Kos, tam anlamıyla değil, AFAIR. Muhtemelen bir noktada dinamik olarak ayırdığınız kaydırılacak nesneyi iletirsiniz. Daha sonra akıllı işaretçi onun mülkiyetini alır ve artık gerekmediğinde (zamanın farklı akıllı işaretçiler için farklı karar verildiği zaman) doğru şekilde yok edilmesini sağlar.
Péter Török

3

Bu benim c ++ 11 tarzı çözüm. 'base' parametresi tüm alt sınıfların temel sınıfı içindir. oluşturucuları, alt sınıf örnekleri oluşturmak için std :: function nesneleridir, alt sınıf 'statik üye işlevi' create (some args) 'için bir bağlayıcı olabilir. Bu mükemmel olmayabilir ama benim için çalışıyor. Ve bu biraz 'genel' bir çözümdür.

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_;
};

Kullanımla ilgili bir örnek.

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();
  }
}

Bana iyi geliyor. Statik kaydı nasıl uygularsınız (belki bazı makro büyüler)? Sadece temel sınıfın nesneler için bir servis sınıfı olduğunu hayal edin. Türetilmiş sınıflar, bu nesnelere özel bir hizmet sunar. Ve bu tür hizmetlerin her biri için tabandan türetilmiş bir sınıf ekleyerek aşamalı olarak farklı hizmet türleri eklemek istersiniz.
St0fF

2

Fabrika Desen

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

Ve eğer derleyici Dönüş Değeri Optimizasyonunu desteklemiyorsa, hendekleyin, muhtemelen çok fazla optimizasyon içermez ...


Bu gerçekten fabrika modelinin bir uygulaması olarak düşünülebilir mi?
Dennis

1
@Dennis: Dejenere bir vaka olarak ben de öyle düşünürdüm. Sorun Factory, oldukça genel ve çok fazla zemin kaplamasıdır; bir fabrika, örneğin (ortama / kuruluma bağlı olarak) argümanlar ekleyebilir veya bazı önbellekleme (Flyweight / Pools ile ilgili) sağlayabilir, ancak bu durumlar yalnızca bazı durumlarda anlamlıdır.
Matthieu M.

Sadece derleyicinin değiştirilmesi sizin
sesinizi çıkarmanız

@rozina: :) Linux'ta iyi çalışır (gcc / clang oldukça uyumludur); Windows'un hala nispeten kapalı olduğunu kabul ediyorum, ancak 64 bit platformda daha iyi olmalı (doğru hatırlıyorsam, daha az patent).
Matthieu

Ve sonra bazı subpar derleyiciler ile tüm gömülü dünya var .. :) Ben dönüş değeri optimizasyonu olmayan böyle bir ile çalışıyorum. Keşke olsaydı. Ne yazık ki şu anda geçiş yapmak bir seçenek değil. Umarım gelecekte güncellenir ya da başka sth için bir anahtar yapacağız :)
rozina

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.