getters & setters are evil - do I have the right idea?

Pages: 12
@clanmjc

Hi, thanks for your reply.

The way I understand class inheritance is, there are 3 types of relationships: 'Is a', 'Has a', and 'Uses a'.

'Is a' implies inheritance (the object is a type of the base class), 'Has a' implies composition because the member is not a type of a base class, but is a necessary part of the class, 'Uses a' implies the object needs an object of this type to do something - it usually comes as a parameter of a function.

So the composition is an example of a 'Has a' relationship. So for classes such as sword, dagger, axe etc, they would all have to have a CCutting mCuttingAttributes; member, which is repetition of that member for each class. In my view, inheritance is supposed to avoid that situation.

I coded the inheritance above because they all exhibit the 'Is a' relationship.

@ne555
It's a relationship between characters, the weapon is part of the message.


OK, so should we have a virtual RecvAttack function in the Actor / Character class, that takes a Actor & Weapon as parameters? The Actor is an param so that presumably the Enemy / Player can fight back.

Or go further and make `healt_type' a class.


That's another great idea, I see, to hide the implementation of damage & health. If there were classes CHealth & CDamage, then I can see how, Players & Enemies 'have' (composition) CHealth, and Weapons 'have' CDamage. Although that might mean I should do the same for the other Weapon members, so maybe it's better to stick with the Weapon.Use() as an interface for now.

Cheers




Thanks everyone for their info - especially Disch for the extra effort involved in detailed replies.

I look forward to any future advice from you guys.
I'm going to resurrect this.
OK, so should we have a virtual RecvAttack function in the Actor / Character class, that takes a Actor & Weapon as parameters? The Actor is an param so that presumably the Enemy / Player can fight back.
1
2
3
4
void cheater::RecvAttack(Weapon &weapon, Actor &the_other_guy){
   this->health.increase( weapon.attack() );
   the_other_guy.RecvAttack(weapon, the_other_guy);
}
Every time that you attack me I grow stronger, and you hurt yourself in the confusion.
I'm going to resurrect this.


No worries, I'm always interested in new ideas.

Just some reference info:
The simple model: Damage is subtracted from Health.
Complex model: There are different behaviours for Weapon / Attacked object scenarios.

1
2
3
CPlayer Hero; //derived from CActor because of common attributes like Health
CEnemy Troll; //derived from CActor
CSword HeroSword;  //Derived from CWeapon 


or this instead:
1
2
3
CPlayer *pHero = new CPlayer ; //derived from CActor because of common attributes like Health
CEnemy *pTroll = new CEnemy ; //derived from CActor
CSword *pHeroSword = new CSword;  //Derived from CWeapon 


So, How to do this problem:

1. The worlds easiest, but somewhat bad solution

Have a getDamage function in CWeapon, and getHealth, setHealth in CEnemy.

Immediate problem - uses getter & setters which we are trying avoid, especially the public setHealth which is nonsense.

2. Have a virtual Attack & RecvAttack functions in CActor.

This is a initiate & handler scenario. An Attack is initiated by one object with the Attack function, and this handled by the object being attacked, with the RecvAttack function.

1
2
3
4
virtual bool Attack(CActor *pAttacker, CActor *pAttackee, CWeapon *pAWeapon){

return true;  //so we can return some kind of success status - might use a status enum to be more fancy
}


The call to it would look like this:
 
pHero->Attack(this, pTroll, pHeroSword);


The Attacking object is included so the object being attacked can mount a counter attack.

Weapon could have a virtual Use function, which for the simple model just returns the damage. It is a getter in disguise. Can be redefined to carry out more complex behaviour.

Actor objects could have a virtual RecvAttack function, for the simple model subtracts the damage from health. It is a setter in disguise. Can be redefined to carry out more complex behaviour.

Although these function are getters /setters, they are virtual and in the base class, so they form an interface.

Problem - this implies a free for all, Player vs Player & Enemy vs Enemy - not what we want. Also want to avoid the situation with cheater objects ne555 just pointed out.

Possible partial solution - use RTTI to check whether it's a valid attack - that is, the the typeid's aren't equal.

3. Have a virtual Attack & RecvAttack functions in CPlayer & CEnemy

I tried doing this, but had problem with circular #includes. Tried forward declaration of classes, but couldn't get it to work for both situations. The code compiles, but I can't do any thing with a Player object in CEnemy.

In CEnemy.h
1
2
3
4
5
class CPlayer;	//fwd declaration to avoid circular includes
                       // cannot do anything with a Player object
virtual bool Attack(CEnemy *, CPlayer *, CWeapon *);
virtual bool RecvAttack(CPlayer * , CWeapon * ) ;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "/home/david/projects/RGP/CActor.h"
#include "/home/david/projects/RGP/weapon/CWeapon.h"
#include "/home/david/projects/RGP/enemy/CEnemy.h"

class CPlayer : public CActor 
{

public:
    CPlayer();
    virtual ~CPlayer();
	
	virtual bool Attack(CPlayer *, CEnemy *, CWeapon *);
	virtual bool RecvAttack(CEnemy * , CWeapon * ) ;
};


4. Have a virtual RecvAttack function in CActor - no Attack function

The arguments would be the attacking object & the weapon. Would need RTTI to check validity of the attacking object. Is simpler because there is 1 function not 2. Doesn't solve the cheater problem

OK, so those are my ideas so far. Am interested to hear how to solve the cheater problem.
> Complex model: There are different behaviours for Weapon / Attacked object scenarios.

Yes, the Rock-paper-scissors scenario - with polymorphic rocks etc.

May be, use mediators? http://en.wikipedia.org/wiki/Mediator_pattern
Last edited on
Thanks JLBorges, for your reply.

I like that idea of mediators - I will keep it in mind.

One observation:

This was originally a simple problem - best way to subtract damage from health.

Am just trying to sort out a good interface. Hopefully virtual functions / mediators will handle the complex model.

As far as the "cheater" problem goes, If we have a public interface to a class, wouldn't any derived class be able to 'cheat', because it now has access to the private members, and can call it's own private functions?

I am off to visit friends, will ruminate on this a bit more.
3. Have a virtual Attack & RecvAttack functions in CPlayer & CEnemy

I tried doing this, but had problem with circular #includes. Tried forward declaration of classes, but couldn't get it to work for both situations. The code compiles, but I can't do any thing with a Player object in CEnemy.


What do you mean you cant do anything with a Player object in CEnemy?

CEnemy.h
1
2
3
4
class Player; //forward declare
//...
virtual bool Attack(CEnemy *, CPlayer *, CWeapon *);
virtual bool RecvAttack(CPlayer * , CWeapon * ) ;



CEnemy.cpp
1
2
3
4
5
#include "Player.h"
//...
bool CEnemy::Attack(CEnemy * a, CPlayer * player, CWeapon *b){
   player->doSomething();
}


You do the same forward declares in the header and #includes in the cpp for the Player class. But wait, I need implementations in the header, I'm using templates(Or I just NEED to have it in the header). Then, use the forward declare, #include strategy.

e.g.
CEnemy.h
1
2
3
4
5
6
7
8
9
10
11
12
class Player;//forward...

class CEnemy{
//...
public:
   virtual bool SomeFunction(CEnemy *, Player*);
};//Class declaration done.

#include "Player.h" //OK, Player.h gets pulled in AFTER CEnemy.h, no circular reference.
bool CEnemy::SomeFunction(CEnemy * pEnemy, Player * pPlayer){
   pEnemy->DoSomethingCatastrophic(pPlayer);
}


The problem with circular reference, in it's simplest terms is this.

Player includes Enemy. Enemy includes Player. Each header includes the other, but they include the other first. That is the key.

Player.h
#include "Enemy.h" //Ok compiler sees this, pulls in Player.h

Enemy.h
#include "Player.h" //Ok compiler sees this, pulls in Player.h

Player.h
...
So on and so fourth. The problem is, you are telling the compiler that Player.h depends on Enemy.h and vice versa. By doing the former (Posted above), you do not have this dependency.
Last edited on
@clanmjc,

Thanks for that, it worked really well - ThingsLearnt++;

Going back to the 'cheater' scenario, the other thing to do, is make the parameters to class functions const where possible.

Thanks for everyone's help.
Topic archived. No new replies allowed.
Pages: 12