Penso che la ragione per cui hai problemi con quale percorso prendere è perché qui c'è un difetto di progettazione fondamentale. Hai classi anemiche qui, che è di per sé un anti-pattern OO. La logica esterna che sta attivando il tipo deve essere inserita nella famiglia di classi Product
.
L'odore del codice per questo è che, non importa in quale percorso si va, sarà necessario controllare un membro del tipo o instanceof
in altri punti del codice. L'altro odore è che dovrai costantemente interrogare un oggetto Product
per i dati e prendere decisioni basate su di esso (vedi tell-don't -Chiedere ). Ciò significa che sarà difficile modificare o aggiungere un comportamento perché sarà necessario apportare modifiche in molti e diversi luoghi piuttosto che in una classe appropriata per mantenere tale comportamento per ciascun tipo.
Modifica
L'aggiunta dei dettagli del codice mostra che c'è un po 'più di sfumatura nella tua domanda. Iniziamo osservando di cosa stiamo parlando per "tipo" di prodotto.
Tradizionalmente, ogni volta che ascoltiamo la parola "tipo", saltiamo sul concetto di oggetto di Tipo (che capitalizzerò per differenziare). Questo tipo è associato a comportamento polimorfico, ereditarietà, incapsulamento e tutti gli altri concetti orientati agli oggetti. Qui, però, non stiamo necessariamente descrivendo un tipo. Stiamo parlando di quello che sembra essere un attributo di dati chiamato tipo. Il comportamento non cambia in base al tipo, solo al valore .
Come esempio da confrontare, possiamo guardare interi. Abbiamo int
s 1
e 2
. I comportamenti tra loro sono esattamente gli stessi; solo i valori differiscono. Ciò significa che non dovremmo avere un tipo astratto per int
implementato come tipo di calcestruzzo int1
e un tipo di calcestruzzo int2
, ecc. Abbiamo bisogno di un tipo concreto per tutti int
s, e un semplice modo di interagire tra loro (come il confronto per l'ordinamento). La tua idea di un tipo di prodotto è la stessa.
Se i tuoi prodotti hanno o avranno comportamenti distinti e polimorfici, l'ereditarietà è una soluzione valida. Qui, però, il tuo tipo è solo un attributo di dati, come il prezzo e il marchio, senza alcuna differenza di comportamento. Pertanto, dovresti avere un attributo type come hai un attributo price e un attributo brand.
Idealmente, vorrai rendere il tuo codice più aperto-chiuso, quindi dovrebbe esserci un modo per farlo nel modo più elegante possibile. Possiamo creare un enum contenente i diversi tipi di prodotti esistenti, ordinati nell'ordine in cui devono essere emessi (utilizzando uno statico std :: set o simile su product
potrebbe essere più facile lavorare con, se preferisci) . L'attributo type di product
punterà a un valore enum. Nella tua classe product_manager
, invece di tre vettori nominati, dovresti avere una struttura di tipo map / dictionary per mappare da enum di tipo al rispettivo vettore, generato dagli elementi nell'enum. In questo modo, quella struttura aggiungerà automaticamente i vettori per ciascun tipo all'istanziazione. Quando aggiungi un prodotto, seleziona il vettore per aggiungerlo in base al tipo di prodotto. Quindi puoi scorrere sulla mappa, quindi su ciascun vettore per stampare i prodotti, raggruppati per tipo.
Pseudocodice:
enum product_type { electrical, electronic, mechanical }
class product {
//...
get_type() {
return this.type;
}
//...
}
class product_manager {
map<product_type, vector<product>> products;
product_manager() {
foreach(p_type in product_type) {
products.insert(p_type, new vector<product>);
}
}
//...
add_product(product p) {
products.at(p.get_type()).append(p);
}
//...
print_products() {
foreach(p_type in product_type) {
foreach(product in products.at(p_type)) {
print_product(product);
}
}
}
//...
}
Se non vuoi utilizzare il tipo in nessun altro luogo, puoi renderlo privato e rendere product_manager
un amico a product
.
Voglio anche fare un rapido punto sul codice di esempio che hai postato.
//in real application we don't know instantiation proccess, so don't know what type of product comes to us.
test::electrical_product *ep = new test::electrical_product;
test::electronic_product *etp = new test::electronic_product;
test::mechanical_product *mp = new test::mechanical_product;
// set products' members values
pm.add_product(ep);
pm.add_product(etp);
pm.add_product(mp);
pm.print_electrical_products();
pm.print_electronic_products();
pm.print_mechanical_products();
Si utilizza l'overloading del metodo con diversi tipi di parametri per determinare quale implementazione chiamare. Il problema è che, nella tua vera applicazione, tutti i prodotti sarebbero di tipo abstract_product *
. Il compilatore indirizza le chiamate al metodo in base al tipo tempo di compilazione dell'oggetto, il che significa che qualsiasi prodotto aggiunto verrà inviato a un metodo product_manager.add_product(abstract_product *)
invece dei metodi sovraccaricati, il che significa che non lo farebbe cosa intendi.