Domanda

Ho il seguente codice per " factory " implementazione del modello di progettazione.

class Pen{
public:
     virtual void Draw() = 0;
};

class RedPen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

class BluePen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

auto_ptr<Pen> createPen(const std::string color){
     if(color == "red")
         return auto_ptr<Pen>(new RedPen);
     else if(color == "blue")
         return auto_ptr<Pen>(new BluePen);
}

Ma ho sentito che si può fare meglio usando " modelli C ++ " ;. Qualcuno può aiutare come è fatto e come l'approccio template è migliore di questo?

Qualsiasi pensiero

È stato utile?

Soluzione

Nell'esempio che hai pubblicato, per me né un approccio di fabbrica né di modello ha senso. La mia soluzione coinvolge un membro di dati nella classe Pen.

class Pen {
public:
    Pen() : m_color(0,0,0,0) /* the default colour is black */
    {            
    }

    Pen(const Color& c) : m_color(c)
    {
    }

    Pen(const Pen& other) : m_color(other.color())
    {
    }

    virtual void Draw()
    {
        cout << "Drawing with a pen of color " << m_color.hex();
    }
    void setColor(const Color& c) { m_color = c; }
    const Color& color() const { return m_color; }
private:
    Color m_color;
};

class Color {
public:
    Color(int r, int g, int b, int a = 0) :
        m_red(r), m_green(g), m_blue(other.blue()), m_alpha(a)  
    {
    }

    Color(const Color& other) : 
        m_red(other.red()), m_green(other.green()), 
        m_blue(other.blue()), m_alpha(other.alpha())
    {
    }

    int red() const { return m_red; }
    int green() const  { return m_green; }
    int blue() const { return m_blue; }
    int alpha() const { return m_alpha; }

    std::string hex() const
    {
        std::ostringstream os;
        char buf[3];
        os << "#";

        sprintf(buf, "%2X", red());
        os << buf;

        sprintf(buf, "%2X", green());
        os << buf;

        sprintf(buf, "%2X", blue());
        os << buf;

        sprintf(buf, "%2X", alpha());
        os << buf;

        return os.str();
    }

private:
    int m_red;
    int m_green;
    int m_blue;
    int m_alpha;
}

Ovviamente, la classe di colore dovrebbe essere adattata all'API di disegno che usi - e forse essere molto più avanzata di questa (spazi di colore diversi, ecc.)

Perché non i modelli?

Il motivo per cui non ha senso usare i modelli è che (presumibilmente) l'unica differenza tra le diverse operazioni di disegno è la variabile colore. Quindi, usando i template (o dichiarando manualmente diverse classi, come hai fatto tu), duplicherai un codice simile. Ciò renderà il tuo programma più ampio e rallenterà.

Quindi, la funzione di disegno dovrebbe prendere il colore come argomento o (come nel mio esempio) avere il colore come membro dei dati della classe.

Altri suggerimenti

Un altro modo è registrare dinamicamente una funzione creatore su un oggetto Factory dinamico.

BluePen *create_BluePen() { return new BluePen; }
static bool BluePen_creator_registered = 
                       Factory::instance()->registerCreator("BluePen", 
                                                            create_BluePen);

Un effetto interessante nel fare questo è che la variabile bool statica BluePen-creator-register verrà impostata prima che main () inizi così rendendo automatizzata la registrazione.

Queste linee sono talvolta create attraverso macro ordinarie, cioè come

#define METAIMPL( _name ) \
_name *create_ ## _name() { return new _name; } \
static bool _name ## _creator_registered = \
                        Factory::instance()->registerCreator(# _name, \
                                                             create_ ## _name)

... e usato vicino al costruttore

METAIMPL( BluePen ); // auto registers to the Factory

BluePen::BluePen() : Pen() {
   // something
}

Quindi l'attività della Fabbrica sarà quella di memorizzare e cercare queste funzioni creatore . Lascio il resto come l'esercizio ;) cioè l'uso di una macro METADECL

Se vuoi maggiori informazioni, vedi qui nel capitolo 4.1 Meta Information che include anche un metodo per espandere includendo possibilità per ispettore caratteristiche

Ho imparato questo usando ET ++ che era un progetto per il porting della vecchia MacApp su C ++ e X11. Nel tentativo di Eric Gamma ecc ha iniziato a pensare a Design Patterns

E ... (7 maggio 2011) Finalmente è arrivato il momento di dare un esempio a github
https : //github.com/epatel/cpp-factory

La tua fabbrica va bene. Prendo il BluePen e così via erano solo esempi di nomi di classe. Puoi utilizzare i modelli se sono soddisfatte le seguenti condizioni:

  

Quando sai al momento della compilazione (cioè quando scrivi il codice) che vuoi che venga restituito un tipo specifico, usa un modello. Altrimenti, non puoi.

Ciò significa che, nel codice, puoi farlo:

template<typename PenType>
auto_ptr<Pen> createPen(){
    return auto_ptr<Pen>(new PenType);
}

Avendolo installato, puoi usarlo come

...
auto_ptr<Pen> p = createPen<BluePen>();
...

Ma quell'argomento template, il BluePen , non può essere una variabile impostata su un tipo in fase di esecuzione. Nel tuo esempio, passi una stringa, che ovviamente può essere impostata in fase di esecuzione. Quindi, quando leggi che puoi usare i modelli C ++, quella raccomandazione è solo in modo condizionale vero - allora, quando la tua decisione, quale penna creare, è già fatta al momento della compilazione. Se tale condizione soddisfa, la soluzione modello è la cosa giusta da fare. Non ti costerà nulla in fase di esecuzione e sarà esattamente ciò di cui hai bisogno.

Dichiarando speciali classi vuote per i colori, puoi fare tutto usando i template. Ciò richiede che ogni scelta di colore sia resa disponibile al momento della compilazione. In questo modo, eviti di usare una classe base con metodi virtuali.

struct Red{};
struct Blue{};

template < typename Color >
class Pen{};

template <>
class Pen< Red >
{
     void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

template <>
class Pen< Blue >
{
     void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

template < typename Color >
std::auto_ptr< Pen< Color > > createPen()
{
     return auto_ptr< Pen< Color > >(new Pen< Color >());
}

Potresti scrivere una classe factory oggetti generica come classe template (o usare quella ben descritta in questo articolo di gamedev.net ).

In questo modo, se hai più di una factory nel tuo codice, è meno difficile definire ciascuna factory.

Come supplemento alla mia altra risposta, solo per discutere del modello Factory rispetto all'uso del modello:

Il motivo principale (e più semplice) per utilizzare i modelli è che il codice è identico in ogni caso, ad eccezione dei tipi di dati su cui funziona. Esempi qui sono i contenitori STL. Sarebbe possibile scrivere una funzione di fabbrica createVector (" string ") e digitare manualmente ogni contenitore - ma questo è chiaramente non ottimale.

Anche quando il codice differisce, non solo i tipi di dati, è possibile utilizzare le specializzazioni dei modelli, ma in molti casi una funzione di fabbrica avrebbe più senso.

Ad esempio, si consideri una libreria di astrazione del database. Sarebbe possibile utilizzare le specializzazioni dei modelli, in modo che la libreria possa essere utilizzata come " db :: driver " ;. Ma questo ti costringerebbe a digitare il tipo di database ovunque nel codice (rendendo la libreria un po 'inutile in primo luogo ...) o eseguendo un caso con un'interfaccia di tipo db :: driver class.

In questo esempio, è più intuitivo dire db :: get_driver (odbc) e riportare il cast di classe appropriato al tipo di interfaccia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top