The 5millionth post on OO design principles..

Started by
51 comments, last by Remnant 23 years, 3 months ago
I guess I have two questions. The easy one first : 1) Can anyone recommend a book that has a solid section on principles of code design when following OOP? I''ve been programming for the past 6-8 years, but become interested in OO for only this past year, so I''m interested in a book that gives a more in-depth look at OO use in the real world: Writing proper OOP code without resorting to inefficienct bloat or work-arounds is what I am interested in. I''ve heard good things about Design Patterns : is it worth the purchase price? 2) Now for my actual situation. It is a common thing in my current game engine for an object in the world, to contain inside of it other objects (in an array, whatever) that define its behavior. Example : a spaceship has a set of engines, generators, guns, etc. These objects are literally modular parts that are plugged into the ship in-game; the problem stems from the interaction between a parent object and its parts. The problem is, these part need access to nearly all of the information of their parent ship. In the case of an engine, it needs to modify the ship''s velocity vector when its Act() function is called; a generator needs to supply power to other parts on the ship; an autonomous turret needs targeting information from the ship''s sensors; etc. To achieve this, I have been letting the parts keep an Owner pointer, from which they can call appropriate functions on the ship; the engine calls a setvelocity() function, a generator setenergy(), etc. However. I don''t really like this system. I''m keep a lot of extra pointers around, for one thing. I''ve also heard that having to do this is a sign of improper OOP: what would be the proper way of having this sort of modular part design? I can''t simply pass in the data on calling the module''s Act(), since the ship doesn''t know what part in its part container is what, and trying to do so is a bad idea (see the other OO thread going on about linked lists) The issue becomes more acute for "special" modules. These have varying and often quite wild effects on their parent ship. For example, there might be a part that allows for cloaking, a part that gives temporary invulnerability, etc. These parts have to directly change crucial inner data for the ship (its draw function, its sensor appearance, damage reception routine, etc), So the abstract question is : how do you best achieve data-sharing and modification between an owner class and a contained class? And then how to achieve radical behavioral (functional) changes in the owner class by functions in the parts class? OO always make so much sense in abstract, but I get into problems when getting into the actual implementation... After getting into quagmires like this, it makes me grumble and just want to go back to C. - Remnant
- (Steve Schmitt)
- Remnant- (Steve Schmitt)
Advertisement
A couple of thoughts.

Let''s take your ship example. Ideally the game engine itself shouldn''t be calling the update function on each individual part of the ship. Instead it should just say Ship->Act(). And then the ship should update it''s individual parts. Now that the ship object is calling the update on each part, it can, if it feels like pass a pointer to itself to the parts. That way the engine doesn''t need to store an owner pointer, but it has access to it when it needs it.
Also ideally the ship should know what kind of parts it has attached. So when it tells the engine to update, the engine might just update it''s thrust internally. Then the Ship uses thrust and mass to determine it''s velocity. So the Ship->Act() function might look like:
Ship::Act() {  m_pEngine->Act();  velocity = m_pEngine->GetThrust() / m_iMass;} 

Or if the generator supplies energy, and components use energy your code might look like:
Ship::Act() {  m_iEffectiveMass = m_iMass;  m_pGenerator->Act(this);  m_iEnergy = m_pGenerator->GetSuppliedEnergy();  m_pEngine->Act(this);  m_pShield->Act(this);  m_pSensors->Act(this);  m_pMisc1->Act(this);  m_pMisc2->Act(this);  m_pBattery->Act(this);  velocity = m_pEngine->GetThrust() / m_iEffectiveMass;}Engine::Act(Ship * owner) {  int EnergyReceived = owner->RequestEnergy(10);  m_iThrust = 10 * EnergyReceived;}Battery::Act(Ship * owner) {  m_iEnergyStore += owner->RequestRemainingEnergy();  if (m_iEnergyStore >= StoreMax) m_iEnergyStore = StoreMax;}InertialDampner::Act(Ship * owner) {  int EnergyReceived = owner->RequestEnergy(3);  owner->m_iEffectiveMass /= (EnergyReceived + 1);} 
1) Design Patterns is well worth buying. While it''s not about code design it will teach you about many common OO problems and patterns that you can use whilst doing your design. Try the Pattern Languages of Programming (PLOP) series for similar reasons and the C++ Gems books might even interest you. Do you have C++FAQs?

2) I can''t add to this and think SiCrane should be commended for writing a pretty good clear response.
I've finally finished reading Design Patterns, and though most of it doesn't apply to games (or least I didn't understand how :p), a few do.

I use the Factory pattern. Alot. I also use Chain-Of-Command (as described by SiCrane above) quite a bit, in all my windows programs (i have used this method before, just never called it CoC).

The material in the book isn't exactly unique, its more like a compendum of OOD methods (which are called patterns). None of the techniques were particularily hard to implement, and it gives you a series of concrete examples of 'when this pattern applies' that I think is very useful. The factory method, unlike CoC, never occurred to me before I read that book. It was one of the first ones, and my jaw dropped when I read it.

A FSM pattern would be nice...

...
To acheive the radical special effects you could use a decorator class. A decorator encapsulates the parent (CShip) class, and gets first crack at any method calls.

Something like
    class CCloakedShip : public CShip{//override//cloaked ships can't be detectedBOOL OnDectect(float fDetectionLevel){return(FALSE);}//cloaked ships can't fireBOOL OnFireWeapon(DWORD dwWeaponID){return(FALSE);}//let everything else pass onto CShip};    

Using this method you can add new effect that are not part of the original design, without affecting that design.
A CShip* can point to a CCloakedShip, and so long as OnDetect() & OnFireWeapon() in CShip are virtual, everything works.

This is where you pay a performance penalty, its not much, only one extra deference for each OnFire & OnDetect call. So having virtuals on your render path is bad. Having a virtual for OnDetect & OnFire won't matter because they only happen a few times a second max.

If cloaking or something else, where a common thing I'd add support directly in the class, like a bool m_bCloaked.



Magmai Kai Holmlor
- The disgruntled & disillusioned


Edited by - Magmai Kai Holmlor on December 7, 2000 1:43:19 AM
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Just bought the Design Patterns book over the weekend and I''d recommend it. Even only 20 pages in I was impressed.

Now I''m only a decade behind on the SE stuff!
That feeling quickly fades after the first 20 pages, it starts becoming a big yawn after the initial wow factor wears off.
Still very useful stuff, but it reads like the text book that it is - and not like TotWGPG or even Petzold''s Win32 book.

I was coming back to add that; Every time some one has posted an OOD question they''ve gotten less flames & more useful dicussion has ensued- so keep ''em coming!

...
There''s a small group of us here that believe OOD is the way, and I think we have convinced the other guys to ''just leave the crazy ones alone''

Magmai Kai Holmlor
- The disgruntled & disillusioned
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Just noticed the timing of this thread. Check out the design patterns section I just put on GDNet tonight: http://www.gamedev.net/gdpatterns.

Kevin

thanks for the replies. I can see that it would be no problem ditching the owner pointer I''ve been keeping internally and just passing it in; even for a 5000-called-per-second routine, 1 more push onto the stack per, is negligable. My previous method of getting the parts to produce what they need is far more clunky than what you outlined there, Si.

Magmai,
I''ve thought of using decorators before, but the problem, as I see it, is that they''re a tough fit for my code. For example, a player might find a cloaking unit somewhere and install it onto their ship; there''s no clean method that I know of to replace the current instance of the ship (and all the pointers to it), with the new, decorated copy.

Also, I remember reading an interesting post on usenet a while back regarding OO choices. It was talking about OO in the realm of RPGs, and the interesting distinction it made was how an actor should NOT be subclassed by way of profession; because of this exact problem, a profession (or special ability in my case) can change at any time.

Example :

Character X is-a human. X currently works as-a peasant. X can go to school and become a mason, and gain all the abilities that that entails. Then X could move on and become a warrior, changing abilities yet again. Throughout all this though, X is still the same person, just with different skills.

This is a problem because a naive class hierarchy would look like:

Object -> Mobile -> Player -> Player_Peasant
Player -> Player_Mason
Player -> Player_Warrior

however this doesn''t allow for a human changing jobs, nor for having multiple professions at once. (nor, in my case, for having multiple special parts onboard). And of course, in this context, its clear that you may also want to have other Mobile-subclasses take on those professions; you probably will want NPC_Warriors and NPC_Peasants, for example.

This pops up in all sorts of other places too, sometimes where I barely recognize that its the same design problem.

I don''t want to get sidetracked about if the last example is best for writing RPG classes or not, but it DOES help exposese the problem I''m talking about from a different angle. Sometimes that helps percolate new ways to address the problem..

- Remnant
- (Steve Schmitt)
- Remnant- (Steve Schmitt)
I borrowed design patterns from my company library and returned it just recently, so I don''t have it on hand. From that, I think the "Visitor" pattern also might work.

Your ship would have a list of components that are all "visitable". That is, they each inherit the interface "acceptVisitor (Visitor& v)". The ship on each update creates a Visitor object, whose purpose is to collect information about the components'' actions for that tick. It iterates through the list and passes this Visitor object to each component''s acceptVisitor method. The component can then tell the visitor object what it''s doing. Once it''s done with the list, maybe the Visitor has the new calculated bearing, velocity, ship state, whatever, and then the ship can act on that.

Design Patterns tells it better. You can also have multiple types of visitors for different types of algorithms or actions. It''s pretty neat. I gotta buy that book for myself.
Wow, I love the idea of having a design patterns section that we can all add to. It''ll help a lot of people out tremdously. I only wish that the patterns all had examples, as examples make it much easier to understand how to apply the pattern.


- Houdini
- Houdini

This topic is closed to new replies.

Advertisement