| Grey Wolf (3232) | |||
| |||
|
|
|||
| TheIdeasMan (1753) | |
Thanks Grey Wolf.ThingsLearnt++;
| |
|
|
|
| kbw (5520) | ||
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
|
||
| L B (3806) | |||||||||||||
|
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.
@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.
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".
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! | |||||||||||||
|
|
|||||||||||||
| helios (10258) | ||
| ||
|
|
||
| TheIdeasMan (1753) | ||||||
@L B
.
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
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)
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.
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. | ||||||
|
|
||||||
| L B (3806) | |||||||||||
| |||||||||||
|
Last edited on
|
|||||||||||
| naraku9333 (1038) | |
|
@TheIdeasMan I know what a virtual table is, but google isn't helping with ptable. | |
|
|
|
| TheIdeasMan (1753) | ||||
@L B
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:
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.
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. | ||||
|
|
||||
| TheIdeasMan (1753) | ||||||||||||||
|
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:
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
The Output:
Living.h
Rose.cpp
Animal.cpp
Bee.cpp
| ||||||||||||||
|
|
||||||||||||||
| L B (3806) | |||||||||||||||||
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.
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
|
|||||||||||||||||
| TheIdeasMan (1753) | ||
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:
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
|
||
| xerzi (605) | |
|
@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
|
|
| L B (3806) | |||||||||||||
| |||||||||||||
|
|
|||||||||||||
| TheIdeasMan (1753) | |||||
|
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.
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:
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. | |||||
|
|
|||||
| L B (3806) | |||||
I only used "friend" in my example to save writing time. | |||||
|
|
|||||
| TheIdeasMan (1753) | |||||
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:
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?
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.
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. | |||||
|
|
|||||
| L B (3806) | |||||||||||||
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)
And again, whoever is reporting posts, there is no reason to do so with these posts, so please stop. | |||||||||||||
|
|
|||||||||||||
| TheIdeasMan (1753) | ||
|
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:
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
|
||
| TheIdeasMan (1753) | ||||
|
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:
The interactions are the interface to the classes. They are not subclassed because they take pointers to the base class. Here is another example:
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
|
||||