Bad design? So what's the solution?

Pages: 123
closed account (z05DSL3A)
TheIdeasMan wrote:
BTW, When I have reported posts (like a blatant ad for example) it deletes them - not sure why that is.
It is if you report a new member (few posts). That is the proper behavior for this site, self moderation.
Thanks Grey Wolf.

ThingsLearnt++;
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.
That is not the case.

In the example I gave, only those that support the necessary interface can use the polinate function. It is a system that does not cast. So the compiler will ensure that only flowers with nectar can be polinated. Let the type system do the work.

Further, I think it's wrong to have a single tree that has all this IsXXX() methods. Those features should be mixed in using inheritace.
Last edited on
The problem is that whatever solution I use cannot have any kind of "hard-coding". If I were, for example, to have overloaded methods for adding objects to the object list that added certain types of objects to certain types of lists (eg add(LivingThing) overloaded by add(Bee) and add(Pollinateable), each adding to a specific list and then calling the next most specific method) it would be impossible to extend this to include classes that plugins might add without adding more overloaded methods.

The point is, all of my code should not have to take into account the existence of other types. Not all types should have to be known ahead of time, but some types need to be able to interact with other specific types that they do know about.

If I had to add IsPollinateable methods to LivingThing, what would I have to to for every single other class that I have made, will make, or can make? It doesn't work. It can't account for classes added by external code and it forces all types to be aware of all other types at the top-level abstract base class. It's ugly, and it's hard-coded.

If I use dynanmic_cast, that's great and all, because types can choose which other types they are aware of and the base classes can be left untouched when new types are added, but I've been told (and I haven't been told why) that this is poor class design. Yes, I've been told that checking the type of an object and downcasting is poor class design. It's pretty common in Java, though.

If I can have my code autonomously manage various lists of objects, where an object is referenced from all lists that apply to it, that would be ideal. That way, the managing code could go through all the lists, and call the appropriate Tick method from the correct context without casting and without checking the actual type of the object. This is bordering pretty close to reflection, but I'm pretty confident that RTTI is all I need.

helios wrote:
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.
No, both cases are identical from a programming perspective.

TheIdeasMan wrote:
There is no sending of lists of all the objects
That's correct. An object doesn't know and cannot know about all other objects that also exist within the same program.

@TheIdeasMan I cannot have any code in base classes that deal with or account for specific types. Specific types can inform managing classes that they exist, however.

helios wrote:
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.
That is not one of my proposed solutions. Perhaps you misinterpreted when I said that there would be multiple lists and I would iterate them all; for example, there would be a vector<LivingThing> that included all living things, a vector<Pollinateable> that included all pollinateable things, and a vector<Bee> that included all bees. There would be no need to check the type because you would know which list you were iterating, and there would be no need to cast because the list's generic type is already of the type you need.

At this point I should make it clear that there won't be one class or one part of code responsible for making everything "live".

coder777 wrote:
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
Yes, which is my point, except I'd been told and have read that having to do that is poor design somehow.

TheIdeasMan wrote:
Of course, one could have the base classes HasPollen & NoPollen.
Then all types must suddenly be aware of Pollinateable things whether they have anything to do with them or not, and must explicitly extend the correct base class. This is so far from possible that I'm not even going to try to think of a workaround.

kbw wrote:
In the example I gave, only those that support the necessary interface can use the pollinate function. It is a system that does not cast. So the compiler will ensure that only flowers with nectar can be pollinated. Let the type system do the work.
You have given me a skeleton and told me that the flesh is perfectly healthy.


My potential solution:
I will have a simple abstract base class called Manager with one pure virtual member function Manage. There will be a master list of all Manager instances (all Manager classes will be singletons, the idea is to get the polymorphic behavior). The layout of Manager classes will parallel the actual classes that represent e.g. bees and flowers and Pollinateables. Every 'tick', the master list of managers is iterated and each manager has Manage called. Thanks to polymorphism, the correct context will be used to have the correct specialized behavior. How is this all set up? Upon loading a plugin, the plugin adds its manager classes to the master list of managers. Even the actual vanilla behavior of my game will be encapsulated in a plugin in order to make this work elegantly.

For those of you who have no idea what I mean or how it solves the problem, rest assured I've been writing and tsting code and it's all working flawlessly. I'll eventually get around to making a working example witht the bees and flowers. The code is currently meeting these goals:
-No use of dynamic_cast
-No use of RTTI
-No subclass-specific support in base classes
-Polymorphism and multiple virtual inheritence w/o issue or double calls

I'm very happy with my solution and plan to share it, maybe as an article or something. Thanks for all your help - readiny your arguments and discussions gave me the mental capacity to come up with my own solution that fits perfectly in all cases I can think of.

Thanks!
That is not one of my proposed solutions. Perhaps you misinterpreted when I said that there would be multiple lists and I would iterate them all; for example, there would be a vector<LivingThing> that included all living things, a vector<Pollinateable> that included all pollinateable things, and a vector<Bee> that included all bees. There would be no need to check the type because you would know which list you were iterating, and there would be no need to cast because the list's generic type is already of the type you need.
Alright, I might have missed that bit. That's pretty much the idea I was suggesting.
@L B

@TheIdeasMan I cannot have any code in base classes that deal with or account for specific types. Specific types can inform managing classes that they exist, however.


.
..
-No subclass-specific support in base classes
-Polymorphism and multiple virtual inheritence w/o issue or double calls

Not even 1 virtual function in the Living Class that takes a pointer to a Living Thing?

You seem to imply, that there is a lot of overhead in doing this - I am saying there isn't.

There is not a function for each object type in the derived classes, nor are there overloaded functions to do the same thing. It is important that the function is not a pure virtual function, because one does not want to redefine all of them. The Animal class has a virtual function which is inherited.

In this situation, it is better to have double function calls (Pollinate for the Bee, virtual PollinateMe for objects) IMHO

.... there would be a vector<LivingThing> that included all living things, a vector<Pollinateable> that included all pollinateable things, and a vector<Bee> that included all bees.


With my solution, there is only the need for one vector of LivingThings - which is unavoidable really.

If you have a Pollinate-able list then you have to perform a find operation on that list for every object landed upon by the Bee. That is O(m * n) efficiency in my mind. All the objects are m, and n is the number of pollinate-able. It is easier to call the objects virtual function which gives O(m).

Having a Bee vector makes it worse - now you potentially have O(m * n * b). With my solution each Bee has it's own Pollinate virtual function (if it's behaviour it different to other Bee's). Which still gives O(m)

..... rest assured I've been writing and testing code and it's all working flawlessly. ....


Now I respect your ability & knowledge & experience (Much more than mine all around, I think), but I can't help thinking that I should remind you that just because code works, it doesn't necessarily mean it is efficient or elegant. I am not try to criticise your ability to write good code, nor do good design. To be fair, I am sometimes guilty of thinking in C, then someone presents a far better C++ solution.

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

L B wrote:
Then all types must suddenly be aware of Pollinateable things whether they have anything to do with them or not, and must explicitly extend the correct base class. This is so far from possible that I'm not even going to try to think of a workaround.


Yes, that is right - my bad. Having such base classes isn't a natural use.

I still have the impression that you guys might not know about (I could be wrong) taking advantage of the PTABLE & VTABLE, which is at the heart of my solution . No one has commented on this, either way.

Any way, now that it is morning here, I will start writing my code. Will get back later this morning.
TheIdeasMan wrote:
Not even 1 virtual function in the Living Class that takes a pointer to a Living Thing?

You seem to imply, that there is a lot of overhead in doing this - I am saying there isn't.
I don't think you're understanding? This is not about efficiency, this is about extensibility without adding to existing code and recompiling.
TheIdeasMan wrote:
In this situation, it is better to have double function calls (Pollinate for the Bee, virtual PollinateMe for objects) IMHO
By "double function calls" I meant "the tick function gets called twice or more per tick because of Multiple Inheritance". My solution avoids this by having a separate tick function for each class in the object's inheritance tree - these are invoked properly by the manager classes because they take a reference to the Manager that invoked them. Polymorphism saves the day. If I had had only one tick function, then there would be a problem, because with objects ending up in multiple lists, their tick function would be called multiple times.
TheIdeasMan wrote:
If you have a Pollinate-able list then you have to perform a find operation on that list for every object landed upon by the Bee.
Why would a Bee ever be told about an object that is not Pollinateable within the context of its pollination routine? With my solution, objects are only made aware of the objects they need to be aware of, and there is no "checking" to see if an object is of a certain type or if it is in a certain list. You don't check your cereal to see if it is in your bowl before you decide to eat it; it's already in the bowl, just eat it.
TheIdeasMan wrote:
but I can't help thinking that I should remind you that just because code works, it doesn't necessarily mean it is efficient or elegant.
It's certainly elegant (afaik what most people consider elegant), and there doesn't seem to be anything blatantly inefficient about it like the nested iterations you were describing - I think you've misunderstood me.
TheIdeasMan wrote:
I still have the impression that you guys might not know about (I could be wrong) taking advantage of the PTABLE & VTABLE, which is at the heart of my solution . No one has commented on this, either way.
I think you're confused - my solution relies on polymorphism, aka the compiler doing vtable magic for most popular implementations. At least, I think that when you say vtable you're referring to the most common polymorphism implementation - are you not?
Last edited on
@TheIdeasMan
I know what a virtual table is, but google isn't helping with ptable.
@L B

I think you're confused - my solution relies on polymorphism, aka the compiler doing vtable magic for most popular implementations. At least, I think that when you say vtable you're referring to the most common polymorphism implementation - are you not?


Yeah look, I think we may be saying the same things, but coming from a different direction, or expressing it differently. My comments are base on my interpretation of what you are saying - it is possible that we have both misunderstood each other. It is also possible that our solutions are similar, but I wasn't getting that impression from your description. With out seeing your code I couldn't say. I am nearly finished coding my solution and will post it soon. Rather than having this huge debate, we should just compare code?

This part might be kind of similar to mine:

By "double function calls" I meant "the tick function gets called twice or more per tick because of Multiple Inheritance". My solution avoids this by having a separate tick function for each class in the object's inheritance tree - these are invoked properly by the manager classes because they take a reference to the Manager that invoked them. Polymorphism saves the day. If I had had only one tick function, then there would be a problem, because with objects ending up in multiple lists, their tick function would be called multiple times.


Except I only have one list - all the things.

Just to reiterate, I wasn't saying your code was inefficient or inelegant - just that it might be possible there is a better algorithm - I should have said that the first time - my bad, that was confusing. Again this is all based on my interpretation of your description.

..... to be anything blatantly inefficient about it like the nested iterations you were describing - I think you've misunderstood me.


I was wondering how you could avoid the find operation on the list of Pollinate-able things. If the Bee lands on M different things, and there are N things on the Pollinate list - how is that not O(M * N)?

It sounds like the Bee is only going to land on N Pollinate-able things, but that is contrary to the original problem IMO (could be construed as cheating :D), because the type isn't known until one gets there. Same for the Block problem - a player moves from block to block and only finds out what sort it is once it is on it.

@naraku9333

The ptable is very similar to the vtable, in that each class in the inheritance tree internally maintains pointers to it's parent and child classes. This is used to implement casting up & down, and it is also why a pointer to a derived class is a valid pointer to a base class, because the compiler can follow the pointers from the derived to the base class. The vtable works exactly the same way to determine which virtual function to call. It is also the cause the Diamond inheritance problem, because it means there are ambiguous vtable pointers.

That was just my general understanding of it.
First some Notes:

This is all a bit rough & ready (It is bares bones functionality), but enough to get my idea across.

Would have better if there was a class factory rather than individually creating all the objects.

It doesn't matter how many animals there are, they all have the same virtual function, which has no need to be redefined.

Flowers and Bees could be easily modified by redefining it's virtual function so that they can have different behaviour, like accepting / delivering different amounts of pollen. That is just run of the mill virtual functions which I am sure you guys know inside out.

The key to it all is declarations like these:

1
2
 
std::vector<CLiving*> VTRpCLiving ;


virtual bool PollinateMe(CBee *Bee) ;

bool CBee::Pollinate(CLiving* Thing) {

And the fact that they can be called with pointers to any derived class, like this:

pHoneyBee->Pollinate(VTRpCLiving.at(i));

There is only one list of all the things. There is no casting or RTTI. There is no cheating by making a list of flowers. There is no find operation. There is support for derived classes in the base class - but it is no big deal.

The only multiplier is the number of different bees, I guess this corresponds to multiple players in a game. If that was going to happen on a large scale then a design pattern would be better.

Anyway, I am interested to see what you all think.

OK, here are some of the relevant files - not all, there are 20 of them in total. If you want the rest, I can send them.

main.cpp

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <vector>
#include <algorithm>

#include "CLiving.h"
#include "CBear.h"
#include "CBee.h"
#include "CBumbleBee.h"
//#include "CFlower.h"
#include "CHoneyBee.h"
#include "CLily.h"
#include "CRose.h"
#include "CTiger.h"


using std::cout;
using std::cin;
using std::endl;

int main() {
	
	//create vector of ptr to living things
	std::vector<CLiving*> VTRpCLiving ;
	
   //create some living objects animals & flowers
	//have to use new so pushback will work with derived class ptrs
	//taking address with & doesnt work
	CBear *pBear = new CBear();
	CTiger *pTiger = new CTiger();
	CRose *pRose = new CRose();
	CLily *pLily = new CLily();
	
	VTRpCLiving.push_back(pBear);
	VTRpCLiving.push_back(pTiger);
	VTRpCLiving.push_back(pRose);
	VTRpCLiving.push_back(pLily);
	
	CHoneyBee *pHoneyBee = new CHoneyBee();
	CBumbleBee *pBumbleBee = new CBumbleBee();
	
	for (std::size_t i=0; i<VTRpCLiving.size(); i++){
		pHoneyBee->Pollinate(VTRpCLiving.at(i));
		pBumbleBee->Pollinate(VTRpCLiving.at(i)) ;
		
	}
   
    return 0;
}


The Output:


Honey Bee Can't Pollinate me I am a Bear
Bumble Bee Can't Pollinate me I am a Bear
Honey Bee Can't Pollinate me I am a Tiger
Bumble Bee Can't Pollinate me I am a Tiger
Honey Bee is Pollinating:Rose
I am a Rose Pollen Accepted

Bumble Bee is Pollinating:Rose
I am a Rose Pollen Accepted

Honey Bee is Pollinating:Lily
I am a Lily Pollen Accepted

Bumble Bee is Pollinating:Lily
I am a Lily Pollen Accepted


Living.h

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
#ifndef CLIVING_H
#define CLIVING_H

#include <string>

//#include "CBee.h"
class CBee;

class CLiving
{
protected:
	
	CLiving(); //Cant make an object of this type
	
	std::string m_Name;
public:
    
    virtual ~CLiving();
	virtual bool PollinateMe(CBee *Bee) ;
	std::string Name() const ;
};

#endif // CLIVING_H



Rose.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "CRose.h"
 

#include <iostream>

CRose::CRose()
{
	m_Name = "Rose";
}

CRose::~CRose()
{

}
bool CRose::PollinateMe(CBee *Bee) {
	std::cout << Bee->Name() << " is Pollinating:" << this->Name() << std::endl
;
	std::cout << "I am a " << m_Name << " Pollen Accepted"<< "\n"<< std::endl;
	return true;

}


Animal.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "CAnimal.h"
#include "CBee.h"

#include <iostream>

CAnimal::CAnimal() {}

CAnimal::~CAnimal(){}

bool CAnimal::PollinateMe(CBee *Bee) {
	std::cout << Bee->m_Name << " Can't Pollinate me I am a " << m_Name <<
std::endl;
	return false;
}


Bee.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "CBee.h"

#include <iostream>

CBee::CBee()
{

}

CBee::~CBee()
{

}
bool CBee::Pollinate(CLiving* Thing) {
	if (Thing->PollinateMe(this)) {
		m_GoodPollCount++;
		return true;
	}
	else {
		m_BadPollCount++;
		return false;
	}
 
}


TheIdeasMan wrote:
There is support for derived classes in the base class - but it is no big deal.
It's a huge deal, actually - this is the number one thing I have to avoid at all costs. I'll have no control over what classes will be created, so there is no way for me to account for them in the base class. It is also poor class design to have the base class have functionality that only applies to a limited set of subclasses.
TheIdeasMan wrote:
38
39
40
41
42
43
44
45
	CHoneyBee *pHoneyBee = new CHoneyBee();
	CBumbleBee *pBumbleBee = new CBumbleBee();
	
	for (std::size_t i=0; i<VTRpCLiving.size(); i++){
		pHoneyBee->Pollinate(VTRpCLiving.at(i));
		pBumbleBee->Pollinate(VTRpCLiving.at(i)) ;
		
	}
These bees are not within the main object list, therefore each one is its own list. You now have three lists, not one.

Your solution works when all potential interactions are known by accounting for such interactions in the base class. In my case, all potential interactions are unknown and therefore cannot be accounted for in the base class. This is what I mean when I said you were misunderstanding me. I know how your solution works, and it doesn't solve my problem.



Here's a quick watered-down type-up of my solution (I'm somewhere where I don't have access to my actual code). Assume that things like the copy/move constructors and assignment/move operators are properly defined or deleted and that all destructors are defined properly and are virtual.
1
2
3
4
struct Manager
{
    virtual void Manage() = 0;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct ThingManager;
struct Thing : public virtual Cloneable
{
    virtual void Tick(ThingManager &manager) = 0;
};
struct ThingManager : public virtual Manager
{
    virtual void Manage()
    {
        for(unsigned i = 0; i < things.size(); ++i)
        {
            things[i]->Tick(*this);
        }
    }
private:
    std::vector<Thing *> things;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct RockManager;
struct Rock : public virtual Thing, public virtual Cloneable::Auto<Rock>
{
    virtual void Tick(ThingManager &manager)
    {
        //display the rock
    }
    virtual void Tick(RockManager &manager)
    {
        //give the rock some wear and tear
    }
};
struct RockManager : public virtual ThingManager
{
    virtual void Manage()
    {
        for(unsigned i = 0; i < rocks.size(); ++i)
        {
            rocks[i]->Tick(*this);
        }
    }
private:
    std::vector<Rock *> rocks;
};
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
struct FlowerManager;
struct Flower : public virtual Thing, public virtual Cloneable::Auto<Flower>
{
    virtual void Tick(ThingManager &manager)
    {
        //display the flower
    }
    virtual void Tick(FlowerManager &manager)
    {
        ++pollen;
    }
private:
    unsigned pollen;
    friend struct Bee;
};
struct FlowerManager : public virtual ThingManager
{
    virtual void Manage()
    {
        for(unsigned i = 0; i < flowers.size(); ++i)
        {
            flowers[i]->Tick(*this);
        }
    }
private:
    std::vector<Flower *> flowers;
    friend struct BeeManager;
};
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
36
37
38
39
40
41
struct BeeManager;
struct Bee : public virtual Thing, public virtual Cloneable::Auto<Bee>
{
    virtual void Tick(ThingManager &manager)
    {
        //display the bee
    }
    virtual void Tick(BeeManager &manager)
    {
        //buzz
    }
    virtual void Tick(BeeManager &manager, Flower &flower)
    {
        std::swap(pollen, flower.pollen); //pollinate
    }
private:
    unsigned pollen;
};
struct BeeManager : public virtual Manager
{
    BeeManager(FlowerManager &flowermanager) : fm(flowermanager)
    {
    }
    virtual void Manage()
    {
        for(unsigned i = 0; i < bees.size(); ++i)
        {
            bees[i]->Tick(*this);
            for(unsgned j = 0; j < fm.flowers.size(); ++i)
            {
                if(rand()%2)
                {
                    bees[i]->Tick(*this, *(fm.flowers[j]));
                }
            }
        }
    }
private:
    Flowermanager &fm;
    std::vector<Bee *> bees;
};
When instances are created they automatically add themselves to their respective manager(s) (so the Manager parameters are for function overloading & polymorphism only). I didn't have time to include that code but I will. Just know that there is a master list of Managers and every tick they have Manage called on them. Obviously there are a lot of interfaces such as LivingThing, Pollinateable, Pollinator, etc. and they all have managers. Essentially, the entire class layout including MI will have a mirrored Manager responsible for creating interactions, getting new instances, and deserializing without static factories. The friend declarations would also only bee for the abstract interfaces, so that more types of bees and bee managers could be added without having to modify the definitions of Flower or FlowerManager, or it could be done without the friend declarations if need be. The point is, relationships can be created without affecting the common base class.

I'll work on making this more closely mirror the system in my game and then I'll put it on GitHub and link it here. It's going to take some work because my setup is designed to load plugins and such and I'll have to refactor the parts where it instantiates everything so that it works for the example.
Last edited on
OK, I have had a quick look - it's nearly 3am at my end, so a detailed response will have to wait until I have had some shut eye, but I will ask these quick questions:

Obviously there are a lot of interfaces such as LivingThing, Pollinateable, Pollinator, etc. and they all have managers.


You have said that you understand my code, so I will restrict my comments to the differences between yours & mine.

Is each Type of Bee going to have it's own manager? The same for each of the other types - every type of Flower, every type of Rock, every type of Animal etc? If you had a Bear and a Tiger would they have their own mangers, or would there be a manger for Animals? That's a lot of managers. It seems like the inheritance tree for all things is duplicated with a tree of Mangers - maybe I have that wrong.

I also notice you have pure virtual functions - so this means the will have to be redefined for every derived class.

My code cuts down on the number of interfaces. Anything that isn't pollinate-able (Animals, rocks or whatever) will have the same virtual function (not pure) that is as high up the inheritance tree as possible. Bees & Flowers are the only that need to modify (redefine) their not pure virtual functions, and only when necessary because they exhibit different behaviour.

I will admit that my code has less efficiency when there are more bees. That is number of Bee types multiplied by number of all things. That is 2 lists btw - All things list and Bees list, not 3 lists. I am not sure yet whether yours is any different in terms of efficiency.

As I said earlier, if it really complex (like unlimited players with lots of Monsters, Tools, valuable objects, everyone versus everyone, in an on-line game), then it needs a Mediator Design Pattern.

Any way i am falling asleep, so will catch up later. zzzzzzzzzzzzzZZZZZZZZZZZ

Last edited on
closed account (o1vk4iN6)
@L B

Why do you inherit from other managers ? I assume you never actually use the vector from ThingManager in FlowerManager, BeeManager, etc... just seems like unneeded overhead. When a Bee is created it is added to the ThingManager and the BeeManager but not the ThingManager in BeeManager, yes ?
Last edited on
TheIdeasman wrote:
Is each Type of Bee going to have it's own manager? The same for each of the other types - every type of Flower, every type of Rock, every type of Animal etc? If you had a Bear and a Tiger would they have their own mangers, or would there be a manger for Animals? That's a lot of managers. It seems like the inheritance tree for all things is duplicated with a tree of Mangers - maybe I have that wrong.
This is correct and intentional, and also required - it's essentially emulating static polymorphism.

TheIdeasMan wrote:
I also notice you have pure virtual functions - so this means the will have to be redefined for every derived class.
In C++, Java, and many other object-oriented languages, only the most direct subclass must provide a definition for pure virtual member functions in its base classes. If the provided definitions are not also pure virtual, further subclasses are not required to provide different implementations.

TheIdeasMan wrote:
My code cuts down on the number of interfaces. Anything that isn't pollinate-able (Animals, rocks or whatever) will have the same virtual function (not pure) that is as high up the inheritance tree as possible. Bees & Flowers are the only that need to modify (redefine) their not pure virtual functions, and only when necessary because they exhibit different behaviour.
The goal is to have more interfaces, and to keep things at the most derived level possible. I'm shooting for ease of extensibility, not minimal code.

TheIdeasMan wrote:
I will admit that my code has less efficiency when there are more bees. That is number of Bee types multiplied by number of all things. That is 2 lists btw - All things list and Bees list, not 3 lists. I am not sure yet whether yours is any different in terms of efficiency.
If you provide different code for different objects at the iteration level, they are different lists. I can see now that your example can be refactored to have only two lists - earlier I was just looking directly at the two different lines in the for loop.

TheIdeasMan wrote:
As I said earlier, if it really complex (like unlimited players with lots of Monsters, Tools, valuable objects, everyone versus everyone, in an on-line game), then it needs a Mediator Design Pattern.
The managers are mediators.

xerzi wrote:
Why do you inherit from other managers ? I assume you never actually use the vector from ThingManager in FlowerManager, BeeManager, etc... just seems like unneeded overhead. When a Bee is created it is added to the ThingManager and the BeeManager but not the ThingManager in BeeManager, yes ?
That is the side-effect of not having all the interfaces that I have in my actual code. In reality a concrete manager will only ever be extending an abstract manager. They must extend each other to take advantage of polymorphism - there is a master list of Manager * and each one has Manage() called. In my watered-down example the abstract managers were omitted because it would have taken up extra room and I didn't have time. In my actual code there is no duplication of the vectors, the inheritance is purely polymorphic.
With the number of lists in my code - you are right in a way. IMO, it is 2 lists one multiplied by the other - which is what happens in the for loop I had. However it is not as bad as it sounds, because a bee does not visit every object in the environment (they have a "flight plan" which is a direction, then they rely on colour to determine what to land on). Just the same as the player of a game does not visit every tile on a game map, only the ones of interest.

With the efficiency, I am not sure that it is something that can be avoided, no matter what design pattern is used. If you have 10 bees, that have to land on 20 objects - then there will be 200 function calls. Just the same as 10 Players who interact with 20 game objects.

This is correct and intentional, and also required - it's essentially emulating static polymorphism.


The goal is to have more interfaces, and to keep things at the most derived level possible. I'm shooting for ease of extensibility, not minimal code.


The managers are mediators.


My code is easy to extend.

I would have thought that decoupling should be one of your goals. The Mediator Design Pattern is supposed to have ONE Manager to avoid many-to-many relations. Just like a table in a RDMS placed btween 2 others to avoid M-T-M. Having multiple managers that are friends with each of their targets, doesn't sound any different (probably worse) than the M-T-M you had in the first place.

For reference to Design pattern examples:

http://www.vincehuston.org/dp/


The game analogy is best I think. This is the multi-player, multi-enemy, all vs all internet game.

On one side you have multiple players who want to perform multiple actions of different types - Look, Shoot, Acquire, Place bomb, Fight etc.

On the other side you have a Map Tile, which could have various things that occupy it - another player, monster, ammunition, weapons, trigger some action (floor collapses say).

In the middle there is the one and only Mediator. It has one Execute function (not overlaoded), which takes pointers to the base classes Player, Action and Tile - as parameters. It can then carry out the action on the thing that occupies the tile.

There are some other possible solutions like the Command Design Pattern.



TheIdeasMan wrote:
My code is easy to extend.
It is impossible to extend without modifying the base class.
TheIdeasMan wrote:
In the middle there is the one and only Mediator. It has one Execute function (not overlaoded), which takes pointers to the base classes Player, Action and Tile - as parameters. It can then carry out the action on the thing that occupies the tile.
Then the TileManager class has a public interface for the Player and PlayerManager classes to use.

I only used "friend" in my example to save writing time.
It is impossible to extend without modifying the base class.


If you mean extend the inheritance, then I disagree. The Base class virtual functions have base class pointers as parameters, so there is no need to change them. When these functions are called, they are given derived class pointers as arguments. This is the whole point of what I have been saying.

However, maybe you mean to extend the functionality by adding another Action (Apart from pollinate). That would be best done (In the Mediator scenario) by adding a new action to the Action tree. In my code, I don't see it as a hardship to add a new function to a base class, because it is new behaviour and it is best added as high up the tree as possible.

When you go to extend yours - you need to write a new manager it seems.

Although, with this:

In my case, all potential interactions are unknown and therefore cannot be accounted for in the base class.


Why is that? In a game all Actions must be known beforehand, so you can code them. Did you mean that you want to add new Actions easily to the code, without having to rewrite other code to accommodate them?

Then the TileManager class has a public interface for the Player and PlayerManager classes to use.


That is the whole point of using the Mediator Design Pattern (or one of the others maybe) - There is only ONE manager (The Mediator).

If you meant adding a Player Manager between the Player and the Mediator - I am struggling to see the point of doing that. The management should be done by the mediator.

The Player deals with the Mediator's Execute function, and it calls the right function for the Tile. There is no interaction between Player & Tile.

I only used "friend" in my example to save writing time.


But that increases the coupling - something to be avoided IMO. I can't help thinking that if you implement this Design Pattern correctly then you will have much less code to write.

Gee, maybe I should some Mediator code as an example.
I don't know who keeps reporting posts, but that's not a nice thing to do.(I say that like it has any significance :p)
TheIdeasMan wrote:
In my code, I don't see it as a hardship to add a new function to a base class, because it is new behaviour and it is best added as high up the tree as possible.[/code]It is definitely a hardship when some poor plugin developer wants some interaction between his two classes and h has to ask me to make an update to the program to support it.[quote=TheIdeasMan]When you go to extend yours - you need to write a new manager it seems.
Why wouldn't I? There is a manager for every class that will be a game object, abstract or not, and no more. All classes will provide functionality to interact with eachother via their public interfaces and their managers. The managers will always be defined in the same place as the classes themselves.
TheIdeasMan wrote:
Why is that? In a game all Actions must be known beforehand, so you can code them. Did you mean that you want to add new Actions easily to the code, without having to rewrite other code to accommodate them?
Partially that is a goal, but the main thing is that this is a plugin-based engine. All code will be plugins, and I'll not be the only one writing plugins. Therefore I cannot know what interactions will take place and account for them because I can't see the future or read people's minds. It also seems really ugly to put these very subclassed interactions at the top level base class.
TheIdeasMan wrote:
There is only ONE manager (The Mediator).
Then we'll say I'm using my own design pattern :p
TheIdeasMan wrote:
If you meant adding a Player Manager between the Player and the Mediator - I am struggling to see the point of doing that. The management should be done by the mediator.
Definitely not - both Player and Tile will have PlayerManager and TileManager, and all four will be aware of eachother and have public interfaces to interact with. The PlayerManager cannot be instantiated without being given and instance to a TileManager, but it still only manages players. Players can ask their manager about nearby tiles.
TheIdeasMan wrote:
The Player deals with the Mediator's Execute function, and it calls the right function for the Tile. There is no interaction between Player & Tile.
The player asks its manager for nearby tiles that it is allowed to interact with, and then the player calls member functions on those tiles via the public interface. A TileGUI could do the same thing so that when someone presses the buttons it adjusts the tile.
TheIdeasMan wrote:
But that increases the coupling - something to be avoided IMO. I can't help thinking that if you implement this Design Pattern correctly then you will have much less code to write.
It was to save time writing the example. I will not be using "friend" like that in my actual code (and am not). The code seems excessive now because it doesn't do much, but the goal is that it is easier to add to it later because of things accounted for first of all. While some people are "better late than never", I'm "I'd better do it now because my future self won't even remember", so this results in a lot of preliminary code writing.

And again, whoever is reporting posts, there is no reason to do so with these posts, so please stop.
Edit: I wrote this before I read you last post - I must learn to type faster !!

Seems we have a serial reporter - No worries, with any luck their account will be closed for them.

@L B, hopefully I am not coming across as a very persistent dog with a bone, but I have some more thoughts & suggestions. Hope you don't mind all this debate, just that it is my critical thinking kicking in, hopefully it will result in a better solution, and things learnt on both sides. I hope you don't mind me explaining things in detail, that you might be very well familiar with - I cannot know whether you do know these in detail. I also try to keep a realistic view of where I stand compared to others in terms of knowledge & experience etc - I am much further down than you I think.

With your example, you seem to be using the Prototype Design Pattern to clone your Managers. As I said earlier this increases the coupling big time and might not do anything to solve M:M relations.

On the Mediator pattern there is an example of the Unix System with users & groups:

http://www.vincehuston.org/dp/mediator.html


So this is M:M (Many to Many) relations and solved by putting the mediator between users & groups, so now there are 1:M:1 (1 to Many to 1) relations.

Your code seems to do this (I have renamed 1 of the users):

Tom ---> TomManager
Richard -----> RichardManager (aka DickManager :+D)
Harry ----> HarryManager

ADM ----> ADMMgr
DEV ---------> DEVMgr
ROOT -------> ROOTMgr

Now I am not sure whether there is M:M relations between the managers, or M:M between User Managers & Groups, and M:M between Group Mangers & users. Either way it hasn't done anything to solve M:M, in fact it is worse because all these managers have been written, and we still have M:M.

I am trying to think about how this might work exactly with a Design Pattern or Patterns to model Players, Actions, and Tiles which could be occupied by various things. It is very easy to think of very complex scenario's, with M:M everywhere.

The Mediator seems like a good candidate, but others like Command seem to have merit too.

Just wondering whether this scenario would work:

Create a tree of Action classes:
- LookInto Look into something to find a resource;
- Acquire ammunition, Weapons, Medic Packs etc;
- AskToJoinForces Collaborate with other players;
- Attack needs a Weapon argument;
- Operate a lever to open a door or something.

Create trees of Players, Enemies, Weapons, resources classes.

Create a Tile class which knows what is occupying it.

Create a GameMap with Rooms, Containing Tiles.

Create the Mediator with 1 Execute function that takes pointers to Action and Tile. Mediator could also have a ReturnMsg function so results can be passed back to the originator. The call to Execute can be used by any Player or Enemy.

The Tile knows which particular action to carry out because of the virtual functions associated with what ever object is occupying it. Invalid operations like Acquire when there is a Monster there, are dealt with by the virtual function at the top of it's respective tree.

It is scalable because:

To add more Players, Enemies Weapons etc - just add them and their behaviour (virtual function) into the respective tree;

To add more Actions, add them to the Action Tree. Add virtual functions to the tree of objects that need to react to them (as high as possible).

Hopefully I haven't worn you out with all this - Look forward to your reply.








Last edited on
Not worried about being reported - it makes no difference to me. Only to admin - not someone to annoy.

OK, I see what you are saying about plugins. Are you part of an Open Source project?

Does this become a project management question then? Would a discussion about design patterns happened fairly early on in the process?

Maybe you are using different Pattern's or combo of Pattern's that I am not aware of, and is way over my head.

I am still not sure about this:
It also seems really ugly to put these very subclassed interactions at the top level base class.


The interactions are the interface to the classes. They are not subclassed because they take pointers to the base class. Here is another example:

1
2
3
4
5
6
7
class CUnits {
public:

virtual CUnits* ToMetric(CUnits* Imperial) ; 
virtual CUnits* FromMetric(CUnits* Metric) ;

};


The virtual functions only need to be added where they are needed.

Whatever works for you is fine, as long as the project manager is happy - then it is all good. As I said before, it is my critical thinking kicking in, nothing like having a good ole go at it.

Thanks for all your time explaining your side of the argument.

Cheers
Last edited on
Pages: 123