c++ dividing up the interface

Started by
6 comments, last by Storyyeller 14 years, 5 months ago
One thing I've been really confused about lately is figuring out what to put where in the interface. Often times, some members need to be accessed only be certain other classes or functions. For example, suppose I have a class Bullet that wants to give itself a Move behavior. The move behavior needs a pointer to an object with a move function in its interface in order to move its owner. But if I make the move function part of Bullet's public interface, then anything can move it, not just the behaviors. The only options I can think of are sticking everything in the public interface (a seeming violation of encapsulation), or abuse of Friend functors for everything, which seems unnecessarily complicated and prone to mistakes.
I trust exceptions about as far as I can throw them.
Advertisement
Your terminology is a bit confusing. It's not clear what you mean by a "behavior," nor why it would need a pointer to an object, nor why a class function being public is a problem.

You are the programmer. If you want the Bullet class to have a move function, add one. As the programmer, you'll determine when that function gets called and you needn't worry about "anything" calling it.

Do I misunderstand your concern?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

People often overuse inheritance and code-reuse because they're taught from the beginning that it's pretty much the holy grail of software development. Problem is, in my experience, your first few attempts at creating a technology are not going to be good enough to merit reuse.

What's worse is that you spend 8 months trying to figure out if you should create a hierarchy that stipulates Base->Renderable->Dynamic->Bullet or just Base->Object->Bullet or however and forget that all you're trying to do is make a handful of pixels zip across the screen which will probably only be visible for a quarter of a second anyway, and then clean up the memory used for it. One of the most intelligent things I've ever heard was, "A good engineer can estimate the difficulty of a problem and choose the correct set of approaches to consider as solutions..." That was Jay Stelly from Valve, discussing one of the technical problems they had when creating Half-Life. His point was they found a good middle ground devoting just enough time to solve the problem in a way that would be acceptable but they didn't spend 3 years working out how to solve something that most players wouldn't notice. MY very long point is this: will your player notice a difference if your bullet that flashes across the screen is inherited from 8 base classes or just 1 base class?

If you're trying to create a reusable engine and this is your first attempt at such a project, I'd say drop the "reusable" part and the "engine" part for that matter and focus on the end-result. Once you can complete a whole project it makes it much easier to break down how you want objects to interact in the code.
More like my second attempt. My first attempt wasn't planned out at all, and ended up duplicating a fair amount of code. I thought that I needed to redesign and rewrite my game, or else it would grow unmanageable once I expanded it to a larger game with more features etc.

I suppose I am going overboard in terms of worrying about design though.


Anyway, here's an example of what I've got so far
class Behavior: public GenericInterface, public ActiveInterface{    bool active;    protected:    virtual void UpdateMe(){}    virtual void drawMe(SDL_Surface *screen, Sint16 screenx, Sint16 screeny) const{}    void Deactivate() {active=false;} //Call the callback if there is one    public:    Behavior() :active(true) {}    virtual ~Behavior() {if(active){Deactivate();}} //Make sure the Deactivation, and possible callback is always                                                    //called prior to destruction, but don't call it twice (check if it is already not active)    void Update() {if(active){UpdateMe();}}    void draw(SDL_Surface *screen, Sint16 screenx, Sint16 screeny) const    {        if(active){drawMe(screen,screenx,screeny);}    }    void Kill() {Deactivate();}    bool IsActive() const {return active;}};class Mover: public Behavior{    //We are using a regular pointer, rather then a shared pointer, because behaviors do not own their parent    MoveableInterface* target;    double vx,vy;    void UpdateMe()    {        if (target)        {            target->Move(vx,vy); //defined in MoveableInterface        }        else        {            Deactivate(); //If the parent doesn't exist, there's no use for us anymore        }    }    public:    Mover(MoveableInterface* parent, double vx0, double vy0)        : target(parent), vx(vx0), vy(vy0) {}};
I trust exceptions about as far as I can throw them.
Hey, look, he's using multiple inheritance.
A wise man once said that once you find yourself using multiple inheritance for the simplest of tasks and objects, you know your design is flawed. I wouldn't be surprised if you end up with a PolyObject class that looks like this:
class PolyObject : public IMovable, public IRotatable, public IScalable, public IPhysicsable, public IStorable, public IGPUResourceUser, public IRenderable, public IRobot{//...};
What should I do instead?

In this particular case, I wasn't actually using those, so I took them out. But what about in general?
I trust exceptions about as far as I can throw them.
It could go either way really. I could argue that Dink Smallwood turned out to be a great game despite having an 11,000 line header file and a single source file for the whole game, or I could argue that Unreal Tournament (the original) defined every object, even redefining objects like classes. It all depends on your experience level and style. Carmack accomplished the same thing Sweeney did using a mash-up-hack C method of coding while Sweeney used design patterns and object-orientation. Both games turned out to look, feel, and play about the same. This is what makes me argue that it really doesn't matter.

When I say you're overdoing it I just mean that someone with less experience should be focusing on the core game itself, not complex design patterns and object-oriented programming. They're just weapons in a coder's arsenal, and like weapons, you should probably learn to use the basics before moving onto the complex. Things generally get ugly if someone thinks that because they can use a knife they can use a gun. And I'm definitely running this analogy straight into the ground so I'm gonna stop myself here.

So for now, just keep in mind what you're trying to do and how it will affect the end-result. Keep It Simple, Stupid (KISS), is another practice that many famous programmers have religiously advocated.

Its hard to tell you how you SHOULD do it because based on the code you've shown, I doubt the Bullet class is the only one being over-complicated. I personally just create an array during initialization, drop a projectile in it when needed, then set it's state variable to DEAD when I'm done with it. I do a little more than that but it's not much more complicated than a simple array.
I'm thinking about just starting over again, hopefully achieving a middle way between too much focus on patterns, and not enough focus. Hopefully I can just combine lot of the code from my first two attempts a simple, but scalable hybrid design.
I trust exceptions about as far as I can throw them.

This topic is closed to new replies.

Advertisement