Scegli solo una delle numerose eredità

6

Potrei sembrare un idiota totale con una simile domanda, ma non è venuto fuori nulla quando l'ho cercato, quindi immagino perché no. Ecco il mio problema:

Sto facendo il lavoro di UML per un gioco in C ++, che mostrerà animali e razze.
Innanzitutto, ho una classe astratta Entity e tre classi ereditate Human , Dog e Cat .
Ora diciamo che voglio aggiungere un nuovo tipo di specie, i Droidi per esempio, in un modo in cui ci sono tre nuove classi: DroidHuman , DroidDog e DroidCat . Ecco tre nuove classi da creare per una nuova gara, e se voglio aggiungere la gente del Vuoto, ci saranno altre tre classi da creare.

Invece di fare questo, preferisco avere una sorta di modello di progettazione che mi permetta di mantenere le tre classi originali e solo queste: Human , Dog e Cat .
Un cane normale e un cane droide sarebbero entrambi istanze della classe Dog che eredita da Entity . Ci sarebbe anche una super classe Normal e una super classe Droid . La differenza tra un cane normale e un cane droide sarebbe che ereditano da EITH Normal O Droid .

So che sto chiedendo l'impossibile, ma ho immaginato che ci sarebbe stato un modo simile per farlo che potrei non essere a conoscenza.
Ho inizialmente pensato al modello di fabbrica, ma non posso pensare in alcun modo che possa risolvere il mio problema.
L'unico altro modo in cui ho trovato per evitare questo immenso casino è aggiungere un attributo come boolean isDroid = true; per ogni nuova razza. Ma non sono contento di modificare il codice esistente per adattarlo alle nuove cose.

Non posso essere l'unico ad aver affrontato questo problema, vero?
Qualsiasi aiuto è apprezzato. Molte grazie.

    
posta qreon 17.08.2016 - 03:37
fonte

5 risposte

4

Probabilmente userò il modello di strategia per le classi Normal vs. Droid. In C ++ questo è tipicamente implementato come parametro template:

class Normal {};

class Droid {};

template <class T>
class Entity {
    virtual void ~Entity() {}
};

template <class T>
class Human : public Entity {
};

template <class T>
class Dog : public Entity {
};

template <class T>
class Cat : public Entity {
};

Ora puoi creare un'istanza di (Cat|Dog|Human)<Droid | Normal> . Essenzialmente si ha una matrice rettangolare, in cui si deve scrivere una classe per ogni riga e un'altra per ogni colonna, e si può istanziare l'una sull'altra per produrre una nuova classe per l'intersezione di ogni riga con ciascuna colonna.

Scrivi classi M + N modelli e ottieni M * N possibili istanziazioni.

    
risposta data 17.08.2016 - 07:27
fonte
5

Vorrei utilizzare il modello decoratore per evitare l'esponenziazione delle classi

class Entity
{
    public:
        virtual void walk() = 0;
};

class Human: public Entity
{
    public:
        void walk()
        {
            //one human step
        }
};

class Dog: public Entity
{
    public:
        void walk()
        {
            //one dog step
        }
};

class Droid: public Entity
{
    private:
        Entity* origin;

    public:
        Droid(Entity* origin)
        {
            this->origin = origin;
        }

        void walk()
        {
            //initialize engine in order to be able to move

            origin->walk(); //walk as a normal one
        }
};

Uso

Entity* human = new Human();
Entity* dog = new Dog();
Entity* humanDroid = new Droid(human);
Entity* dogDroid = new Droid(dog);
    
risposta data 17.08.2016 - 07:36
fonte
1

Se vuoi mantenere le stesse classi per i droidi e le normali, significa che condivideranno l'interfaccia. Tutto dipende quindi da come il comportamento di un droide rispetto a un normale dovrebbe essere diverso:

  • Il decoratore proposto da Spotted è una buona alternativa se le tue entità condividono la stessa interfaccia (ad esempio walk() ). Tuttavia, non sarebbe adatto alle tue necessità se diverse entità avessero interfacce diverse (ad esempio talk() per human bark() per i cani)
  • Il modello di strategia basato su modello proposto da Jerry Coffin è un'ottima alternativa se il comportamento di Dog / Cat / Human si basa su alcune primitive definite dal parametro template. Ma devi sapere che Dog<Droid> non è la stessa classe di Dog<Normal> . È solo un modo più elegante per generare le classi.
  • Se mantenere la stessa classe è importante per te, potresti in alternativa optare per un modello di strategia di runtime (ad esempio, anziché i parametri del modello di tempo di compilazione, utilizzare un parametro di runtime all'istanziazione). Ma sappi che in questo caso perdi i controlli in fase di compilazione e alcune ottimizzazioni del compilatore.

Ma come nella programmazione dei giochi, vale la pena menzionare l'architettura dei componenti esposta da Mike McShaffry in Game Coding Complete . Chiama entità attori, e gli attori sono piatti.

La sua posizione è che l'eredità è lì solo per aggiungere caratteristiche / comportamenti agli attori. Per mantenere la flessibilità al massimo, vede quindi gli attori come una semplice raccolta di componenti che portano le caratteristiche per composizione (componenti di rappresentazione grafica, componenti comportamentali, ecc.).

Gli attori vengono quindi creati da una fabbrica che assembla i componenti (ad esempio per un droide umano, aggiunge i soliti componenti umani, ma aggiunge un filtro vocale sintetico al posto del lettore vocale umano liscio e aggiunge una cella di potenza con capacità energetica, e la strategia di combattimento droide).

Questo facilita la creazione o la combinazione di nuove specie. Ma rende anche la struttura più complessa in quanto combina diversi modelli di design contemporaneamente.

    
risposta data 20.08.2016 - 13:19
fonte
1

Dato che stai usando C ++, puoi anche utilizzare l'ereditarietà multipla a tuo vantaggio:

class Entity {...};

class Human : virtual public Entity {...};
class Dog : virtual public Entity {...};
class Cat : virtual public Entity {...};

class Normal : virtual public Entity {...};
class Droid : virtual public Entity {...};
class Void : virtual public Entity {...};

class DroidHuman : public Human, public Droid {...};

Se la maggior parte delle classi completamente specificate non ha bisogno di alcun codice speciale, puoi persino utilizzare un modello in quel punto:

template<class Species, class Race>
class GameEntity : public Species, public Race {...};

Con ciò puoi facilmente istanziare un cane vuoto dove ti serve:

GameEntity<Void, Dog> myPet;

Ma, naturalmente, lo schema del decoratore è più flessibile (vedi la risposta di Spotted).

    
risposta data 20.08.2016 - 18:15
fonte
0

Se capisco bene il gioco, un gatto droide non è un tipo speciale di gatto. È uno stato di un gatto normale, come sarebbe un gatto zombi. Non una nuova istanza, ma qualcosa in cui potrebbe trasformarsi la tua normale cat application esistente. E questo è probabilmente il motivo per cui l'ereditarietà e / o il polimorfismo non sembrano giusti.

Ciò non in alcun modo invalida (più) l'ereditarietà o supporta l'idea che "la composizione è migliore". Più ereditarietà non è proprio adatta al problema che descrivi.

Quindi, penso che sarebbe meglio implementare modalità droid per le entità esistenti. La prima cosa potrebbe essere una proprietà IsDroid. Non ho idea di come il droide possa avere un impatto sul comportamento, immagino che un gatto droide userebbe un diverso set di parametri di configurazione.

    
risposta data 20.08.2016 - 14:54
fonte

Leggi altre domande sui tag