oop fundamentology question

Started by
5 comments, last by Hodgman 10 years, 2 months ago

I am not sure if i will be understood (If my question will be understood) but hope so

I am not using oop, and I think i do not understand its ideology, but there is some question i could ask and maybe answers would help me to better understand it

I understand in oop you can build some graph/net of objects where objects can seen each other through the references to other objects they hold as its own fields,

for example

class Game {

private:
Graphics& g;
Window& w;

public:

///

}

class Window {

private:

InputSystem& i;

Audio& audio;

public:

///

}

class Audio {
private:

HardwareSubsystem& h;

public:

////

}

etc, this is fictiuos example but it shows that in OOp you can build some net

of objects seeing each other through the references

My question is : is this kind of graph environment main purpose or such oop program; On the other words Is the main aim of oop coder to build such net

of objects which will be interfering each another throught this references

I dont know if this pure way of interacting between objects is the aim of this

I am asking becouse of that in oop languages like c++ for example you could

probably reach to other objects in some other way than through such kind of

references (would it be violation of oop rules for oop programmers?)

Other part of the question is that before you have the objects (like Game, Audio, Window here) and before you have setted the references between them you must

set up then manually (you must instantiate the objects somewhere in the code

tree and you must run also the code which will setup/bind the references - this is some kind of 'inner' and 'outer' sides of oop if you understand what i mean :

in inner side of the oop you got internal code of objects and references to other as its fields - in outer side of oop you must setup the objects and setup the references before the system of objects could work

is the main purpose of oop to work in this inner side (when you are not interested where objects was instantiated if it is global space, main function or field of another objects of local scope of some function - but only interested in the inners of objects and explicit references ) and the outer side is only used to setup the

thing -

or the outer side (and scope visibility and such things) is also a field where some oop techniques could be developed and used (or they should be avoided and only focused to develop the proper objecst graph)

I hope i will be understod, I am curious how indyvidual oop programmers see this thing (though i am not sure if i will be understood what i mean by inner and outer

side of oop)

Advertisement

I have been told that this kind of coupling between objects is a big no no! This is kinda seen as a "trick" to make OOP not be OOP.

The OOP purists out there will rather spend 500 lines of OOP code, building all kinds of interfaces and objects, where as a procedural programming can get away with less then 10. It is all about memory leakage, stacks overflowing, exceptions and so on. I think the idea is that if you abide by OOP you will be "safe".

Required reading cool.png laugh.png
http://en.wikipedia.org/wiki/Single_responsibility_principle
http://en.wikipedia.org/wiki/Open/closed_principle
http://en.wikipedia.org/wiki/Liskov_substitution_principle
http://en.wikipedia.org/wiki/Interface_segregation_principle
http://en.wikipedia.org/wiki/Dependency_inversion_principle
http://en.wikipedia.org/wiki/Loose_coupling
http://en.wikipedia.org/wiki/Law_of_Demeter
http://en.wikipedia.org/wiki/Separation_of_concerns
http://c2.com/cgi/wiki?CompositionInsteadOfInheritance

My question is : is this kind of graph environment main purpose or such oop program; On the other words Is the main aim of oop coder to build such net
of objects which will be interfering each another throught this references

If you model the program as a collection of objects, then yep, you'll produce a kind of graph like this, where you have a bunch of self contained 'classes' with relationships between each other. The relationships you've shown here are "has a" (composition), but there's many other kinds, such as "is a" (inheritance).
If you take a class on OO, you'll often be taught to plan these "graphs" using UML class relationship diagrams.

Also note that instead of the "Audio has a HardwareSubsystem" relationship shown in your example, you could instead have something like "Audio uses a HardwareSubsystem", e.g.
class Audio {
public:
void UpdateHardwareBuffers( HardwareSubsystem& );
};

is the main purpose of oop to work in this inner side (when you are not interested where objects was instantiated if it is global space, main function or field of another objects of local scope of some function - but only interested in the inners of objects and explicit references ) and the outer side is only used to setup the
thing - or the outer side (and scope visibility and such things) is also a field where some oop techniques could be developed and used (or they should be avoided and only focused to develop the proper objecst graph)

No, you're using OOP in both of those situations.
e.g. your "outer" code might look like this, but it's still OOP as you're working with OO objects and relationships:


int main() {
  InputSystem input;
  HardwareSubsystem hwAudio;
  Audio audio( hwAudio );
  Window win( input, audio, 640, 480 );
  Graphics gfx( win );
  Game game( gfx, win );
  game.Run();
}

This code is often called "glue code", because it's setting up the relationships between actual objects (the relationships between classes was already coded earlier - in your example code), or gluing objects together. You will find this kind of "glue code" inside the main function for sure, but you will also find it inside more complex classes (so it appears at both "inner" and "outer" levels).

One of the key ideas in OO is that you can create more complex objects via composition -- by using simpler objects as building-blocks that get glued together, it's easy to construct larger, more complex objects.

I have been told that this kind of coupling between objects is a big no no! This is kinda seen as a "trick" to make OOP not be OOP.

Loose coupling is a good practice, but just because the above classes all have concrete dependencies doesn't mean that you're not using OOP...

If you do have too much coupling (each class knows about too many other classes), to the point where it's making your code brittle or hard to maintain, there's lots of methods to reduce coupling. e.g.


// Example original code -- Game is tightly bound to Inputs. This may or may not be an issue for you.
class Inputs { bool IsKeyDown(char); }
class Game {
  void Update( Inputs& in ) {
    if( in.IsKeyDown( 'W' ) )
      DoStuff();
  }
};
...
Inputs i;
Game g;
g.Update( i );
 
// Fix #1 - Create a Keyboard interface. Game and Inputs now both know about Keyboard, but Game now doesn't know about Inputs any more.
interface Keyboard { bool IsKeyDown(char); }
...
class Game {
  void Update( Keyboard& in ) {
    if( in.IsKeyDown( 'W' ) )
      DoStuff();
  }
};
...
class Inputs : public Keyboard { bool IsKeyDown(char); }
...
Inputs i;
Game g;
g.Update( i );
 
// Fix #2 - Game requires a lambda instead of an interface. Game no longer knows about inputs, but will interact with anything that can supply a valid lambda.
class Game {
  void Update( function IsKeyDown ) {
    if( IsKeyDown( 'W' ) )
      DoStuff();
  }
};
...
Inputs i;
Game g;
g.Update( i.IsKeyDown );

// Fix #3 - Game requires a template class instead of an interface. Game no longer knows about inputs, but will interact with anything that fulfils the usage patterns (non-statically typed langauges, e.g. Lua, Javascript, etc, will work this way by default!)
class Game {
  <template<class T> void Update( T& in ) {
    if( in.IsKeyDown( 'W' ) )
      DoStuff();
  }
};
...
Inputs i;
Game g;
g.Update( i );

The OOP purists out there will rather spend 500 lines of OOP code, building all kinds of interfaces and objects, where as a procedural programming can get away with less then 10.

That's not paradigm A vs paradigm B. That's good code vs bad code, or enterprise code vs sane code.

Unfortunately, you're likely to find a lot of "enterprise programmers" within OOP culture, whereas you're likely to find a lot of "80's hackers" in procedural culture. That's just stereotyping cultures though, not a property of the paradigms.

@up allright, tnx, i understand it though i need a little time to get it more claryfied

though I got yet some general question about oop:

are here some set of rules defined how it should look like

or at least some set of sub rules what should be not used,

or in hgeneral this is a 'free wrestling' way where you could

write it anyway you like?

yet as to my question - in such example i wrote you

got a set of objects glued by references and only allowed

way would be reaching the other objects by this explicit

references - Is this reccomended way? I mean other kind

of referencions banned (the other would be mainly direct

reaching for other objects data or functions

(by direct i mean without pointer - just by use avaliable

scope visibility)

I must say that i dont see clearly the rules that would

rule in this field, i f i got no rules i could do many strange things

here (and many strange things are done here by mane people

here) Is there one official reccomended style maybe or there are

very many styles here?

is the main purpose of oop to work in this inner side (when you are not interested where objects was instantiated if it is global space, main function or field of another objects of local scope of some function - but only interested in the inners of objects and explicit references ) and the outer side is only used to setup the
thing - or the outer side (and scope visibility and such things) is also a field where some oop techniques could be developed and used (or they should be avoided and only focused to develop the proper objecst graph)

No, you're using OOP in both of those situations.
e.g. your "outer" code might look like this, but it's still OOP as you're working with OO objects and relationships:


int main() {
  InputSystem input;
  HardwareSubsystem hwAudio;
  Audio audio( hwAudio );
  Window win( input, audio, 640, 480 );
  Graphics gfx( win );
  Game game( gfx, win );
  game.Run();
}

This code is often called "glue code", because it's setting up the relationships between actual objects (the relationships between classes was already coded earlier - in your example code), or gluing objects together. You will find this kind of "glue code" inside the main function for sure, but you will also find it inside more complex classes (so it appears at both "inner" and "outer" levels).

PS I would call such glue code always as 'outer' code, because it

is only a setup code for the real environment of objects, where the real code works (so this glue code is some kind of outer even if it is spreaded across the whole source - it also has no real purpose except of setupping the object environment - or no? it is also probably mostly unchanging i mean when references between objects are setupped they rarely change in runtime - if they will change the object graph of a program would be changing in time and i think it is assumed to be static - or no? (this is maybe not so important but those are my thoughts here, more important to my understanding would be if there is some reccomended style of oop (against other styles maybe and what are rules of it)

Not sure how experienced in the language you are, so if this is obvious forgive me, but are you familiar with the difference between a pointer (Sometype* name) and a reference (Sometype& name) in C++? It's fairly significant. I could be wrong, but I have never used or seen references used that way. I suppose it's legal, but you'll have to initialize those references with Object::Object(SomeObject some_value) : some_reference(some_value){}. Is that really what you wanted? Can it never reference nothing (as in nullptr/NULL)?

Also, referencing objects is only necessary for objects that the base object does not own. Ownership is not clear from your post, but say a Box owns 6 Sides, it would probably look more like

[source]class Box{

Side top,front,back,bottom,left,right;

};[/source] rather than a bunch of pointers to Sides (Side*) or worse references.

If a Train runs on a Railroad, which is presumably shared between many railroads, it might look more like

[source]

class Train{

Railroad* railroad;

};[/source]

Of course, this has its own problems. Raw pointers are easy to get wrong, but c++11 has introduced smart pointers to the stl (and boost has had them for ages)... but that might be a topic for another day. Coupling an object with a hard reference (SomeType&) makes it very hard to check if SomeType is even still around. Scoping also seems like it would become a real pain - and if you're dynamically allocating the memory, why go through the hastle of storing it's location as a reference instead of a pointer?

Then again, I'm no expert. Just chipping in.

it also has no real purpose except of setupping the object environment - or no?

No.
At every "layer" of the program, you're building larger objects by composing smaller objects. e.g. A std::vector might have struct{ T* buffer; int capacity; int size; } -- that's 3 primitive types grouped together to form a larger type. A "Scene" might use multiple vectors to store a bunch of objects -- composing those smaller types into an again bigger and more complex type. A "game" might combine a "Scene" with a "SceneLoader" and a "SceneRenderer" and a "CollisionSystem" to create a virtual world.
At each "layer" the abstraction/interface also changes. Vector allows you to push/pop "T" items. A scene allows you to create entities which optionally have Renderable and Collidable components. A game allows you to load a file from disk, which then creates a visual interactive world.
These "layers" all have one thing in common -- they take a whole group of smaller/simpler objects, and combine them in a particular way to create something bigger.
There isnt' just two layers -- inner and outer -- there's endless layers, each building upon the one below it (e.g. int/pointer -> vector -> scene -> game).


it is also probably mostly unchanging i mean when references between objects are setupped they rarely change in runtime - if they will change the object graph of a program would be changing in time and i think it is assumed to be static - or no?

As Zouflain was saying, you don't have to use unchanging references. e.g. struct HomingMissile { Entity* target } -- you may want to change which entity your homing missile is chasing.
There's a big difference between the object graph and the class graph. Here, a HomingMissile always has a link to an Entity. However, one particular HomingMissile may have a link to one particular Entity at a time, but different objects may have different links, and those links may change over time.

This isn't obvious in the example of the main function, because we only have one instance (object) of each class. Often in a game though, you will have hundreds of objects created from each class!
The relationships between the classes are unchanging (decided in the source code), but the relationships between specific objects can be very dynamic.

are you familiar with the difference between a pointer (Sometype* name) and a reference (Sometype& name) in C++? It's fairly significant. I could be wrong, but I have never used or seen references used that way. I suppose it's legal, but you'll have to initialize those references with Object::Object(SomeObject some_value) : some_reference(some_value){}. Is that really what you wanted? Can it never reference nothing (as in nullptr/NULL)?
...
Coupling an object with a hard reference (SomeType&) makes it very hard to check if SomeType is even still around. Scoping also seems like it would become a real pain - and if you're dynamically allocating the memory, why go through the hastle of storing it's location as a reference instead of a pointer?

In some projects I've worked on, this is extremely common. I actually recommend to use this style wherever possible.
A reference is just a pointer that is never null, and can never change it's value.
As for why - you've answered it above cool.png
You can't have objects in a state where they exist, but are unusable (e.g. a Graphics object exists, but you haven't called SetWindow yet).
It forces an obvious order of construction (e.g. in the main example, you can't create the graphics object until after the window object is created).
It forces you to get your lifetime/scoping rules right -- you're simply just not allowed to destroy the Window object before you destroy the Graphics object.
This last one is scary if you're coming from a GC language like C# or Java, but will be completely normal for programmers coming from C.
In C, you have to sit down and actually plan your object lifetimes and come up with a sensible design that takes ownership and memory lifetime into account. If so, then relying on these well designed lifetimes via references is no problem, and is a very natural way to document the design in the code.
In C#, you can be as lazy as you like and never stop to think about lifetimes... which will get you into trouble in C++ land. Even if you decide just to use smart-pointers, you still have to plan your design well enough that you know where to use shared_ptr and where to use weak_ptr and where to use unique_ptr... Otherwise you'll still end up with circular references and leaks.


Based on Zouflain's examples, here's a whole bunch of different types of "has a" relationships that you can use in C++:


//The railroad must exist before a train can be created.
//You promise that: the railroad will not be deleted until after all Trains on it are deleted (if you break this promise, your program will get crashes/corruption!).
//Once created, the train cannot move to a different railroad.
class Train{  Railroad& railroad;  };


//The train can exist without a railroad.
//The train does not own the railroad.
//The railroad can be deleted while there are trains on it (The train's railroad link will become NULL)
//The train can move between different railroads.
class Train{  weak_ptr<Railroad> railroad;  };

 
//The train can exist without a railroad.
//The train is a shared owner of the railroad.
//The railroad will not be deleted while there are trains on it (railroad will be kept around until all trains are done using it)
//The train can move between different railroads.
class Train{  shared_ptr<Railroad> railroad;  };
 

//The train can exist without an engine.
//The train is the only owner of the engine - when the train is deleted, the engine will also be deleted.
//The train can delete it's engine and switch to a new one at any time.
class Train{  unique_ptr<Engine> engine;  };


//The train always has an engine.
//The train is the only owner of the engine - when the train is deleted, the engine will also be deleted.
//The train cannot change engines.
class Train{  Engine engine;  };

This topic is closed to new replies.

Advertisement