OOPs way to do this in C++?

J

Jim Langston

I want to change a class's methods at run time. I'm making a game and want
NPC objects to have different methods depending on a game designer. That
is, they may decide that this mob should be able to fly for movement, be
agressivie for combat, etc... Rather than a bunch of switch statements or
if..else I was thinking it would be good to be able to plug in
functions/methods to the class.

Well, there's the old C way, which I've done here, but am not happy with it
at all. For a few reasons:
1. I have to friend every function
2. I have to assign a function pointer, and things can go wrong there, plus
it gets to be a pain to maintain
3. It's just not, IMO, the best way it could be, but I don't know of a
better way.

I've only shown the function here for movement, but in reality there would
be one for movement, one for combat, one for guarding (if it's a guard),
etc...

Any suggestions on how to improve the design?

#include <iostream>
#include <string>

class Base
{
friend void Walk( Base& base );
friend void Fly( Base& base );
friend void Swim( Base& base );
friend void Float( Base& base );

public:

Base( int X, const std::string Movement ): X(X), Move_( NULL )
{
if ( Movement == "Flies" )
Move_ = Fly;
else if ( Movement == "Walks" )
Move_ = Walk;
else if ( Movement == "Swims" )
Move_ = Swim;
else if ( Movement == "Floats" )
Move_ = Float;
else
Move_ = Walk;
}

void Move() { if ( Move_ != NULL ) { Move_( *this ); } }

private:
void (*Move_)( Base& );
int X;
};

void Walk( Base& base )
{
base.X += 5;
std::cout << "Walked to " << base.X << "\n";
}

void Fly( Base& base )
{
base.X += 7;
std::cout << "Flew to " << base.X << "\n";
}

void Swim( Base& base )
{
base.X += 3;
std::cout << "Swam to " << base.X << "\n";
}

void Float( Base& base )
{
base.X += 2;
std::cout << "Floated to " << base.X << "\n";
}

int main()
{
Base MyBase( 10, "Flies" );

MyBase.Move();

std::string wait;
std::getline( std::cin, wait );
}

Regards,

Jim Langston
 
Z

Zeppe

Jim said:
Any suggestions on how to improve the design?

The correct answer, as aiouua said, is to use the decorator pattern.
Anyway, I wanted to point out that even with your design you don't need
any friend function. Consider this:

#include <iostream>
#include <string>
#include <functional>

class Base
{
private:
void Walk();
void Fly();
void Swim();
void Float();

public:

Base( int X, const std::string Movement ): Move_( NULL ), X(X)
{
if ( Movement == "Flies" )
Move_ = std::mem_fun_ref(&Base::Fly);
else if ( Movement == "Walks" )
Move_ = std::mem_fun_ref(&Base::Walk);
else if ( Movement == "Swims" )
Move_ = std::mem_fun_ref(&Base::Swim);
else if ( Movement == "Floats" )
Move_ = std::mem_fun_ref(&Base::Float);
else
Move_ = std::mem_fun_ref(&Base::Walk);
}

void Move() { Move_(*this); }

private:
std::mem_fun_ref_t<void, Base> Move_;
int X;
};

void Base::Walk()
{
X += 5;
std::cout << "Walked to " << X << "\n";
}

void Base::Fly()
{
X += 7;
std::cout << "Flew to " << X << "\n";
}

void Base::Swim()
{
X += 3;
std::cout << "Swam to " << X << "\n";
}

void Base::Float()
{
X += 2;
std::cout << "Floated to " << X << "\n";
}

int main()
{
Base MyBase( 10, "Flies" );

MyBase.Move();

std::string wait;
std::getline( std::cin, wait );
return 0;
}


As already said, it;s better to decorate, having a Base with

virtual Move() = 0;

and each derived class implement a different Move().

Regards,

Zeppe
 
G

Gianni Mariani

Jim said:
I want to change a class's methods at run time. I'm making a game and want
NPC objects to have different methods depending on a game designer. That
is, they may decide that this mob should be able to fly for movement, be
agressivie for combat, etc... Rather than a bunch of switch statements or

switch statements are bad for extesibility.

if..else I was thinking it would be good to be able to plug in
functions/methods to the class.

There are techniques that you can load up DLL's/DSO's and have methods
register themselves. The Austria C++ generic factories do this.
Well, there's the old C way, which I've done here, but am not happy with it
at all. For a few reasons:
1. I have to friend every function
2. I have to assign a function pointer, and things can go wrong there, plus
it gets to be a pain to maintain
3. It's just not, IMO, the best way it could be, but I don't know of a
better way.


The code below does what you describe - no friends, no function
pointers, and is extensible by simply loading a DLL/DSO that registers
methods on the fly. Note that it does not show how to unregister them
when a DLL is unloaded.


.......................................................
#include <iostream>
#include <string>
#include <map>

// define a position type
class Position
{
public:
Position( int X )
: X(X)
{
}

int X;
};

// define an abstract class that knows how to move a position
class Mover
{
public:

virtual void Move(
Position & io_position
) const = 0;
};

// define a registry of movers - this should be handled as
// a static local variable to a getter function...
std::map<std::string, const Mover *> s_registry;


class Base
{
public:

Base( int X, const Mover * i_mover )
: m_mover( i_mover ),
m_position(X)
{
assert( m_mover );
}

Base( int X, const std::string & i_mover )
: m_mover( s_registry[ i_mover ] ),
m_position(X)
{
assert( m_mover );
}

void Move()
{
m_mover->Move( m_position );
}

private:

const Mover * m_mover;
Position m_position;
};

// helper class to register a type
template <typename T>
class Register
{
public:
Register( const std::string & name )
{
s_registry[ name ] = new T();
}
};

// define a walk method
class Walk : public Mover
{
virtual void Move(
Position & io_position
) const
{
io_position.X += 5;
std::cout << "Walked to " << io_position.X << "\n";
}
};

// register the walk method
Register<Walk> reg_walker( "Walks" );


// define and register the rest
class Fly : public Mover
{
virtual void Move(
Position & io_position
) const
{
io_position.X += 7;
std::cout << "Flew to " << io_position.X << "\n";
}
};

Register<Fly> reg_flyer( "Flies" );

class Swim : public Mover
{
virtual void Move(
Position & io_position
) const
{
io_position.X += 3;
std::cout << "Swam to " << io_position.X << "\n";
}
};

Register<Swim> reg_swimmer( "Swims" );


class Float : public Mover
{
virtual void Move(
Position & io_position
) const
{
io_position.X += 2;
std::cout << "Floated to " << io_position.X << "\n";
}
};

Register<Float> reg_floater( "Floats" );


int main()
{
Base MyBase( 10, "Flies" );

MyBase.Move();

std::string wait;
std::getline( std::cin, wait );
}
 
B

BobR

Jim Langston said:
I want to change a class's methods at run time. I'm making a game and want
NPC objects to have different methods depending on a game designer. That
is, they may decide that this mob should be able to fly for movement, be
agressivie for combat, etc... Rather than a bunch of switch statements or
if..else I was thinking it would be good to be able to plug in
functions/methods to the class.

Well, there's the old C way, which I've done here, but am not happy with it
at all. For a few reasons:
1. I have to friend every function
2. I have to assign a function pointer, and things can go wrong there, plus
it gets to be a pain to maintain
3. It's just not, IMO, the best way it could be, but I don't know of a
better way.

I've only shown the function here for movement, but in reality there would
be one for movement, one for combat, one for guarding (if it's a guard),
etc...
Any suggestions on how to improve the design?

You may want to check out "Thinking in C++" vol.2, "Patterns"->"Abstract
factories". Might give you an idea.
Couple that with what the other posters have suggested.


Get "Thinking in C++", 2nd ed. Volume 1&2 by Bruce Eckel
(available for free here. You can buy it in hardcopy too.):
http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.html
 
A

aiooua

The pattern he's looking for is most likely strategy.

well, given that the additional features might need to applied/revoked
dynamically, i think it's a classic case for decorator. also, neither
state nor strategy support composition of features as naturally as
decorator does. what if the mobile needs to swim & fly?

----
#include<iostream>
using namespace std;

class Base{ public:
virtual void move() { /* X += 7; */ cout <<"move me." << endl;}
};

class Decorator: public Base{
protected:
Base *base;
public:
Decorator(Base *b) : base(b){};
void move(){base->move();}
};

class FlyDecorator: public Decorator{
public:
FlyDecorator(Base *b):Decorator(b){};
void move() { cout << "I flew, "; base->move();}
};

class WalkDecorator: public Decorator{
public:
WalkDecorator(Base *b):Decorator(b){};
void move() { cout << "I walked, "; base->move();}
};

class SwimDecorator: public Decorator{
public:
SwimDecorator(Base *b):Decorator(b){};
void move() { cout << "I swam, "; base->move();}
};

int main(){
Base *plain = new Base;
plain->move();
WalkDecorator *more = new WalkDecorator(plain);
more->move();
SwimDecorator *even_more = new SwimDecorator(more);
even_more->move();
FlyDecorator *lot_more = new FlyDecorator(even_more);
lot_more->move();
}
----

this does all that the OP's code did, plus has all the additional
benefits of decorator.

thanks,
 
P

patelvijayp

Hello,

Below is simple way to do it.


#include <iostream>
#include <string>

enum ACTIONTYPE {WALK, FLY, SWIM, FLOAT, COMBAT, GUARD};

class Base
{
void Walk(void)
{
X += 5;
std::cout << "Walked to " << X << "\n";
}

void Fly(void)
{
X += 7;
std::cout << "Flew to " << X << "\n";
}

void Swim(void)
{
X += 3;
std::cout << "Swam to " << X << "\n";
}

void Float(void)
{
X += 2;
std::cout << "Floated to " << X << "\n";
}

void Combat(void)
{
X += 2;
std::cout << "Combating around " << X << "\n";
}

void Guard(void)
{
X += 2;
std::cout << "Gruarding around " << X << "\n";
}

public:


Base( int X, const ACTIONTYPE Action1 = WALK ): X(X),
Action( Action1 )
{
std::cout << "Action initialized to " << Action << "\n";
}

//void Move() { if ( Move_ != NULL ) { Move_( *this ); } }
void doAction(void)
{
switch(Action) {
case WALK:
Walk(); break;
case FLY:
Fly(); break;
case SWIM:
Swim(); break;
case FLOAT:
Float(); break;
case COMBAT:
Combat(); break;
case GUARD:
Guard(); break;
}
}

void changeAction(const ACTIONTYPE Action1)
{
Action = Action1;
std::cout << "Action changed to " << Action << "\n";
}



private:
//void (*Move_)( Base& );
ACTIONTYPE Action;
int X;
};


int main()
{
Base MyBase( 10, WALK);

MyBase.doAction();
MyBase.changeAction(FLY);
MyBase.doAction();
MyBase.changeAction(SWIM);
MyBase.doAction();

std::string wait;
std::getline( std::cin, wait );
}
 
G

Gianni Mariani

Hello,

Below is simple way to do it.

I believe that the OP said no switch statement ?

Contrary to popular belief, enums are not very OO.

A good method extensibility by "plug in", as the OP requested is to have
a registry of methods and select the method based on a key (name of
developer, time of day etc). The design requirement should be that
there are no changes to the core application to extend via plug-in. In
the design you propose below, you have:

a) an enum to change - requires compiling all files when changed
b) a method in the main class to add
c) a new case in your switch statement

In the design I proposed earlier, the only thing required is to add a
new file to your linker or dynamically load a file containing the class
type derived from an application class and a "registration" object to
register the new method. No changes to the main code at all. The only
thing to be careful is to force the linker to load the .obj/.o into the
executable or dll/dso.

i.e.

// define a walk method
class Walk : public Mover
{
virtual void Move(
Position & io_position
) const
{
io_position.X += 5;
std::cout << "Walked to " << io_position.X << "\n";
}
};

// register the walk method
Register<Walk> reg_walker( "Walks" );

....
 
P

patelvijayp

Hello,

Here is simple way to do that.

#include <iostream>
#include <string>

enum ACTIONTYPE {WALK, FLY, SWIM, FLOAT, COMBAT, GUARD};

class Base
{
void Walk(void)
{
X += 5;
std::cout << "Walked to " << X << "\n";
}

void Fly(void)
{
X += 7;
std::cout << "Flew to " << X << "\n";
}

void Swim(void)
{
X += 3;
std::cout << "Swam to " << X << "\n";
}

void Float(void)
{
X += 2;
std::cout << "Floated to " << X << "\n";
}

void Combat(void)
{
X += 2;
std::cout << "Combating around " << X << "\n";
}

void Guard(void)
{
X += 2;
std::cout << "Gruarding around " << X << "\n";
}

public:


Base( int X, const ACTIONTYPE Action1 = WALK ): X(X),
Action( Action1 )
{
std::cout << "Action initialized to " << Action << "\n";
}

//void Move() { if ( Move_ != NULL ) { Move_( *this ); } }
void doAction(void)
{
switch(Action) {
case WALK:
Walk(); break;
case FLY:
Fly(); break;
case SWIM:
Swim(); break;
case FLOAT:
Float(); break;
case COMBAT:
Combat(); break;
case GUARD:
Guard(); break;
}
}

void changeAction(const ACTIONTYPE Action1)
{
Action = Action1;
std::cout << "Action changed to " << Action << "\n";
}



private:
//void (*Move_)( Base& );
ACTIONTYPE Action;
int X;
};


int main()
{
Base MyBase( 10, WALK);

MyBase.doAction();
MyBase.changeAction(FLY);
MyBase.doAction();
MyBase.changeAction(SWIM);
MyBase.doAction();

std::string wait;
std::getline( std::cin, wait );
}
 
P

patelvijayp

Oops,

My 2nd post is unintentional. I realized later that its already
present as No.7.

Sorry.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top