What's wrong with OOP?

Started by
25 comments, last by Madhed 12 years, 1 month ago
@Hodgman: Thanks for provide the low level point of view. It's nice to know such disadvantage for coupling between functions and data, which I never think of before. However, I don't understand the 3 empty bytes for padding? Shouldn't all variable be contiguous?

One of the complain about OO is that it does not reflect the real world because not everything in the eral world is object. However, I think people play with word on this one rather focus on the idea. Let's not call it object, but call it concept. An object in OOP reflects a real world concepts, not an object as in noun. For instance, kicking is an action which can be modeled by a class named Kick along with its attributes to present it as a concept:


class Kick{
private:
int velocity_;
int force_;
public:
Kick(int velocity, int force):velocity_(velocity),force_(force){}
virtual ~Kick(){}

void high(Fighter target){ /*implement high kick */ }
void mid(Fighter target){ /*implement mid kick */ }
void low(Fighter target){ /* implement low kick */ }

int get_velocity() const { return velocity_; }
int get_force() const { return force_; }
};

class Fighter{
private:
int health_;
int ki_;
Kick kicking_stance_;
public:
enum AttackPosition {
high,
mid,
low,
};

FIghter(int health, int ki, int kick_stance):health_(health), ki_(ki), kicking_stance_(kick_stance){}
virtual ~Fighter(){}

void kick(Fighter target, AttackPosition pos){
if (pos == high) kicking_stance_.high(target);
else if (pos == mid) kicking_stance_.mid(target);
else kicking_stance_.low(target);
}
};


So, what's the problem here? I think one of the reason people complain that is, Kick is not an object in real world itself. Only live entity like human, horses can kick. Another example is, if I got an Invoice class, should object of invocie, invokes actions like invoice.save(), invoice.send()? For this reason, we have patterns, design and design principles because pretty much people can invent different ways to present a concept. As a consequence, OO is accused for low reusability, despite the abstraction is for concept reusability. In the example above, other people might put Kick concept inside an abstract class Fighter, while other might define the MartialArtStyle abstract class, and put kick action inside it as a abstract method. For this reason, it's more difficult to reuse, since if there's a more complicated object system, a member function of an object may operate on the abstract data type of that object, and inside the abstract data type, it operates on other abstract data types as well.

This is what I got from the articles. Correct me if I'm wrong.

However, I still don't think it's the fault of the paradigm, but rather, the language. Let's consider another scenario:

  • An ancient wizard has a powerful spell which can turn objects (such as tea cup) into living entity like human being.
  • There's a tea cup in front of him
  • The old wizard casts the spell on the tea cup to make it live.

In language like Java, the problem is that I have to plan ahead for TeaCup object. If I want to have a tea cup to behave like human, I have to:

  • Write an interface for shared human behaviors, such as talk(), walk(), and let TeaCup implement it.
  • Since not all TeaCup can talk, and I want to model the real work closely so other can understand, the TeaCup must not always talk.
  • So, I define a TeaCup hierarchy, which (for simplicity, I will create one) has TalkingTeaCup inherits from TeaCup (which is the normal one), and implements the shared human behaviors interface.

Look at this way of constructing concept, we can see that it creates overhead. In this case, instead of a tea cup with has varied behaviors, we got different versions of TeaCup which varies a little in behaviors. This is just another simple case. Imagine if it applies for a large and complicated system.

There's no way for me to keep the TeaCup object as simple as it is, because TeaCup is a TeaCup, it can't operate on it own until the wizard gives it a life. Thus, TeaCup must not contain any related behaviors to make it "lively" (to prevent issue like whether invoice should save() / send() or not). Dynamic behaviors will be added later after the wizard casts life spell on TeaCup.

I don't think we can add methods to TeaCup if we don't write it in class or implement via interface. Although, Java can dynamically add more class with varied parameter at runtime with Dynamic Object Mode (which we have to do more work)l: http://dirkriehle.co...005/plopd-5.pdf . In C, we can have an array of function pointers, however, this approach is not convenient, since it operates on low level, so it's error-prone and it is strong typing, which make it less flexible.

With function as first class object, it creates a more elegant way to solve the above problem. Consider this piece of Lisp code (I'm learning, so pardon me if it's not elegant) written for this scenario:


//I have to use C comment because our forum does not support Lisp comment style.

//collection of wizard spells. In Lisp, a variable is just a list, which might be a single value, or a list of symbols/values/functions
(defparameter *wizard-spells* nil)

//class object with its attributes consists of name and action
(defstruct object name action)

/* This is the wizard spell for giving an object a life. Here you can see, upon calling this function, it will add function talk and walk into the variable action of the class object */
(defmethod giving-life ((o object))
(princ ( type-of o))
(princ " is now as lively as human!")
(push #'talk (object-action o))
(push #'walk (object-action o)))

//wizard spell for making an object become poisonous, so if someone contacts with the object, they will die a moment later
(defmethod poison ((o object))
(princ ( type-of o))
(princ " is covered with poison."))

//talk action, which is a human behavior
(defun talk ()
(princ "Hello I can talk now"))

//walk action, which is a human behavior
(defun walk ()
(princ "Hello I can walk now"))

//add spells to spell collection of the wizard
(push #'giving-life *wizard-spells*)
(push #'poison *wizard-spells*)

//create a teacup
(defparameter *tea-cup* (make-object :name "TeaCup" :action nil))

//funcall will execute the functions inside the variables.
(funcall (nth 1 *wizard-spells*) *tea-cup*)
(funcall (nth 0 (object-action *tea-cup*)))
(funcall (nth 1 (object-action *tea-cup*)))


In sum, OOP creates or more flexible way for "creativity" (in which we call design, where people can create different abstractions for the same concepts. This might be a good or bad thing). Those are the ideas I "suddenly" got it after practicing with Lisp for a while. Not sure if it's to the point or not, so If you guys can verify it, I am really thankful.
Advertisement
Part of it comes down to this: OOP applied to the correct problem in the correct way is pretty great -- OOP applied to the incorrect problem in the correct way, or the correct problem in the incorrect way is hell, and OOP applied to the incorrect problem in the incorrect way will make you want to shoot yourself in the face (or at least throw away all the code and start over biggrin.png).

OOP languages tend to be much more unfriendly to re-factoring (especially if the original factoring violated OOP principles like SRP) -- bad OOP tends to be made up of unruly hierarchies, tightly-coupled classes, and metaphors that have been stretched to the point of breaking -- It's a mess to clean up. Other paradigms tend to either be more flexible (by focussing less on rigid structure) such as procedural languages like C, or to strongly discourage poor design from the first line of code (either by force or by accepted idioms) such as functional languages like Haskell.

There's also a correlation between approach-ability in a language, and the quality of its practice. That is, the type of people who are capable of thinking about anything non-trivial in a functional language are rather few, hence elite, and so the community around, say Haskell, is rather strongly made of very intelligent people -- even relative idiots who stick with Haskell long enough will pick up good practices. On the other hand, the type of people who can think about problems using an "object" metaphor are common (in part due to industry/accadamia focus on OOP) and so they are not necessarily the cream of the crop to begin with, hence the number of elite member of that community relative to the total size of the community is not high enough to provide the sort of "herd immunity" that languages with a higher ratio of elites exhibit. In short, more elite communities discourage poor practice, while broader communities tolerate (or even promote, in the new/less-educated factions) poor practices.

throw table_exception("(? ???)? ? ???");


@Hodgman: Thanks for provide the low level point of view. It's nice to know such disadvantage for coupling between functions and data, which I never think of before. However, I don't understand the 3 empty bytes for padding? Shouldn't all variable be contiguous?


It has to do with memory alignment, in c++ (on x86 atleast) objects will be aligned to the nearest 4 bytes, This is because the CPU has a 4 byte word size and always fetch memory in chunks that start at an even 4 byte offset, (if the memory isn't aligned then performance will be worse)
http://en.wikipedia....cture_alignment

This is all well and good when you work on single objects, However, the general case tend to be to have multiple objects of the same type that you work with and then the extra padding results in less efficient use of the cache. (bigger objects means you'll fit less objects in the cache), also, if you are going to update 300 enemies it is likely that you will run the update method for all of them and change the position of all of them, Thus it is better for the cache to have enemy positions stored close to eachother.

Data oriented design attempts to solve this by having you structure your data based on how it is used rather than what it represents.(Enemies class with a vector of positions and a single method to update all enemies) rather than an Enemy class that you store in a vector for example)
This is in my opinion alot harder to do intuitively (simply replacing a vector of objects with an object with vectors isn't always optimal either (if you have both a velocity and a position then you probably want to store it something like x,vx,y,vy,z,vz using a single vector (as the velocity is usually added to the position it helps to keep it close) while for example the health value is better stored on its own or maybe close to the armor value.).

It really is all about tradeoffs, OOP is a way to keep large projects from collapsing under their own complexity and it is very good at it when done right, Sometimes other paradigms can make code clearer(Not everything can be cleanly modelled as a set of objects) and sometimes you have to sacrifice clarity for performance but OOP is a reasonably decent default choice.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!

(if you have both a velocity and a position then you probably want to store it something like x,vx,y,vy,z,vz using a single vector (as the velocity is usually added to the position it helps to keep it close)

No you wouldn't. You might have them adjacent, but even then that's doubtful. You will be more likely to have two separate arrays, one for position and one for velocity. Having them interleaved the way you showed would be the worst case option, as it eliminates almost any possibility to use SIMD or any other vector instructions (on your choice platform) to perform the operation.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Even ignoring SIMD, interleaving like that makes no sense. In your typical game, you might update the position once per frame per object from velocity, whereas there's a ton of use cases that can end up at higher use counts where you simply don't care about velocity and it's merely wasting cachelines and memory bandwidth at best:

Rendering (both for culling and for setting up transforms)
AI (pathfinding, LOS / sight distance checks, target prioritization)
Gameplay (distance based damage calculations like AoE falloff, fog of war calculations)

With seperate arrays, you're potentially halving your required cache/bandwidth from getting positions if you only care about the position.

Interleaving can speed things up by reducing bandwidth during random access when you're using all the fields, but even then you're going to gain less than nothing by interleaving on a per-component basis.
I think the reason why OOP gets dumped on a lot is because of the examples people use when explaining how OOP should work. These examples are ridiculously contrived and if you coded them in a non OOP language it would take half the code or less and be easier to read.

Things like classifying animals like cats and dogs to show inheritance, or representing a car as a vehicle with a certain number of wheels. ARGH!

I think if you can grok the idea behind OOP and get past trying to model real world objects as classes, OOP can make coding (and maintenance) of complex systems MUCH easier. That said, OOP is no silver bullet. It's like saying "Concrete is the best construction material". There's plenty of situations where that is a colossally bad idea, like say when you're making a car or a rowboat. The same applies to OOP.
[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

[quote name='SimonForsman' timestamp='1331940672' post='4922698']
(if you have both a velocity and a position then you probably want to store it something like x,vx,y,vy,z,vz using a single vector (as the velocity is usually added to the position it helps to keep it close)

No you wouldn't. You might have them adjacent, but even then that's doubtful. You will be more likely to have two separate arrays, one for position and one for velocity. Having them interleaved the way you showed would be the worst case option, as it eliminates almost any possibility to use SIMD or any other vector instructions (on your choice platform) to perform the operation.
[/quote]

Good point, i didn't think of simd at all and as mauling monkey correctly notes, the normal usecase for games is different aswell.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
A lot of problems with OOP stem from the way it is badly mis-taught to programmers. The recent push for data oriented design is related, but it isn't quite the same. The trouble is that object oriented programming as presented in most educational material is badly flawed. It does not reflect real world usage of objects, it does not reflect real world problem solving, and it certainly isn't a good way to think about code.

We learn in school and in books that object oriented programming is invariably applied to logical, human based objects. I've heard this referred to as "noun-based" design, and frequently imposes constraints that have everything to do with a human abstraction and nothing to do with the technical problem we're dealing with. The problem is further exacerbated by is-a and has-a relationships, UML diagrams, design patterns (GoF), and all that other crap that passes for "object oriented software engineering". Then you wind up with projects like Java, blindly applying the OO principles in the most pointlessly abstract terms imaginable.

And to top it off, most of this code never turns out to be "reusable". The biggest promise of OOP simply does not deliver. And the tight coupling headache leads us down a whole new rabbit hole of design patterns, IOC, and stuff that lands us so far in abstracted land that we've lost sight of what it was we were trying to do. I understand why the guys who think we should have stuck to C feel that way, though they are misguided.

The object as a unit of control and a point of polymorphism is, in itself, a useful tool. Functions are useful tools. Combining the two gives us an enormous amount of flexibility, and largely defines what an OO language is all about. (Hi Java, you piece of moronically designed trash.) But it is absolutely critical to dispense with the notion that either of these tools must or even should correlate to any kind of normal human concept. And I for one am finding that particular habit to be enormously difficult to get away from.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

A lot of problems with OOP stem from the way it is badly mis-taught to programmers. The recent push for data oriented design is related, but it isn't quite the same. The trouble is that object oriented programming as presented in most educational material is badly flawed. It does not reflect real world usage of objects, it does not reflect real world problem solving, and it certainly isn't a good way to think about code.

Can you please give an example between simple which "does not reflect real world usage of objects" (such as Animal, Shape, Car examples) and real world examples how OOP is applied.

We learn in school and in books that object oriented programming is invariably applied to logical, human based objects. I've heard this referred to as "noun-based" design, and frequently imposes constraints that have everything to do with a human abstraction and nothing to do with the technical problem we're dealing with. The problem is further exacerbated by is-a and has-a relationships, UML diagrams, design patterns (GoF), and all that other crap that passes for "object oriented software engineering". Then you wind up with projects like Java, blindly applying the OO principles in the most pointlessly abstract terms imaginable.[/quote]
Underlying of human abstraction is still the technical problems we have to deal. What's wrong with creating a high level point of view which is close to human, and solve its technical underneath?

And to top it off, most of this code never turns out to be "reusable". The biggest promise of OOP simply does not deliver. And the tight coupling headache leads us down a whole new rabbit hole of design patterns, IOC, and stuff that lands us so far in abstracted land that we've lost sight of what it was we were trying to do. I understand why the guys who think we should have stuck to C feel that way, though they are misguided.[/quote]
Can you give an example how OOP is not reusable? I think if something which can be packaged into libraries and is independent of any application code, it is already resuable? With this definition, resuable is presented in any language and programming paradigm? Or is OOP less resuable?

Finally, I wonder, if OOP is so bad, why is it so widely used, especially in game industry (this is what I read and heard of)?

Underlying of human abstraction is still the technical problems we have to deal. What's wrong with creating a high level point of view which is close to human, and solve its technical underneath?

For me the GameEntity is a good example. From the "human high level point of view" it sounds fine to say "See, a tree is a GameEntity, the player is a GameEntity, NPCs are GameEntities". But then you end up putting this insanely bloated interface on the GameEntity and somehow trees end up with interfaces for conversations etc.



Finally, I wonder, if OOP is so bad, why is it so widely used, especially in game industry (this is what I read and heard of)?
[/quote]
It's not so much that OOP in general is bad, but it often gets blindly applied even when it is not a good fit for the problem at hand.

This topic is closed to new replies.

Advertisement