Modelli di design per contenitori intelligenti

1

Supponiamo che ci siano più classi (chiamiamole Container-s ) che hanno una struttura in qualche modo simile. Sono contenitori intelligenti per alcune altre classi Foo-s .

Questi Container-s hanno:

[1] Un singolo contenitore STL privato (vettore, set, mappa) per oggetti di un'altra classe Foo

[2] Operazioni pubbliche standard per lavorare con il contenitore in termini di Foo class ( Add(FooObject) , Remove(FooObject) , IsMember(FooObject) , iter Begin() , Clear() ecc. )

[3] Operazioni pubbliche con nomi e argomenti standard specifici per la classe Foo . Ad esempio, Add(int id, int profile) può effettivamente creare un oggetto Foo dentro Container e aggiungerlo al suo contenitore STL privato

[4] Operazioni pubbliche con nomi non standard, ad esempio GetNumberOfRedFooObjects() che esistono solo per la classe specifica Foo

class SomeContainer {

private:

    std::vector<SomeFoo> __someFoos; [1]

public: 

    (about 5-30 functions)

    void Add(const SomeFoo &foo);    [2]

    void Add(int id, int profile);   [3]

    int GetNumberOfRedSomeFoos();    [4]

}

Domanda 1: quali buone scelte di design / linee guida sono disponibili per tale Container-s dato che ce ne sono molte (decine, centinaia)? Dovrei scrivere ogni Container di classe da zero? Devo implementare alcune classi di base basate su modelli per tale Container-s ?

Domanda 2: Va bene avere metodi di entrambi i tipi [2] e [3] o dovrei limitarmi a < strong> [2]

Il linguaggio è C ++, ma penso che sia una domanda agnostica più o meno linguistica. Grazie per il tuo aiuto in anticipo!

    
posta Konstantin 13.01.2017 - 21:05
fonte

2 risposte

1

Questo è un buon candidato per applicare il design basato sulla politica , come sostenuto in Design moderno C ++ di Alexandrescu. Si basa sul modello di progettazione della strategia, ma al momento della compilazione, utilizzando i modelli.

Il principio è definire il SmartContainer come modello. I suoi parametri devono essere "politiche" che specificano alcuni aspetti del comportamento.

Passaggio 1: definisci la tua classe basata sui criteri

Ecco un esempio semplificato, con SmartContainer che utilizza un primo parametro del modello per indicare il tipo di contenitore standard da utilizzare. Fortunatamente, questi parametri possono essere modelli stessi. Un secondo parametro definisce il tipo di elementi:

template <template <typename...> class C, class T>
class SmartContainer {  
    C<T> mycontainer;   
    Adder<C,T> a; 
public:  
    void add (const T& element) { ... } 
    int get_number_of_elements() { return mycontainer.size(); } 
    void print() { for (auto &x:mycontainer) 
                       cout << x<<endl;  
                   cout<<endl; }
};

Nel tuo codice chiamante puoi quindi creare un'istanza usando qualsiasi contenitore standard che desideri:

SmartContainer<vector, Item> c1;  // will use a vector of items
SmartContainer<list, Item> c2;    // will use a list of items

Passaggio 2: utilizza le classi di aiuto

Ottenere le dimensioni in questo SmartContainer è simile per tutti i tipi di contenitori previsti qui. Ma la funzione di implementazione add() potrebbe dipendere seriamente dal contenitore scelto.

Quando affronti questo tipo di problema, definisci una classe helper:

template <template <typename...> class C, class T>
class Adder {
public:
    void operator() (C<T>& a, const T& element);  
};

Il bello delle classi template è che puoi fornire specializzazioni parziali:

template <class T>  // only one parameter is still flexible 
class Adder<vector, T> {  // the first parameter is fixed with vector
public: 
   void operator() (vector<T>& a, const T& element) {
       a.push_back(element);  //ok, as expected
   } 
};

template <class T>
class Adder<list, T> {
public: 
   void operator() (list<T>& a, const T& element) {
       a.push_front(element);   // it can be totally different.
   } 
};

Ora puoi aggiungere l'implementazione della funzione generica add ():

void add (const T& element) { a(mycontainer, element); } 

Grazie alle specializzazioni parziali, ora hai un SmartContainer molto flessibile. Se si desidera utilizzare un nuovo tipo di contenitore standard, è sufficiente fornire una specializzazione parziale, seguendo la logica illustrata sopra.

Demo online

Passaggio 3: parametrizza il comportamento che dovrebbe essere flessibile

Per ogni comportamento che richiede una certa flessibilità, utilizzare un parametro di modello aggiuntivo. Nell'esempio sopra, invece di fare riferimento alla classe Adder direttamente nel modello, potresti renderlo un criterio:

template <template <typename...> class C, class T, template template <typename...> class C, class T> class A>
class SmartContainer {  
    C<T> mycontainer; 
    A<C,T> a;             // <=== additional policy 

Potresti quindi utilizzare diversi "sommatori", come ad esempio un frontatore frontale, che sarebbe utilizzabile solo per i contenitori che lo consentono.

Passaggio 4: diventa un esperto di modelli

Il passaggio successivo consiste nell'imparare a utilizzare i tratti del tipo, in modo da poter prendere in considerazione le proprietà dei parametri del modello per ottimizzare i criteri e la classe SmartContainer .

Il libro di Alexandrescu sarebbe un buon inizio. Uno dei suoi casi è una classe SmartPointer, con una strategia di archiviazione (ad esempio strategie specializzate per oggetti brevi ecc.), Una strategia di gestione della proprietà, una strategia di conversione puntatore e una strategia di controllo della coerenza.

    
risposta data 14.01.2017 - 22:30
fonte
4

Una classe base template è la soluzione migliore. In questo modo è possibile evitare il codice ridondante specifico per il caso 2 (metodi contenitore standard) pur continuando a supportare il caso 3 nelle classi derivate.

Per quanto riguarda l'eventuale necessità di aggiungere "operazioni pubbliche con nomi e argomenti standard specifici alla classe Foo" (caso 3), dipende in realtà dall'utilizzo di queste classi. Questi possono essere considerati metodi di convenienza, quindi il codice potrebbe facilmente andare altrove.

    
risposta data 13.01.2017 - 21:55
fonte

Leggi altre domande sui tag