Archived

This topic is now archived and is closed to further replies.

Object Oriented Programming

This topic is 5155 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am currently working on mastering the principles of OOP, and I understand some of it, but there are a few things that are bothering me. I think some of my questions would be best demonstrated by example. I will use an RPG style thing I was working on because I believe it demonstrates my questions the best.
#include <iostream>
using namespace std;

class Character
{
	protected:
		int health;
		int strength;

	public:
		virtual void DisplayStats() {}
};

class Fighter : public Character
{
	public:
		virtual void DisplayStats()
		{
			cout << "Your strength is:" << strength;
			cout << "Your health is:" << health;
		}
};

class Mage : public Character
{
	protected:
		int magicPoints;
	public:
		virtual void DisplayStats()
		{
			cout << "Your strength is:" << strength;
			cout << "Your health is:" << health;
			cout << "Your Magic Point total is :" << magicPoints;
		}
		void CastSpell(int spellcost)
		{
			magicPoints -= spellcost;
			cout << "You cast a spell!";
			//Do some stuff and cast a spell

		}
};


//Here come some of the bits I am not sure about....

int main()
{
	Mage mage;
	Fighter fighter;
	short choice(0);
	cout << "Please select a class for your character"  << endl
		 << "\t[1]Fighter"								<< endl
		 << "\t[2]Mage"									<< endl;
	cin  >> choice;
	Character *pPlayer;
	if(choice == 1)
		pPlayer = &fighter;
	else
		pPlayer = &mage;

	pPlayer->DisplayStats(); // This works fine, and gives the proper 

							//result regardless of the user's choice


	pPlayer->CastSpell() //This does not work, 

			     //since the player may or may not be a mage!

	return 0;
}

There are a number of things that bother me about making this main function. First is that there is always one object created that is not used, second is that there is no way for one to tell whether the person is a fighter or a mage so I can't know whether to give the person the ability to cast spells or not, and third is that even if I could tell what class the character had chosen, my pointer is limited to the functions that are members of the original cCharacter class! I thought about making a member variable called m_class which would be set with a constructor and tell me what class the person is, but, correct me if I'm wrong, does that not defeat the purpose of polymorphism in the first place? I'd appreciate any help that you guys could give me with this. Thanks. [edited by - sAlFace on November 5, 2003 8:40:51 PM]

Share this post


Link to post
Share on other sites
since pPlayer is a Character pointer, it dont know about the mage class, and it should not either, (because of your example, how could it know if pPlayer is a Mage or not) it cant! (in java you can, but its no good), the idea is to have all the general functions in the Character class, and only use these simple and general functions in your special classes!

so that every time you deal with a charakter, you dont need to know what type it is! , but you should not fill the Character class up with 100ds of functions! instead, try to redesig it so that you have a sort of update/run function, witch does all the stuf that character type should do...

if you have action key, that should do different things on different types, add a act_on_action_key function, and not different functions for all the different classes! it is also nice sometimes to have virtual test functions, like: can_i_cast_spell, so you can ask if you should, before you do stuff!

Share this post


Link to post
Share on other sites
You should only have very generic functions in your base classes. However, there is a way to access specific functions via dynamic_cast:

class Base
{
void doSomething()=0;
}
class Thing:public Base
{
void doSomething(){}
void doSomethingElse() {}
}

Base* b=new Thing;

Thing* t=dynamic_cast<Thing*>(b);

if(!t)
cout<<"Error! b is not of type Thing*!";
else
t->doSomethingElse();

It''s usually a good idea to dynamically allocate the objects so you don''t have to allocate one of every type.

Share this post


Link to post
Share on other sites
quote:
Original post by uncutno
(in java you can, but its no good)


In your feable opinion.

Other than uncutno''s ignorance of Java, he is correct. You can have a function that takes an action key and then returns false if that character is not allowed to do that action.




First make it work,
then make it fast.

--Brian Kernighan

The problems of this world cannot possibly be solved by skeptics or cynics whose horizons are limited by the obvious realities. We need men and women who can dream of things that never were. - John Fitzgerald Kennedy(35th US President)

Do not interrupt your enemy when he is making a mistake. - Napolean Bonaparte

Share this post


Link to post
Share on other sites
You assign the CCharacter* the address of the object. Why not just use the ''new'' keyword?
Also, instead of telling your class what to do, why not allow your class to decide it on its own?

try this:


#include <iostream>
using namespace std;


#define ATTACK 1
#define CAST 2

/*
create the base class from which all others will derive from
*/

class Char
{
public:
virtual void Update(int inInput)=0; //pure virtual function Update(...). must be overridden in derived classes

virtual void DisplayStats()
{
cout<<"Your Health is "<<myHealth;
cout<<"\nYour Strength is "<<myStrength;
}

protected:
int myHealth, myStrength;
};

class Warrior : public Char
{
public:
Warrior()
{
myHealth = 100;
myStrength = 150;
}
void Update(int inInput) //overridden from base class

{
switch(inInput)
{
case ATTACK:
{
cout<<"Warrior Attack!\n\n";
break;
}
}
}
//nothing here, since no warrior code is given

};

class Mage : public Char
{
public:
Mage()
{
myHealth = 75;
myStrength = 100;
myMagicPoints = 50;
myCurrentSpell = Level1;
};
void DisplayStats() //overridden from base class

{
cout<<"Your Health is "<<myHealth;
cout<<"\nYour Strength is "<<myStrength;
cout<<"\nYour Magic Point total is "<<myMagicPoints;
}
void Update(int inInput) //overridden from base class

{
switch(inInput)
{
case CAST:
{
CastSpell();
break;
}
}
}
enum SPELL {Level0, Level1, Level2};
protected:
void CastSpell()
{
if(myMagicPoints)//check is we have magic available for us to use!

{
cout<<"Spell Cast!\n";
myMagicPoints -= myCurrentSpell; //because myCurrentSpell is really an integer

}
}
SPELL myCurrentSpell;
int myMagicPoints;
};

int main()
{
Char* Player; //because the Player is-a character

int answer = 0;
cout<<"\n\nPlease choose character class:\n[0] Warrior\n[1] Mage\n";
cin>>answer;
cout<<endl; //for looks :P

switch (answer)
{
case 0:
{
Player = new Warrior; //assign the player poiner the address of the newly created warrior

break;
}
case 1:
{
Player = new Mage; //assign the player pointer the address of the newly created mage

break;
}
}

do
{
cout<<"\n\nChoose Action\n 1 Attack\n 2 Cast Spell\n 3 Quit\n";
cin>>answer;
Player->Update(answer); //here is polymorphism. because each class derives from the

//Abstract base class Char, they all have this function.

//since Update(...) is a virtual function, the appropriate

//method from the correct class is called. The pointer "morphs" to the correct type.

//you could add many more types to this "game", without changing this line of code,

// as long as they derive from class::Char.

Player->DisplayStats(); //Same thing with this function...


}while(answer !=3);
delete Player; //we delete the memory we allocated!

return 0;
};




EOF

Share this post


Link to post
Share on other sites

class Base
{
void doSomething()=0;
}
class Thing:public Base
{
void doSomething(){}
void doSomethingElse() {}
}

Base* b=new Thing;
Thing* t=dynamic_cast<Thing*>(b);
if(!t)
cout<<"Error! b is not of type Thing*!";
else
t->doSomethingElse();


Using dynamic_cast or some other form of RTTI to switch on behavior is an indication that your inheritance hierarchy is broken. The C++ FAQ talks about this at length, as do other sources on the net (search for "liskov substitution principle"):

http://www.parashift.com/c++-faq-lite/proper-inheritance.html

--
Dave Mikesell Software & Consulting

[edited by - dmikesell on November 6, 2003 7:46:33 AM]

Share this post


Link to post
Share on other sites
This should work...

class Character
{
public:
virtual void DisplayStats()
{
cout << "Your strength is:" << strength;
cout << "Your health is:" << health;
}
virtual void CastSpell(int spellcost)
{
cout << "You cannot cast a spell!";
}
protected:
int health;
int strength;

};

class Fighter : public Character
{
public:
};

class Mage : public Character
{
public:
virtual void DisplayStats()
{
CCharacter::DisplayStats();

cout << "Your Magic Point total is :" << magicPoints;
}
void CastSpell(int spellcost)
{
magicPoints -= spellcost;
cout << "You cast a spell!";
//Do some stuff and cast a spell

}
protected:
int magicPoints;
};

Share this post


Link to post
Share on other sites
there is nothing wrong with putting a type-id in your base class.
some options:
place all possible actions in your base class interface, such as cast_spell, and override them in child classes when appropriate.
use an identifier from your base class to determine what actions are not available.
use overridden generic functions, such as act(), getInput(), etc, to gain access to derived classes.

Share this post


Link to post
Share on other sites
quote:
there is nothing wrong with putting a type-id in your base class.
some options:
place all possible actions in your base class interface, such as cast_spell, and override them in child classes when appropriate.
use an identifier from your base class to determine what actions are not available.
use overridden generic functions, such as act(), getInput(), etc, to gain access to derived classes.


This sounds like a bad idea. If your client code is switching on type-id to determine functionality, you should investigate using virtual functions instead. That's what they're for. Fat interfaces that try to satisfy the needs of a bunch of unrelated classes lead to brittle code.

--
Dave Mikesell Software & Consulting

[edited by - dmikesell on November 7, 2003 7:47:05 AM]

Share this post


Link to post
Share on other sites
I''d have a ''special'' function for each class.
For the mage it would involve magic, for the warrior it may be a strength boost or something.

Then you use a virtual function (either pure or otherwise) in the base class. Then you call "pChar->ExecuteSpecial()"

Share this post


Link to post
Share on other sites
quote:
Then you use a virtual function (either pure or otherwise) in the base class. Then you call "pChar->ExecuteSpecial()"



That''s good, because your client code can use a Character pointer or reference generically, without having to fiddle around with type-ids and type casts. It can also call new code (i.e. new Character classes) without changing, which is what you should strive for.

--
Dave Mikesell Software & Consulting

Share this post


Link to post
Share on other sites