Bad design? So what's the solution?

Pages: 123
I've seen an overwhelming consensus that if you need to know the type of an object at runtime to downcast it and deal with it differently, that there is something wrong with your class design and you have a poor design. Supposedly, this is not very OO.

But I don't see an alternative for some scenarios. Consider a game where everything is represented as a block, each block is an object that is an instance of some class. Some blocks, like dirt and stone, don't have anything to do with storing inventories. Other blocks, like chests and furnaces, do have inventory/ies to deal with. Now what if there was a block that could put items into blocks that have inventories? I can't think of any design that would not involve either affecting dirt/stone to accommodate chests and furnaces or checking the type of an object and downcasting it to access more specific member functions.

Say you have the abstract Block class, which is the class through which all blocks interact with each other. The Dirt and Stone classes extend it for simplistic behavior (simply displaying textures and having different hardnesses/proper tools), whereas the Chest and Furnace classes extend the BLock class for complex behavior; the Chest having a simple multi-item inventory, and the Furnace having to keep track of its fuel, the item it is smelting, and the items it has smelted. Finally, there's the Pipe, which transfers items between blocks and is itself a block.

All I see is:
-add methods to the Block class to support the Pipe class accessing the inventories of Chests and Furnaces; this doesn't make sense because most blocks don't have an inventory and these methods shouldn't be at the Block class level
-have the Pipe class implementation check the runtime type info of the block it it attached to to see if it is a block it can interact with (as simple as checking if the block is an instance of e.g. an interface class for blocks that have inventories) and then downcast to perform the interaction; this seems to be against popular vote for OO principles and ideals

I guess what I'm trying to say is, I've been drinking an unhealthy amount of Java recently and I'm trying to get back to more-correct OOP. This is not only a C++ question, but also an OOP question in general.

Sumamry:
What is the correct OO-compliant way to have various objects interact with each other differently through the same interface they all implement?

How can a Bee detect PollinateableFlowers and Pollinate them, while not Pollinateing Bears, if the Bee only sees all LivingThings within its Environment?


I can't answer the above question without checking the runtime type of objects or giving bears a CanPollinate method, both of which seem wrong.
Last edited on
Could someone at least provide a suitable answer for my summary?
I've seen an overwhelming consensus that if you need to know the type of an object at runtime to downcast it and deal with it differently, that there is something wrong with your class design and you have a poor design. Supposedly, this is not very OO.
It's definitely OO, but there is an unsatisfactory feeling.

Perhaps the problem is that you're trying to fit diverse behaviors into a single inheritance hierarchy. Have you considered mixing in behaviors with multiple inheritance?

As you're spending time with Java, you should be familiar with multiple inheritance of interface--that's what you should be using in C++. Don't try to multiply inherit concrete classes.

How can a Bee detect PollinateableFlowers and Pollinate them, while not Pollinateing Bears, if the Bee only sees all LivingThings within its Environment?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ILivingThings
{
};

class IHaveNectar
{
};

class Bear : public ILivingThings
{
};

class Flowers : public ILivingThings
{
};

class PollinateableFlowers : public Flowers, public IHaveNectar
{
}

void Polinate(IPolinator*, IHaveNectar*);


It's a debatable opinion, but it's a start.
Last edited on
closed account (o1vk4iN6)
Well if you are going to generalize things to the point of LivingThings then that will have to have some sort of CanPollinate method in it. One flower could pollinate another perhaps, but if there is no class where you can say, this can never pollinate then you need some way to identify it and if that means asking if a bear can pollinate then so be it.

@ kbw

The problem with that is you need to have the class Bear and PollinateableFlowers. I think he only has an array of pointers of ILivingThings, at least to my understanding.
Last edited on
If I could add my 5 cents worth ....

I am not sure whether you know all this already - I am aware of trying not to be silly by pointing out things, that have been known by the OP for a long time, when the OP is much more experienced than me. Then again, who knows when any info given might be just what you are after.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class CBlock {

protected:

CBlock() {}  //cannot create obj of type CBlock

//virtual functions
};

class CSImple : public CBlock {};

class CComplex : public CBlock {
public:
virtual bool getBlockInfo() {} //whatever return you want for error processing
virtual bool setBlockInfo {}
};  //complex behaviour

class CChest : public CComplex {
//redefine getBlockInfo & setBlockInfo functions


};

class CPipe : public CBlock {
public:

virtual bool Transfer (CBlock *FromBlock, CBlock *ToBlock) {
//make use of  FromBlock-> getBlockInfo()
//make use of  ToBlock-> setBlockInfo()

}



};



The Transfer function makes use of the idea that a pointer to a derived class is a valid pointer to a base class. When you call the Transfer function, the arguments are pointers to specific objects like Chest or Furnace, and the right FromBlock function is called, because the compiler knows the type from the Transfer function call arguments.

I know that it looks like using getters / setters, but you can implement it however you like - it is the idea that is important.

It works because the compiler keeps track of inheritance with the PTABLE and virtual functions with the VTABLE.

So no casting needed.

This is a really handy feature - it allows one to specify 1 virtual function in a base class, instead of overloaded functions to cater for every type, or using casting.

I hope that this is of some help to you - I look forward to seeing what you think.

Edit: Changed a couple of mistakes in the code.

Edit2: It's the same for the pollinating situation.
Last edited on
@kbw yes I am familiar with the interface idiom, the reason I am choosing C++ is because it allows more control; for instance I can have data members provided by the interface classes and can have implementations for pure virtual functions for subclasses to invoke default behavior, etc.

The problem with your code snippet is that I cannot invoke that method without downcasting, and I cannot downcast without knowing ahead of time that is the object is of the correct type.

@xerzi yeah but that seems awkward. Your response to kbw is correct; I intend to just loop through a list of pointers to the base class type, with no idea what the objects are actually instances of.

@TheIdeasMan The problem is that I'd need to downcast, not upcast. That is, go from base to subclass.


I think I have a partial solution; there could be different tick-groups for different kinds of objects, and they'd be in different lists. Objects could safely be in two different lists at once and such, but the issue is how to set these lists up in the first place...
@L B

I am not sure you fully understand what I am saying.

The problem is that I'd need to downcast, not upcast. That is, go from base to subclass.


But that is what my example does - The function receives a base class pointer (which is transparently a derived class pointer) so it carries out the right derived class action.

Is this the right scenario for the pollinating problem?

There is a container of pointers to various kinds of Living objects - Plants, Animals, etc OR derived classes of these. The vector (say) is defined as a vector of pointers to CLiving objects. Various types of Flowers or Animals are pushed into this vector. That in itself works because a CBear Pointer (say) is a valid pointer of type CLiving pointer. Edit: The underlined bit

The bee sees these as pointers to living things.

The bee has a Pollinate function, that takes a pointer to a living thing. The bee cycles through the container using the pointers to call a function PollinateMe. This is a virtual function (redefined for each object type of Plant or Animal or derivatives of these) which carries out an appropriate action for that object type. It works because the compiler knows what the type of each pointer is, by using the PTABLE.

Another advantage is it could still work for an even more general situation. Say the base class was CForestThings, and there was a subclass CInanimateThings. This class would have it's virtual function PollinateMe which would return false. Of course we don't need to redefine this for it's derived classes, because it is the same for all of them. Now our the bee's Pollinate function can work for all kinds of things like Rocks etc.

I have made use of this feature myself. I have a Units conversion inheritance tree. The base class (CUnits) has pure virtual functions ToMetric & FromMetric which take pointers to CUnits. When the functions are called they are sent pointers to the types of units I want converted, and the appropriate derived class function is called.

Am interested to hear your further thoughts.

Last edited on
and I cannot downcast without knowing ahead of time that is the object is of the correct type.
Isn't that what dynamic_cast is for?
I know perfectly well how to do it. What I don't know is the ideal, perfectly OO way of doing it that doesn't bring people to say "If you have to do it that way, you have poor class design."
I agree with xerzi. If all you have is a collection of LivingThings, you have no choice but to declare a pure virtual isPollinatable() in LivingThing and/or have Pollinate() behave properly according to the class that implements it, or downcasting.
There is indeed an error in design here, and it's that you're giving a Bee a collection of LivingThings, rather than a collection of, say, Flowers.
Last edited on
@L B

Have you digested my post yet? I think it can work for you - if not, I would interested to find out why.

helios is saying the same as me, I think:

..... and/or have Pollinate() behave properly according to the class that implements it. ....


However I am saying it could still work given a pointer to a Living Thing, so I disagree with this part:

There is indeed an error in design here, and it's that you're giving a Bee a collection of LivingThings, rather than a collection of, say, Flowers.


This is L B 's whole point, I think - The bee can't decide to pollinate something until it lands on it.



Last edited on
The thing is, the bee shouldn't need to decide whether something can be pollinated to begin with.
@helios

Really? - If someone puts a glass of beer & a glass of petrol in front of you, should someone else make the decision for you as to which you will drink or whether you wish to drink anything at all?
That's a false analogy. A bee class object is not a bee.

EDIT: To clarify, a class doesn't need to behave like a real world analogue. It just needs to behave in a manner that is consistent with the surrounding class structure. You usually don't code your classes thinking that your own classes might send you incorrect data, do you?
Last edited on
@helios

OK, but isn't one of the features of OOP, being the ability to model real life objects and their behaviour?

And is this not one of the pillars of OO design ? I mean think about the various interactions between real life objects & model the behaviour on that.

Although, I recognise there are lots of situations which really are purely abstract - that is not necessarily related to the real world. For example mathematical concepts, but sometimes it might be possible to see through that to a real world situation as in Physics. I know you have a lot of knowledge in maths, so I am interested for you r thoughts on this.

I am only arguing this because my critical thinking is kicking in here, and as long as you don't mind, I might learn something.
If your interest is simulating an actual bee, then OO design will have little to do with your final design. If, as I think, a "bee" is a metaphor for a small part of a larger system you are interested in, the behavior of bees should have no weight in design decisions.

For example, imagine the interaction between an OS and a program that produces output. If you had to write that program, would you find acceptable an API that threw at you the list of all devices on the computer and gave you a function called isDisplayDevice()? Worse still, what about the list of every open handle and the additional function isDevice(), or isProcess()? That would be totally senseless, right?

Although nonsensical if stated like this, a better approach would be to have the bee ask the world "tell me where a flower is, any flower" or have a flower tell the bee (any bee?) "here, pollinate me" or perhaps to the world "I'm ready to be pollinated". The specifics depend on what exactly it is you're trying to do.

In this specific case:
Consider a game where everything is represented as a block, each block is an object that is an instance of some class. Some blocks, like dirt and stone, don't have anything to do with storing inventories. Other blocks, like chests and furnaces, do have inventory/ies to deal with. Now what if there was a block that could put items into blocks that have inventories? I can't think of any design that would not involve either affecting dirt/stone to accommodate chests and furnaces or checking the type of an object and downcasting it to access more specific member functions.
you have an object that wants to perform a specific action to a specific object, and if that action is inadmissible, nothing should change (or at the very least, the program shouldn't abort). What I would do in this case is have a virtual function in Block that does nothing (and perhaps signals the caller that nothing happened) and override it in the relevant classes.

The main differences between this case and bees are:
1. The bee wants to perform a particular action on any object of a class. The block wants to perform a particular action on a particular block.
2. It could be expected that a user attempt to store money in dirt, while some animal interactions will never happen. A fish should have no business knowing that it can't be pollinated by a bee merely because it's alive. (Note that this is partly a consequence of #1.)

What I would do in this case is have a virtual function in Block that does nothing (and perhaps signals the caller that nothing happened) and override it in the relevant classes.


Maybe we are saying the same thing in different ways, because that's what my examples do (both the Bee and the Blocks) The difference is that my example makes use of the pointer to the derived class being a valid pointer to the base class, so the correct action is carried out. Again it takes advantage of the PTABLE & VTABLE. You guys have a lot of experience in C++ - I am guessing you know all about these?

There is no sending of lists of all the objects, as mentioned in your OS scenario.

Although nonsensical if stated like this, a better approach would be to have the bee ask the world "tell me where a flower is, any flower" or have a flower tell the bee (any bee?) "here, pollinate me" or perhaps to the world "I'm ready to be pollinated". The specifics depend on what exactly it is you're trying to do.


Maybe it is nonsensical because it involves broadcasting and receiving messages - wouldn't that be harder or less efficient than using virtual functions as we both seem to be proposing. Is there a Design Pattern that can be used for that?

The main differences between this case and bees are:
1. The bee wants to perform a particular action on any object of a class. The block wants to perform a particular action on a particular block.
2. It could be expected that a user attempt to store money in dirt, while some animal interactions will never happen. A fish should have no business knowing that it can't be pollinated by a bee merely because it's alive. (Note that this is partly a consequence of #1.)


1. Bees & Blocks are very similar (that's why L B used them as examples) in that we don't want to have a bad solution to cope with different types. My use of the pointers deals with it elegantly IMO.

2. The Animal class will have it's virtual function returning false - all the derived animals will inherit that same function, so no more code needed for animals. This works because of the C++ system's internal VTABLE.

Animals are part of the problem because they can be landed upon. The Bee's detection system relies partly on colour, and it can only detect that is actually a flower once it has landed on it. So when it lands on a Yellow / Orange patch is doesn't know beforehand whether it might be a Tiger or an Orange / Yellow flower.

Your analogy of the OS isn't right IMO, because the OS does know which are output devices beforehand. On Unix, where everything is a file, I can imagine the file being the base class, and things like processes and devices would be derived classes, so it would be easy to know which is what.

Perhaps one could use my example in a similar fashion for the OS. The OutputFile function takes a pointer to an OutPutDevice as a parameter. When the function is called, it doesn't matter whether it is sent a pointer to a printer, the console, or a disk as an argument - as long as the appropriate inheritance and virtual functions etc is set up.

I don't know the actual details of a kernel - it's just the concept I am putting out as being a logical solution.

Anyway, it is good to have an intelligent debate - hopefully not too boring at your end !! L B hasn't said anything - hopefully he doesn't mind me running off with his thread.

The hell? To whoever reported my post: if you disagree with me then say so. Don't hide behind the forum's systems.
And more importantly, don't abuse them. That's not what the report function is for.

There is no sending of lists of all the objects, as mentioned in your OS scenario.
Obviously not. But that's one of L B's proposed solutions: to iterate over a collection of instances of a general type looking for instances of a particular type. The fact that nothing is sent or copied is irrelevant.

Maybe it is nonsensical because it involves broadcasting and receiving messages - wouldn't that be harder or less efficient than using virtual functions as we both seem to be proposing.
Like I said, it depends on what exactly it is you're trying to do, whether the bee wants to pollinate all flowers, any flower, any flower as soon as possible, or one flower in particular. L B just said
How can a Bee detect PollinateableFlowers and Pollinate them, while not Pollinateing Bears, if the Bee only sees all LivingThings within its Environment?
To me, that sounds like either "all flowers" or "any flower", and certainly not "this thing".

Animals are part of the problem because they can be landed upon. The Bee's detection system relies partly on colour, and it can only detect that is actually a flower once it has landed on it. So when it lands on a Yellow / Orange patch is doesn't know beforehand whether it might be a Tiger or an Orange / Yellow flower.

Your analogy of the OS isn't right IMO, because the OS does know which are output devices beforehand.
Then why couldn't the world know which living things are flowers, if it knows that bees will eventually come look for them?
You could make the point about whether the world should also keep track of worms for birds and so on, and then I would have to say that there's no definitive answer.
I've seen an overwhelming consensus that if you need to know the type of an object at runtime to downcast it and deal with it differently, that there is something wrong with your class design and you have a poor design. Supposedly, this is not very OO.
No, the opposite is true. dynamic_cast is perfectly safe for pointer. If you want to make virtual functions for all possible inheritances, the base class becomes a monster which will be nearly impossible to use. I had this case once, since then I take care that nothing from the inherited class infiltrate the base class
@helios

BTW, When I have reported posts (like a blatant ad for example) it deletes them - not sure why that is. I sent a PM to admin, and it stopped for a while, but now it is doing it again. It is bad because anyone can report a valid post (like yours), then it is gone. At least you post is still there.

Then why couldn't the world know which living things are flowers, if it knows that bees will eventually come look for them?


How are you going to do that? By broadcasting / receiving messages? As in an event driven system. Maybe there is a way of doing that efficiently, but the virtual functions sound easier and more elegant so far (In terms of my knowledge).

Of course, one could have the base classes HasPollen & NoPollen.

You could make the point about whether the world should also keep track of worms for birds and so on, and then I would have to say that there's no definitive answer.


I guess one could still use the virtual functions to handle Bees / Flowers, Birds / Worms, Spiders / Flies etc.

With my example, I would have a virtual function for each functionality (one to one relations), but I agree this would be messy for many to many relationships, which could be the reason to employ a Design Pattern, like Mediator maybe.

Just want to reiterate that we could be saying the same thing, but coming at it from different directions.

@coder777
If you want to make virtual functions for all possible inheritances, the base class becomes a monster which will be nearly impossible to use.


In my Units conversion code which I mentioned earlier, I had 2 virtual functions in the base class - and these work with how ever many different unit types I would like to add. There are 2 because they cover ToMetric & FromMetric. Of course I have to redefine them in each Unit class (Possibly dozens of those), that's OK, it's what virtual functions are for IMHO.

Where it gets out of hand is in many-to-many relationships - hence the need to use a design pattern.

Maybe I should write some code, which demonstrates the Bees / Flowers / Bears problem. Do I have any votes for that? Am guessing there might be some yes votes. Cheers.



Pages: 123