Va bene avere una classe genitore che non rappresenta un'entità e non ha una relazione "Is-A" con le sue classi figlie?

1

Voglio avere una matrice che possa contenere elementi di molti tipi di dati di classi, e ognuna di queste classi ha un metodo chiamato printInfo() .

Il modo in cui posso farlo è avere una classe genitore chiamata SomeParentClass che ha un metodo virtuale chiamato printInfo() , e quindi vorrei che i tipi di dati delle classi che possono essere presenti nell'array ereditino da questa classe e sovrascrivi il metodo printInfo() , quindi posso creare un array di tipo SomeParentClass* .

C'è qualcosa di sbagliato in questo approccio?

Chiedo perché ho letto che una classe dovrebbe rappresentare un'entità (ad esempio: BankAccount , Car , House , Student , ecc.), ma SomeParentClass non rappresenta un'entità. Ho anche letto che la relazione tra una classe genitore e una classe figlia è una relazione "Is-A", ma le classi che erediteranno da SomeParentClass non sono SomeParentClass (nello stesso modo in cui Car è a Vehicle per esempio).

    
posta Christopher 21.07.2018 - 06:59
fonte

6 risposte

6

The way I can do that is by having a parent class called SomeParentClass that have a virtual method called printInfo(), and then I would make the classes data types that can be present in the array inherit from this class and override the printInfo() method, and then I can create an array of type SomeParentClass*.

Is there anything wrong with this approach?

Non c'è assolutamente nulla di sbagliato in tutto questo (anche se consiglio vivamente di evitare puntatori e array, e invece scegliere qualcosa come std::deque con std::unique_ptr o std::shared_ptr in modo che tu non stia scrivendo il codice che ha bisogno occuparsi della gestione della memoria).

Da una prospettiva "OO" indipendente dalla lingua, questo è esattamente il tipo di scenario con cui ci si aspetta che l'ereditarietà venga usata.

I'm asking because I have read that a class should represent an entity (for example: BankAccount, Car, House, Student, etc.), but SomeParentClass doesn't represent an entity. I have also read that the relationship between a parent class and a child class is an "Is-A" relationship, but the classes that will inherit from SomeParentClass are not SomeParentClass (in the same way that a Car is a Vehicle for example).

Posso pensare ad alcuni scenari in cui questo potrebbe essere un consiglio ragionevole, anche se posso pensare a molti altri scenari in cui questo consiglio è fuorviante o sbagliato. Ci sono stati troppi libri, blog e tutorial scritti nel corso degli anni che tentano di spiegare OO in termini come questo, e il più delle volte sono scritti da persone la cui comprensione di OO è fuorviata (almeno dal punto di vista che non corrisponde alla definizione originale di Alan Kay del termine "programmazione orientata agli oggetti", che non ha nulla a che fare con le entità).

In primo luogo, non c'è assolutamente alcun requisito che una classe dovrebbe rappresentare un'entità di dati. Attenersi a questo tipo di mantra come una regola difficile può facilmente condurre a un modo molto stretto e fuorviante di pensare alla struttura del codice in cui qualsiasi tipo di comportamento che può essere correlato a una particolare entità di dati appartiene a quell'entità, risultando pesantemente classi gonfiate contenenti dozzine di metodi che hanno una relazione minima o nulla tra loro. Questa mentalità spesso provoca l'anti-pattern "Oggetto di Dio" come descritto qui: link

Il design della classe è completamente diverso da Entity Modeling: se si confondono 'entità' con 'classi', si rischia anche di perdere la separazione logica tra diversi livelli e moduli nel codice.

Ad esempio, in un'architettura con livelli / livelli, è possibile avere strutture di entità semplici che non contengono alcun comportamento e vengono semplicemente utilizzate per supportare le operazioni CRUD in / da un archivio di persistenza. In genere, le classi contenenti la propria logica aziendale principale non dovrebbero avere alcuna conoscenza della persistenza. Se si segue la vista che le classi dovrebbero essere entità, allora si possono finire con le classi 'business logic' che contengono anche molti metodi non correlati alla logica di business come Read o Save , a quel punto il codice perde qualsiasi tipo di struttura chiara o separazione.

La chiave per la programmazione orientata agli oggetti sta dietro la progettazione di classi attorno alla separazione logica e al raggruppamento del relativo comportamento (cioè funzioni / metodi).

Vale a dire, in molte (ma non tutte) circostanze, limitare l'uso delle classi solo per rappresentare entità non fornisce una struttura utile al tuo codice e talvolta può essere controproducente.

Se una classe contiene metodi che non hanno relazioni tra loro e che soddisfano requisiti completamente indipendenti, allora è un'indicazione che hai una classe che fa troppe cose - ma questo è esattamente ciò che è probabile finire con se si trattano le entità come classi. La maggior parte delle volte il modo per arrivare a un codice pulito e modulare è quello di dividere i metodi non correlati tra diverse classi che probabilmente non si adattano perfettamente a nessuna entità nel dominio del problema.

D'altra parte, la modellazione Entity riguarda esclusivamente l'identificazione e il raggruppamento di attributi di dati logicamente correlati e le relazioni logiche tra i dati raggruppati. In molti casi, i criteri che si possono usare quando si decide se creare un raggruppamento di attributi in un'entità (come 3NF / BCNF) saranno completamente diversi dai criteri quando si decide di creare un raggruppamento di comportamenti in una classe; per questo motivo, è meglio evitare di sfumare le linee tra il concetto di un'entità dati e il concetto di classe.

    
risposta data 21.07.2018 - 11:17
fonte
4

Ti suggerisco di creare l'array, di un tipo di interfaccia (classe astratta in c ++). Basta dichiarare un'interfaccia con i metodi che si desidera implementare (definire un contratto) invece di farli ereditare da una classe base. In questo modo stai dicendo che le classi che implementano IPrintInterface soddisfano quel contratto.

Se vuoi che gli elementi nella matrice abbiano il metodo printInfo() questo sarà il mio approccio.

L'ereditarietà è la relazione più strong tra le classi e la tua domanda suggerisce che le classi dell'array non hanno bisogno di tale "forza"

    
risposta data 21.07.2018 - 08:05
fonte
1

Dato che C ++ non ha una classe base di Object, come Java e altre lingue, penso che tu sia sulla strada giusta. A patto che tu dia alla classe genitore un nome adatto che descriva di cosa tratta la tua matrice.

Ma sono un po 'perplesso sul fatto che tu abbia bisogno di quel tipo di array. Mi fa pensare che ci sia qualcosa che non va con l'architettura o che stai combattendo con il framework. Solo un pensiero a cui hai la risposta.

    
risposta data 21.07.2018 - 07:29
fonte
0

Non si ha (necessariamente) la relazione is-a e i tipi di oggetti nell'array potrebbero essere diversi. Questo è un flag per l'utilizzo di interfacce piuttosto che di ereditarietà come suggerisce Badulake.

Per i tipi che rispettano la relazione is-a, potresti ancora implementarla usando un metodo virtuale ma il codice che attraversa gli elementi dell'array li considererebbe come oggetti IP stampabili (IPrintable sarebbe un nome adatto per la tua interfaccia).

Ma il tuo framework di classe potrebbe già avere un metodo virtuale ToString () integrato, supportato da ogni oggetto. Se lo fa, questo potrebbe anche essere adatto per questo scopo perché non importa quanto diversi siano gli oggetti nel tuo array, ci sarà sempre un ToString () che puoi sostituire. In questo caso viene soddisfatto il requisito is-a: tutti gli oggetti sono "stringifiables".

    
risposta data 21.07.2018 - 11:25
fonte
0

...the classes that will inherit from SomeParentClass are not SomeParentClass(in the same way that a Car is a Vehicle for example).

Sì. Ma non è sbagliato avere una tale relazione di ereditarietà. Puoi vederlo in molte librerie e framework. Ad esempio in Java le due implementazioni dell'elenco popolare ArrayList e LinkedList discendono approssimativamente da AbstractList .

L'unica cosa da tenere a mente è che non dovremmo fare riferimento alle istanze dell'elenco concreto ( ArrayList o LinkedList ) come AbstractList . Li faremo riferimento come List per preoccupazione polimorfa o, ArrayList o LinkedList per preoccupazione strutturale dei dati. Tali superclassi offrono praticità quando implementiamo due o più classi simili e, facendo riferimento a tali classi, tali superclassi sono per lo più inutili.

Un altro esempio estremo di questo tipo è vuoto implementato EventListener s. Un'interfaccia listener di eventi può avere due o tre metodi astratti, come onClick() , onLongClick() e onDoubleClick() . Il framework può anche fornire un'implementazione vuota di default per loro. Questa implementazione sarà utile quando siamo interessati solo a uno o due eventi.

    
risposta data 22.07.2018 - 20:55
fonte
0

Niente di sbagliato in questo approccio! Quindi, dovresti chiedere se la stampa è simile nella maggior parte delle classi figlie, e farlo nella classe base.

class Print {                             // renamed for clarity.
    char const *    m_sInfo;
public:      Print(CS s) : m_sInfo(s)  {}
    virtual ~Print() {}
    virtual void print()     const     { cout << "Info: " << m_sInfo; } // universal
    virtual void printData() const = 0;   // abstract method
    virtual void printMisinformation() {} // called less often, thus using longer name
};
class Car : public Print {
    Engine       m_Engine;
public:         Car() : Print("Car"), m_Engine("V8") { }
    virtual    ~Car() {}
    virtual void printData() const     { m_Engine.print(); } // non-universal print
};
    
risposta data 12.08.2018 - 01:57
fonte

Leggi altre domande sui tag