To OOP, or not to OOP?

Started by
40 comments, last by johnstanp 14 years, 2 months ago
Quote:Original post by howie_007
The benefit of OOP relies on how well you understand it and can make use of it. It really comes down to how well you can design your objects to make use of inheritance, polymorphism and the benefit is code reuse. The other thing you can make use of with C++ and OOP is the concept of designing your objects based on an interface. If you've programmed Java you should be aware of this concept.

Except that this doesn't work in practice. Requirements change, domains change - if you can't adapt fast, you lose.

The pure OOP model is perfectly fine if you can fully, completely and permanently model the domain. In some cases, this is possible. In such domains, the actual cost of code change is irrelevant.

And for quite a while now, the IOC and DI approaches have been used to avoid OOP pitfalls in strict-OO languages, especially with regard to code reuse, adaptability and testing.

Quote:I would say OOP all the way. The more you think about your code on a higher level (as objects and not a collection of function and global variables), the better you will get at it.


The opposite of OOP is not functions and global variables. Those are implementation detail, not design choice. OOP also doesn't mean stuff is in classes, and OOP can be perfectly trivially implemented in C and it doesn't even require convoluted arrays of function pointers.

The opposite of OOP is data-centric model (or even functional programming). There is a data store which holds entire state, and there are lightweight stateless manipulators or agents which traverse this state. Unlike OO, this model is I/O centric collection of black boxes.

Or - the unix model. Everything is a file, applications read from files and write to files. It's not perfect, but it just works. When requirements or domain changes, data remains intact, and just select few tools need to be adapted.

Practice shows that such models simply scale better - not in performance (although they usually do), but in more important aspects as well (maintenance, TCO, ...). Anyone who has worked with J2EE will gladly attest it is not a pleasant experience - when such developers migrate to RoR (a toy framework in comparison) they feel like they can fly - and yet achieve everything they could before.

However - many projects are small enough to be fully expressed using either paradigm, and use of heavyweight frameworks is redundant most of the time. This is not intended to say that RoR is an enterprise replacement (it definitely is not, for many reasons), but most problems do not need enterprise-grade frameworks.

And as far as high-performance computing goes - it's all in datacenters these days. Giga-/Peta-bytes of files processed by simple scripts, or python on hundreds of machines.

What Java's OOP model do exceedingly well is its ability to absorb TDWTFs on which enterprises are built. And consequently, businesses are all about risk aversion today, so they favor it.
Advertisement
Quote:Original post by Antheus
OOP also doesn't mean stuff is in classes, and OOP can be perfectly trivially implemented in C and it doesn't even require convoluted arrays of function pointers.

So what does it require? How does OO look like in C?
Quote:Original post by DevFred
Quote:Original post by Antheus
OOP also doesn't mean stuff is in classes, and OOP can be perfectly trivially implemented in C and it doesn't even require convoluted arrays of function pointers.

So what does it require? How does OO look like in C?


X* create()void member_func1(X * p, int x, int y);void member_func2(X * p, char * x);void destroy(X *);


Encapsulates all state, allows polymorphism (this might require some extra functionality or specific linking, perhaps even DLL dynamic binding, or just renamed functions), allows OO-like code re-use.

Obviously, it's possible to reinvent v-table, but it's an overkill in practice.

Most of the good C libraries are written using this style, which means they can be mapped directly to OO code style, even RAII - the code is equivalent from design perspective, just syntax differs. Just that 'this' pointer is passed around explicitly.


For comparison, the data-centric design is something like this:
tool input-file output-file
The file formats are either formal, expressed via some data structure, perhaps a database.

In code, this can be expressed similarily:
void tool(Input *a, Output * b);
where the function is stateless and transient - it can be invoked once, multiple times, run persistently (stream proxy/web server) or in distributed manner. Input and Output are well formed, but opaque and passive. They cannot interpret the contents they carry, they just need to know the structure, or not even that (binary/ascii chunk of bytes).
Quote:Original post by Antheus
Except that this doesn't work in practice. Requirements change, domains change - if you can't adapt fast, you lose.
I disagree with this statement but I'll leave it at that. This is a highly debated subject. Good luck on which ever path you choose.[smile]
Quote:Original post by Hodgman
I love C++ and OOP, but this is still true (except the bit about references being cruft :P ).


Would be nice to be able to see the whole piece of code in plain text. Hard to tell what's going on from the pictures.
Quote:Original post by Zahlman
Quote:Original post by Hodgman
I love C++ and OOP, but this is still true (except the bit about references being cruft :P ).


Would be nice to be able to see the whole piece of code in plain text. Hard to tell what's going on from the pictures.

The one page used in the slide show can be found here, but it still lacks context.
Just write what suits you best. Spanish or Chinese, pick the language YOU like. Don't worry about the few clockcycles you may miss due OOP overhead. You'll miss cycles anyway unless you really know what the compiler is going to produce, meaning in-depth knowledge is required. The good news is that you won't hardly notice it anyway, unless you really fuck it up. But OOP or not, no language, technique or compiler will fix bad code.

Optimized code does matter when you deal with often used functions (math), big algorithms(pathfinding), or hardware stuff such as interrupts. In that case I ussually stay away from OOP, but other than that I'll guess *readable* code gets priority over the fastest possible code. OOP can help here, IF you have a good design. Just making up classes on the fly can still be very confusing when the size and complexity grows. Lot's of practice is the key here.


I'm not a coding expert, but personally I use a mixture. When writing activeX, webservices, API's or stuff like that, I prefer OOP. For one reason, its easy to group and documentate for later. For hardware such as microchips I'll keep it procedural, OOP is often not even supported anyway. For games I'll use classes for interfacing between the different sections and for entities, as it feels more natural. However, the deeper code that does the magic is mostly procedural. Don't know why, it just happened :) But most important, it works, good enough.
Quote:Original post by howie_007
It really comes down to how well you can design your objects to make use of inheritance, polymorphism and the benefit is code reuse. The other thing you can make use of with C++ and OOP is the concept of designing your objects based on an interface. If you've programmed Java you should be aware of this concept.
I would say it comes down to how well you can use composition instead of inheritance, but each to their own ;)
In my experience, a reusable object rarely has the same interface as some other exchangeable reusable object.
e.g. I would hate it if std::vector and std::list both inherited from IContainer so that applications could be blind as to the underlying type of the container. Both vector and list actually do "inherit" the concept of a sequence, which "inherits" container, which "inherits" assignable, but this is only conceptual inheritance (C++ inheritance isn't used) -- there's no need for a run-time Java-style interface here as these classes are designed to be used via composition, not polymorphism.
Quote:Original post by Wan
The one page used in the slide show can be found here, but it still lacks context.
Thanks for that link =D
You can guess at some of the context:
* Culler represents a frustum (such as for the camera).
* Each frame, every game object is passed to the culler, and a virtual func may be called on the object to record the result.
* The critic thinks that frustum culling could be batched, evaluating all objects at once without expensive branches or cache-misses.

The critique of this code is written from a very PS3/Compute-centric point of view. I think the lessons he's trying to push are:
* Converting the floating point math to an integer doesn't give you anything, and just causes unnecessary branches (which are death for SPU/Shader code).
[in floating-point code, 0.0/1.0 can be used for true/false, * for logical-and, + for logical-or, and x>0 for "is x true"].
* Don't process data one piece at a time, branching to other data-processing functions in-between. Batch up your data and perform the same operation as many times as possible. Then pass the batched results on to some other data transformation function.
* Sort your polymorhic types so objects of the same type can be batched as above.
* Doing a bit of extra math can be faster than branching to avoid work.
* Don't use virtual functions in the inner loops of your program (especially not in SPU code!). Be aware of how vtables work and what this means for the cache.
* Be aware of locality.

[Edited by - Hodgman on March 9, 2010 6:53:27 PM]
My argument about why OOP is heavily overused and abused has nothing to do with performance. The scalability I was referring to has to do with management. I have no problem with vector or List<> being OO.

Consider the OOP tutorials:
- Model animal kingdom (single-cell<-multi-cell<-bacteria<-....<-ape<-human)
This doesn't work. This is not what the real world is like - where is virus? Our entire flora and fauna is: Genome + a bunch of other stuff. The inheritance here is expressed via prototyping - one or more genomes are combined to produce another genome based on values - not types.

- Bicycle<-Car<-Truck<-Train?<-Airplane?<?- boat?
Again, unrealistic model of the world. Vehicles do not inherit - bicycle has nothing to do with car, except that it has round things which could in both cases be called wheels.

Typical architectural abstraction is that some things are ignored or thrown away. For purposes of registration, number of axes and engine capacity is a good abstraction. But again there is a problem - Vintage car from before 19xx is taxed based on one value while cars above certain power but without filter first registered before 2000 are exempt from this by law passed in 2002, but it applies only retroactively to vehicles registered before Feb 28 2004 except if vehicle was imported before 2003 or manufactured domestically......

So how do you express this in an interface that has:
int Vehicle::getTaxRate();
The answer is: "// magic happens here in Facade pattern"

And even if such system is properly modeled (taking into consideration that laws are the main model, and that laws are effective over periods of time, and that classifications are often based on exemptions, etc....) - you run into a rule system which operates on a bag of properties.


In an ideal world, with perfect knowledge, with unchangable requirements, with infallible management, with experience developers with arbitrary funds - OO can work well.

At least one or more of above points will fail in real world.

But again, there is no problem with vector being an object, or manager having observer delegates or factory being a singleton or similar... OO applies to model of the domain, and this is where it simply fails to scale, or results in tens of MLOC uber abstracted codebase that requires hundreds of developers just to do trivial tasks. This TOC simply isn't viable anymore, when other approaches consistently deliver more, faster and cheaper.

It's like C++. It's fantastic language. Which is probably why most projects are written in C, and they just use a templated container from time to time, or overload a function here and there. This is just a pragmatic observation - modern C++ is quite cool, except that nobody really uses it for real world, or if they do, much better alternatives exist.

OOP fails as primary design paradigm, but is very convenient as supporting methodology in smaller scale, just like C/C++ are great for native bindings, but practice consistently shows there are much better options for higher level design.
Quote:Original post by Antheus
The opposite of OOP is data-centric model (or even functional programming). There is a data store which holds entire state, and there are lightweight stateless manipulators or agents which traverse this state. Unlike OO, this model is I/O centric collection of black boxes.

Do you have a more googlable term for this than "data-centric model"? Is the main thrust of the concept FP inspired design?

This topic is closed to new replies.

Advertisement