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.