The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

problem with class design

I am having some difficulty designing C++ classes to accomplish the following:

I (currently) have two types of data, D1 and D2, which I want to store in a single container. D1 and D2 are similar in that D2 has all the properties of D1 and a few others.

I am trying to find a way to code D1 and D2 such that they can coexist in the same container and be used with code that won't be ugly.

I thought I could have an abstract base class 'DB' for D1 and D2 and make the container carry D1 and D2 with handles of type DB. But this creates the issue that the code which will use the container now has to figure out which type of data DB points to since D1 can't do everything D2 can. Lots of dynamic_cast<>, if() etc.. Ugly!

I could create a single data class and have a member "type" and then write the code accessing the container such that it will have a switch() to figure out what that data is, and work accordingly.. That's not pretty either.

I can sense I need some sort of polymorphism or something to make this work, but I can't figure it out. Could you guys help me out please?

Thanks!
stuck_newbie
Thursday, February 16, 2006
 
 
I'd have D2 inherit from D1 and then write a wrapper container that provides two iterators, so that code using the container isn't ugly.

One iterator would be a standard iterator, returning all the objects as D1s; the other would be a custom iterator returning only D2s (identifying them with a read-only boolean property of the object, 'isD2').
Jordan Stewart Send private email
Thursday, February 16, 2006
 
 
D2 should extend D1. If you have a function that should behave differently in D2, then you should declare the function virtual in D1 and write the second, alternative implementation in D2.  Simply put, if you do this...

    class D1 {
    public:
        virtual void foo();
    };

    class D2 : public D1{
    public:
        void foo();
    };

    //this is what you want
    D1* myD = new D2();
    myD->foo();

The implementation of foo() from D2 will be invoked. Do some googling on "polymorphism" or just "overriding" for more info, because it's what you're trying to do.
Kyle Send private email
Thursday, February 16, 2006
 
 
After many years, projects, & approaches to C++ design, I've found it best *not* to use a design where objects of both the base class and the derived class(es) can exist as freestanding objects.

My rule-of-thumb advice is: if a class will be derived from, never instance objects of that class directly. Design around it.

The design with a base 'DB' class seems much, much better.

For advice on a proper design in the above case, you do not provide sufficient information.
J Send private email
Friday, February 17, 2006
 
 
Kyle,

I kinda like your approach, however, there is something I am not sure how to handle. D2 will have more data and more methods on top of what D1 has. So in that sense it (perhaps) needs to inherit from D1 and add the extra stuff which D1 won't have. It is not just a matter of the same method behaving differently. It is a matter of D2 having more methods than D1.

If I understand you correctly, you are saying, just make what D2 has but D1 doesn't still be a part of D1 and make them virtual. When D2 inherits from D1, it will fill them in.

Or, to make J happy too, I could still have the abstract DB interface which has everything virtual. Then D1 and D2 inherit from DB and implement whatever they want to. What's not implemented will simply not do anything... and the container will have objects of DB.. the code that stuffs the container will know what to create (D1 or D2) and if someone calls a method not implemented by whatever DB is pointing to in the container, it just won't do anything and the code accessing D1 or D2 via DB will have to figure out what to do in case what DB points to doesn't have an implementation for the particular method.

This seems decent, but it stills smells a bit for 2 reasons:

#1 D1 won't implement some methods, and the code accessing it via DB will not know which methods aren't implemented. This may or may not be a problem. I could have a bunch of IsXXXAvail() functions to figure out if it implements them, but I think that defeats the purpose.

#2 if I end up creating D3 which let's say has everything D1 and D2 has but even more, now I am screwed because DB will need to change to incorporate the new stuff...

It feels like I am close, but not quite there yet. Perhaps what I need is not inheritance, but something else all together. Hence this post. :)  I've had this issue before but couldn't find a decent solution back then either. It appears I need to change my viewpoint, but I seem to lack the viewpoint I need to solve this problem.

Btw, J, could you tell me what else you need to help me? I don't know what is missing from my question. If you ask, I'll try to answer.
stuck_newbie
Friday, February 17, 2006
 
 
>> #1 D1 won't implement some methods, and the code accessing it via DB will not know which methods aren't implemented. This may or may not be a problem.

This is why you're having problems, because you've misdesigned your code. You are doing the equivalent of going to a zoo and asking all the animals to run around. Which is fine, until you get to the aquarium section.
R. C. James Harlow Send private email
Friday, February 17, 2006
 
 
What are D1 & D2? Database connections? Queries? 3D objects? Intelligent agents? Loaded-files representation?

I think the problem is on how you are looking at it. No proof, but I've never seen a case where there wasn't the case: there should be a concept for which there are no 'unimplemented' methods. The body of some methods may be empty, or the return value constant, but that doesn't mean 'unimplemented'.

You should also be able to find a proper name for the concept. And that's the base class' name.

This is the key to good OO design.

Post about the details & we can have a knack at it.

The D1 + D2:D1 is a conceptual mess.
J Send private email
Friday, February 17, 2006
 
 
Solving this in a *physical* sense is easily achieved. boost::any is a variant class you can use off the shelf.

int
main()
{
    vector<boost::any> anyVec;

    anyVec.push_back( boost::any(1) );
    anyVec.push_back( boost::any( string("Hello world!")) );
    anyVec.push_back( boost::any(2.718f) );

    cout << any_cast<int>( anyVec[0] ) << "\n";
    cout << any_cast<string>( anyVec[1] ) << "\n";
    cout << any_cast<float>( anyVec[2] ) << "\n";

  return 0;
}

*However*, the more important thing is, why would you want to do something like this? Keep in mind that to do anything useful with what you have stored, its type must be dynamically resolved. With polymorphism, the language does it for you. In the above case, we must take care of it ourselves. I'd ask myself whether the problem at hand can in any way be solved more gracefully without such convolutions.
Jayan
Friday, February 17, 2006
 
 
Picture D1 and D2 as two types of units (as in a computer game). D2 is a D1 with more things it can do. It has attributes/features that D1 does not have. It is a more-enhanced D1.

I naturally want to keep them in the same container, but I should be able to figure out what I have in the container so I can act accordingly, give access to the enhanced/extra stuff in D2 if there is one.

I am sure this problem has been solved as it appears to be common. I just don't know what pattern to use, or what to google even to get the right answer.
stuck_newbie
Friday, February 17, 2006
 
 
Ah ok, it's that. Especially in that case, forget the D1+D2:D1 thing.

Games are especially tricky as for unclear ontologies. A last minute design requirement may make any class hierarchy you design break down. Thus: do *not* design the software architecture around the game's design.

For example, you may have D2's which are user-controllable units, while D1's are computer controlled, so you decide to have health, ammo, etc... in D1 and interface-specific stuff in D2, deriving them from D1. Then, a game designer decides that it would be cool to be able to take control of an enemy unit and you're screwed.

Or, buildings don't have 'health' for some time but then they end up being destroyable, or any unit can carry a weapon, or suddenly weapons can act on their own, etc...

Have a base class with all the universal stuff: ammo, health, whatever. Do not derive from this class. All units are equal.

Then, if some units need to be controllable by the user, have UserControl * within it. Most units will have a NULL here, others won't. Have a Behavior * as well, have an abstract Behavior class which you never instance, and derive from this the specific ones: UserControlledBehavior, StandAloneEnemyBehavior, SquadMemberEnemyBehavior, etc... Likewise with anything else.

This design works. The ugliest part is that the base Unit class can become quite crowded. But it's the clearest & most flexible design.

This *is* a tricky problem. Don't think it's been well solved or that it's well understood.
J Send private email
Friday, February 17, 2006
 
 
As you want to bea able to do certain actions, those actions define the API.

The classes that need to be manipulated mut thean implement that API.

The base class (interface?) defines the API:
class VBC {
  virtual void f1...
  virtual void f2...
}

class DB1 : public VBC {
  void f1(){DB1 appropriate action;}
  void f2(){NO-OP;} //Not applicable to DB1
}

Class DB2 : public VBC {
  void f1(){ DB2 appropriate action}
  void f2(){ DB2 specific action}//applicable to DB2
}

Of course I haven't prgrammed C++ in ages, so Caveat Emptor
Honu Send private email
Friday, February 17, 2006
 
 
Could I use the Visitor pattern here? I could have a base class for D1, D2, etc.. and if something specific needs to happen, I could create Visitors for each such specific case.
stuck_newbie
Friday, February 17, 2006
 
 
Does this problem have a specific name I can google? I am having difficulty summing it up in a few words...
stuck_newbie
Friday, February 17, 2006
 
 
Yes, you can use the Visitor pattern. Probably a bad idea if the game is to have many elements/actions/unit-types etc. Visitor pattern works well in examples like document processors, etc... where the number of operations is not very high.

If you are trying to use the Visitor pattern to abstract away 100% of the details, I'll warn you that the details *do* need to appear somewhere.

I think this problem is best called "OOD", and you've just hit a problem you can spend your entire life researching.
J Send private email
Friday, February 17, 2006
 
 
RTTI seems to be the easiest and cleanest way to do what I am trying to do...
stuck_newbie
Friday, February 17, 2006
 
 
:)
J Send private email
Friday, February 17, 2006
 
 
Still ugly though!...

Looks like I need to read some more game prog. books to see how others have solved this problem.
stuck_newbie
Friday, February 17, 2006
 
 
What is the thing you want to "do"? If they're game objects, and you want to tick them, just implement virtual void tick() on each class. An AI-controlled unit will want to analyse terrain & attack strategies, move, maybe fire; a player-controlled unit will want to take controller input and react to it. At the moment, if I'm right, you want to try and  call move() and fire() on AI units, and takeInput() on the player one, right? Don't do that. Have tick() in both classes call into the class-specific functions.

The two units *must* have this property in common, otherwise you wouldn't want to put them in a container together. It's a case of figuring out what you want them to do *in terms of the base class that they share*. If you actually can't figure out what this is, you shouldn't store them together.

Even if this isn't the exact problem you're trying to solve, it's probably pretty closely related.
R. C. James Harlow Send private email
Friday, February 17, 2006
 
 
+1 James

OP: I think you are stuck because you think you've got a problem which is solved with the programming language, but that's not the case. It's a common misconception. I suggest you stop looking at C++ features to solve it.
J Send private email
Friday, February 17, 2006
 
 
OK this thread has gone off the deep end.  Let's back up a little.

Kyle had the right solution, using inheritance.  In fact, this is what inheritance was designed for.  The fact that D2 has more data than D1 doesn't matter.  It's just that if you need a D2, you need to cast your D1 to a D2 before using it.

I don't remember what C++ uses to check for types (typeof?) but the simple way to check for a D2 is to have an "int type" variable in D1 and when you make a D2 set that value to something different in D2's constructor.  Then you can do this:

void doStuff(D1* myD)
{
  if(myD.type == 2)  //cast it to a D2
  {
      D2* myD2 = (D2*) myD;
      //work with myD2 as a D2, full of your D2 data
  }
}

If you need a D3, you can similarly cast it to a D3.
Post first, ask questions later
Friday, February 17, 2006
 
 
RTTI is what you said except it is type-safe.. I would use int type with static_cast<> or dynamic_cast<> (ugh!) to do what you said.. But what you said exactly is I think frowned upon since it is not safe.

Thanks for your input though.
stuck_newbie
Friday, February 17, 2006
 
 
> I am trying to find a way to code D1 and D2 such that they can coexist in the same container

Why? This is the constraint that looks wrong without a really good reason.

If you can make them look alike from a behavior perspective then inheritance and virtual methods will work.

Another approach is to have a get(attribute name) method in the base class so you can ask for the information you want generically. If it's not there then you should be able to do something intelligently without dying. Then you don't have to care about the underlying types.
son of parnas
Friday, February 17, 2006
 
 
PostFAQL - that's pretty grim. You're essentially doing yourself what the compiler will do for you if you ask it to. If you want to do that, fine, but that's C's domain, not C++. On top of that, you're using a C-style class, which destroys another of C++'s advantages, strong typing. If you *must* cast (and in your example, you don't need to), use static_cast. The feature you use to get around needing to cast is...virtual functions.
R. C. James Harlow Send private email
Friday, February 17, 2006
 
 
Should have been "a C-style cast", apologies.
R. C. James Harlow Send private email
Friday, February 17, 2006
 
 
stucknewbie, you definitely need to have D2 extend the D1 class via inheritance. Beyond that, you basically have two options:

1) Create "GetType()" functions in each class. Have D2 override D1's "GetType()" to return a different type. When you get an object out of your container, check the return from GetType(), and if it's a D2, then cast it to a D2 and invoke the extra functions your want to invoke. Personally, I think this is the wrong the way to do it. The point of inheritance is to be able to treat multiple classes exactly the same and as soon as you start checking types, it's a warning sign.

2) For all the extra functions you have in D2, also declare them as virtual functions in D1 that do nothing. Then as you pull objects out of your container, you only cast them to D1 and invoke all necessary functions. For D1 objects, your functions will do nothing and return. For the D2 functions, you'll have overridden those functions to actually do something.

Some of the object purists may have a fit about approach #2, but in my opinion, it's most likely the correct approach for your particular situation, given the inheritance tools at your disposal in C++. Hard to say for sure unless you get into more detail by showing us the *actual* classes.
Kyle Send private email
Sunday, February 19, 2006
 
 
Oh, yeah. Some people will recommend using RTTI with dynamic casts and such. I generally don't like to use RTTI in C++ because of (a) the overhead in the C++ runtime, (b) the lack of consistent support across all platforms, and (c) the incredibly ugly syntax you have in C++ to deal with it.
Kyle Send private email
Sunday, February 19, 2006
 
 
I think I decided to do a combination of the suggestions. I'll create a base class DB and then derive D1 and D2 from it. I'll use DB for the container. DB will have the common operations between all the derived classes. For anything specific, I'll use the Visitor pattern. That way I can avoid using switch statements, and add on to my code without having to touch what's already there. The number of visitors I wrote my blow up, but it appears there is no clean simple solution for this problem anyway.. I opted to go this way. We'll see how it works out.

Thank you all for your suggestions!
stuck_newbie
Monday, February 20, 2006
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz