Sign in to follow this  

How much OOP is too much OOP?

This topic is 3864 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm building an RPG in Flash, so I don't know how well you guys will be able to give an exact idea of what I'm asking, but that's ok. I'm currently putting together the character/creature construct (basically the same). I want to have about 20 or 30 core skills (covering everything from aspects of combat to different magic skills, etc), all of which are derived from and modified by a combination of character attributes, current circumstance, and equipped items. So the way I was thinking about setting it up, would be to have attribute class/object, an item class/object, and a skill class/object (I'm just talking about these three classes for this question). The character class creates a set of attribute objects, and then a set of skill objects. Each skill object is assigned a parent attribute, and also when an item is equipped during gameplay, the character class assigns relevant attribute and skill objects a pointer to that item. This way whenever a skill check is needed, it begins with its base number, but then looks at the governing attribute (which might be damaged/enhanced) for a modifier, and then looks up items that are assigned to it, which may or may not be full strength at the time (etc), and thus arrive at a final modified number. My questions is though: Since I want to use the same construct for all creatures, NPCs and whatnot, is that going to be overboard on the instantiated objects? Say 10 creatures with active behavior - 10 x 30 skill objects, 10 x 10 attribute objects, etc... Is it better in this case to simplify? does it matter? Just looking for very general advice here. My OOP ventures have thus far been quite small-scale. Cheers, p

Share this post


Link to post
Share on other sites
Depends.

Flash isn't known for efficiency, but I don't have enough experience to know where it matters.

In C++, especially in combination with boost, templated classes are instantiated by possibly thousands, even tens of thousands - but the compiler is smart to reduce them to bare minimum.

Java has no problems, tens of thousands of classes don't matter. Instantiating tens of thousands of objects per second doesn't matter either (ok, there's better ways to solve such cases, but not necessary)

OO is about making your intentions clear. Design is what's important. If you feel you have too many different classes, rething your design - are all really necessary? Are some merely duplicating others? Do they represent distinct quantities?

There is no "too much" or "too little". Find class relations that describe your object model.

Also, make sure you understand the difference between a class and an instance. Class is definition. Instance is each copy of class that is created. Classes are unique - a Point class is only one. But you will instantiate hundreds, or thousands of classes.

If you only make 1 class for 1 instance, then that's redundant. Having one single class for entire application, that's not enough.

Share this post


Link to post
Share on other sites
It's not that I think I have too many classes, but rather that I don't want to hurt performance by having too many instantiations of those classes.

I could keep the attributes of a create in the creature class as variables, but that's not how I want to organize and work with the data. I would like to isntead organize it as I described above, but not at a severe cost to performance comparatively. As you said - Flash is not efficient, so it counts.

I appreciate your introductory OOP advice, but I am pretty comfortable with object oriented design and am not new to OOP. It's just the OOP I've done has usually been fairly small scale - for example a game where there might be 10 creatures, or 20 alien spaceships, but they have only a few properties each and a very rudimentary A.I.

Cheers,
p

Share this post


Link to post
Share on other sites
This is too much OOP:


// C++ STL
std::transform(myString.begin(), myString.end(), myString.begin(), std::tolower);


It's not so much a problem of object orienting your game as going completely overboard and overdesigning.

Design what you need, not what you think might be cool in 0.00001% of the usage cases.

Note: I'm not particularly responding to your post, I'm ranting on stupid STL design. Your design is more of the high-level idea than actual implementation, so I can't really comment.

Share this post


Link to post
Share on other sites
Quote:
I appreciate your introductory OOP advice, but I am pretty comfortable with object oriented design and am not new to OOP. It's just the OOP I've done has usually been fairly small scale - for example a game where there might be 10 creatures, or 20 alien spaceships, but they have only a few properties each and a very rudimentary A.I.


10,000 isn't too many.

With flash you need to be more careful about invocations, allocations and such. But the total number of objects only impacts memory use. Other performance factor, as always, is the cost of allocation and de-allocation.

So more importantly than how many you have (only limited by memory) is how long it takes to construct them.

The rest doesn't have an impact.

Share this post


Link to post
Share on other sites
Well what I'm doing right now is focusing 100% on design, to create the best workflow for a complicated combat and gameplay engine down the line.

I was just concerned that say 10 or 20 creatures with 10 attributes, 30 skills, 5 equipped items, etc...might end up being too many instances.

But if C++ can do tens of thousands, I'll trust Flash to handle a few hundred without skipping a beat.

It just has notoriously bad runtime performance.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren
This is too much OOP:


// C++ STL
std::transform(myString.begin(), myString.end(), myString.begin(), std::tolower);
Yeah, see, the problem with that is that line is typical of functional programming in C++ rather than object oriented. It's pretty much a map operation that writes back into the original storage.
Quote:

But if C++ can do tens of thousands, I'll trust Flash to handle a few hundred without skipping a beat.

It just has notoriously bad runtime performance.
I believe the new Flash/AS 9 use a JIT engine for their runtime now, so performance might be a lot better now than it used to be.

Share this post


Link to post
Share on other sites
Not to mention that the "annoyances" with that std::transformline are mostly an unfortunate side-effect of the verbosity imposed upon you by C++ itself, and have little to do with the underlying concept, which is quite elegant and sound.

In any case a more illustrative example might have involved iostream_iterators, bind1st, mem_fn, or all of the above...

Share this post


Link to post
Share on other sites
Quote:
Original post by Promit
Quote:
Original post by Nypyren
This is too much OOP:


// C++ STL
std::transform(myString.begin(), myString.end(), myString.begin(), std::tolower);
Yeah, see, the problem with that is that line is typical of functional programming in C++ rather than object oriented. It's pretty much a map operation that writes back into the original storage.


Notice that there is no std::string::to_lower method to compete with the std::transform way of doing it. That's what I meant by bad OOP design. You have a very common operation and force the programmer to jump through bizarre hoops just to do something simple.

As far as the functional aspect goes, this pattern is sweet. I can replace std::tolower with anything that operates on the elements of the string (I could encrypt a container using the 5-argument version of std::transform and pass in two separate iterator pairs and an encryption functor). But as far as the lack of a well developed string class goes it is an unacceptable alternative.

A better alternative in the functional style would have been to define a container interface (ala .Net), and a function signature like "std::transform(container, functor);"

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren
Notice that there is no std::string::to_lower method to compete with the std::transform way of doing it. That's what I meant by bad OOP design. You have a very common operation and force the programmer to jump through bizarre hoops just to do something simple.

As far as the functional aspect goes, this pattern is sweet. I can replace std::tolower with anything that operates on the elements of the string (I could encrypt a container using the 5-argument version of std::transform and pass in two separate iterator pairs and an encryption functor). But as far as the lack of a well developed string class goes it is an unacceptable alternative.

What? None of that has anything to do with OOP. It's just functional programming, made slightly more difficult by C++'s container idioms and the lack of a string tolower function.

Share this post


Link to post
Share on other sites
Quote:

But as far as the lack of a well developed string class goes it is an unacceptable alternative.

Should have picked a better example, then. The SC++L string class is a bit iffy; in conforming to the container interface, it ends up with a lot of extra member functions that aren't that useful for the common practice of "being a string," and lacks some of those functions that would be more useful (such as a native to_lower). But good arguments could be made for both high-level design choices, I don't think this is a case of "too much" (object obsessed) design at all, just a case of everyday design tradeoffs that in retrospect turned out to maybe not be so optimal.

Quote:

A better alternative in the functional style would have been to define a container interface (ala .Net), and a function signature like "std::transform(container, functor);"

It would look cleaner, but it would break the ability to use the SC++L algorithms on native types (such as arrays).

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by Nypyren
Notice that there is no std::string::to_lower method to compete with the std::transform way of doing it. That's what I meant by bad OOP design. You have a very common operation and force the programmer to jump through bizarre hoops just to do something simple.

As far as the functional aspect goes, this pattern is sweet. I can replace std::tolower with anything that operates on the elements of the string (I could encrypt a container using the 5-argument version of std::transform and pass in two separate iterator pairs and an encryption functor). But as far as the lack of a well developed string class goes it is an unacceptable alternative.

What? None of that has anything to do with OOP. It's just functional programming, made slightly more difficult by C++'s container idioms and the lack of a string tolower function.


I wasn't trying to focus on the functional code itself (it's debatable whether performing a mutable string operation is actually functional), I was trying to get people to wonder why there is not a tolower method on the std::string CLASS itself. std::string is a class, in a language touted for object oriented features, not for functional features. I apologize for not being painfully direct in my posts.

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Quote:

But as far as the lack of a well developed string class goes it is an unacceptable alternative.

Should have picked a better example, then. The SC++L string class is a bit iffy; in conforming to the container interface, it ends up with a lot of extra member functions that aren't that useful for the common practice of "being a string," and lacks some of those functions that would be more useful (such as a native to_lower). But good arguments could be made for both high-level design choices, I don't think this is a case of "too much" (object obsessed) design at all, just a case of everyday design tradeoffs that in retrospect turned out to maybe not be so optimal.

Quote:

A better alternative in the functional style would have been to define a container interface (ala .Net), and a function signature like "std::transform(container, functor);"

It would look cleaner, but it would break the ability to use the SC++L algorithms on native types (such as arrays).


Obviously native "int foo[]" arrays don't have their own iterators in C++, either, because they are not a class. The iterators you use for them are external, providing access to native types, and as such there is nothing preventing you from also creating container objects that reference an underlying native type in the same manner.

Share this post


Link to post
Share on other sites
Making all operations on an object into member functions isn't necessarily good OOP practice. Many people (myself included) think that anything that doesn't have to be a member function (i.e. doesn't need access to private fields) should be a free function. std::string has been repeatedly criticized for this, by Herb Sutter and others.

Share this post


Link to post
Share on other sites
Free functions are fine with me as long as you organize them into namespaces. Nothing's worse than forgetting the name of a utility function that's out in the global namespace full of a few hundred thousand different definitions.

If you are a newbie programmer and you're using C, how do you first learn about the innumerable functions in the global namespace like strlwr? Probably looking at examples or browsing through MSDN or other online documentation. But how long does it take you to learn all of the useful functions? It could be months to years as you learn new APIs.


In .Net, you make your object, type its name, press '.', and pretty much everything that you can do with the class is immediately apparent in a concise auto-complete list. Sometimes you have to refer to documentation, but that's usually for the most advanced topics. You can learn everything about a class in a half hour, and start to get familiar with any classes that interoperate with it just as quickly. Without a graph-like organization your learning scope is quickly overwhelmed. Your ability to employ RAD is limited by your ability to know how to do what you want to do. If you don't already know then your best friend is OOP design that is easy to understand.

If you're completely new to an API, it's far easier to learn what objects provide particular functionality if it is present in the classes themselves. I've personally seen this to be one of the biggest productivity issues in professional game development, with the gigantic existing codebases that accumulate over a decade worth of "code re-use". No single coder is possibly going to remember where everything is, so you'd beter organize it so that anyone can quickly find what you need. It is not just your productivity on the line, it's everyone else's, too.

Share this post


Link to post
Share on other sites
Sorry if this is inappropriate to this discussion, but by this time in a thread like this... I was fully expecting some response like this:

ANY OOP is too much OOP. The following is a fully functional Haskell program that you would be proud to show your mother


j[7]:=(*^)??:{]==

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren
Free functions are fine with me as long as you organize them into namespaces. Nothing's worse than forgetting the name of a utility function that's out in the global namespace full of a few hundred thousand different definitions.


Now you're being inconsistent. std::transform is in a namespace - 'std'.

Quote:
In .Net, you make your object, type its name, press '.', and pretty much everything that you can do with the class is immediately apparent in a concise auto-complete list.


No; in your IDE, that happens. Intellisense exists in C++. It's not very good, but that's because of the horrible warts on the language grammar left over from its C legacy, not because of how the library is organized.

Quote:
Your ability to employ RAD is limited by your ability to know how to do what you want to do.


Some would argue that RAD isn't accomplished by typing in code, full stop...

Share this post


Link to post
Share on other sites
There's a simple rule. What's should be an object, should be an object. What shouldn't, shouldn't. In this regard, Java is flawed. The writers of the Java standard library really hate verbs. Instead of a function called 'ProcessElements', they have a class called 'ElementProcessor'.

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren
Obviously native "int foo[]" arrays don't have their own iterators in C++, either, because they are not a class. The iterators you use for them are external, providing access to native types, and as such there is nothing preventing you from also creating container objects that reference an underlying native type in the same manner.


Native arrays do have an iterator type. It is an element pointer.

int a[size];

sort(a,a+size);

Share this post


Link to post
Share on other sites
Quote:
Original post by Nypyren
I was trying to get people to wonder why there is not a tolower method on the std::string CLASS itself. std::string is a class, in a language touted for object oriented features, not for functional features. I apologize for not being painfully direct in my posts.

So to you, OOP equals putting *everything* inside the same class?
The ultimate OOP design would be to have one master class that contains every bit of functionality you might need to operate on its data (which obvious is everything)

Sure, that's an extreme example, but consider, why should something like tolower() be a member function? Yes, it's a common operation, but I don't recall reading any OOP gurus say "all common operations shall be made members of the class they operate on". If that was followed strictly, we would quickly end up with the one master class described above.

Quote:
Obviously native "int foo[]" arrays don't have their own iterators in C++, either, because they are not a class. The iterators you use for them are external, providing access to native types, and as such there is nothing preventing you from also creating container objects that reference an underlying native type in the same manner.

They do have iterators. External or not, they work like iterators, so they are iterators.
But please show how you'd implement a tolower() member function on a char* [wink]
That's one reason why it makes some sense to have it as a free function. It's not tied to any particular representation of the string, they just have to have iterators you can work on. Allowing me to reuse the tolower() function on my own custom string class sounds object-oriented to me. Modularity, loose coupling, code reuse and all that.

Why should I restrict my tolower() function to only work on *my* strings, when the exact same code could work on *any* string type, even native ones like char*'s?

Share this post


Link to post
Share on other sites
Quote:

I wasn't trying to focus on the functional code itself (it's debatable whether performing a mutable string operation is actually functional),


Off topic, I know, but why do you view that as being debatable? A string is just a list of characters. Mapping a function over a list is as functional as it gets.

Share this post


Link to post
Share on other sites
In functional languages, immutability is much more common than mutability. The mapping mutates the string in place, wich arguably makes it less functional.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ahnfelt
The mapping mutates the string in place, wich arguably makes it less functional.


It's indeed less functional. But it's still more functional than it is object-oriented.

Share this post


Link to post
Share on other sites

This topic is 3864 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this