Skip to main content
GameDev.net gamedev.net
🔒 Locked

Is object reusability in OOP just a pipe dream?

Started by Extrakun Nov 11, 2006 at 10:42 PM 23 replies 6k views
Original Post
Extrakun
Extrakun
Hi all, All right, so I was revising for my Software Engineering exams when I come across the "holy grail" of OO, which is reusable objects. This leads me to wonder how achievable is that goal, really. But from my limited experience of coding in OO (Java mostly), it seems to be an unachievable pipe dream. I was reading a book on Aglie Development the other days and the author presented a simple example of lamp and an on switch. This problem is easily solvable with just 2 classes, but if you want to make the objects resuable, you must introduce three or four other interface classes and etc. One practical example I ran into while coding my own engine the other time was this - say I have an Entity class, which does all the logic (calculating distance moved, setting its state and etc.). I really want this Entity class to be a black-box, which means I can use it into another engine if possible. But let say I have my own logging utility. I want my Entity class to log useful debug messages to a .txt in case of crashes. And usually, logging is implemented as a singleton. But if my Entity class uses the logging singleton, it won't work in a project which doesn't use the same logging singleton. I probably can achieve this by having lots of abstract interfaces, but this means in the end I will up with a rather large component which may be a pain to intergate it in with other projects. So is reusable OOP feastible really?
GamesTopica.Net- http://www.gamestopica.net
hplus0603
hplus0603
"re-usable code" is orthogonal from "object oriented code." One of the most re-used pieces of code, ever, is the standard C library. Another re-used piece of code is the Standard Template Library in C++. You can look at both to figure out how to design classes that are re-usable.

In general, there are two kinds of re-usable code: Basic data types, and API-structured interfaces.

For basic data types, like Vectos and Matrices, you can write the library once, and use it over and over. If you want to use it with another library, you can hope that that library uses templates for parameterizing types, or you can create adaptors or typedefs.

For API-structured interfaces, you can write a library, which is in itself self-contained (or has well-defined dependencies), and anyone who wants to do whatever the library is doing, can use the library. That interface is often best expressed using loose coupling, such as abstract base classes.

Given that re-use, in general, always means some form of bridging, wrappering, facading or translation, you don't want re-usable objects that are in the middle -- not enough to be a library, but too complex to be basic data types. Those kinds of objects seldom suit themselves to re-use.
enum Bool { True, False, FileNotFound };
capn_midnight
capn_midnight
Quote:

But from my limited experience of coding in OO (Java mostly), it seems to be an unachievable pipe dream

The fact that you were using Java is proof that you are wrong, because the entire Java API is *gasp* reusable object oriented components. What do you think a String is? How about a JFrame? Are they not reusable? Are they not objects?
Oluseyi
Oluseyi
Quote:
Original post by Extrakun
But from my limited experience of coding in OO (Java mostly), it seems to be an unachievable pipe dream. I was reading a book on Aglie Development the other days and the author presented a simple example of lamp and an on switch. This problem is easily solvable with just 2 classes, but if you want to make the objects resuable, you must introduce three or four other interface classes and etc.

Why bother? A common failing of textbooks in the computer science is how inadequate the examples are to illustrate the concept. What would you ever use a Lamp class for in the real world?

Also, note that many of the interface difficulties you perceive relate to static typing problems, not necessarily object design. With type inference or dynamic typing, plus introspection to determine the available interface of an object, reuse of narrowly-defined objects is a far more attainable goal.

Quote:
I probably can achieve this by having lots of abstract interfaces, but this means in the end I will up with a rather large component which may be a pain to intergate it in with other projects.

Why? A logging interface needs very few exposed APIs: create/close the log stream, specifying the file; set buffered/unbuffered writes; write; flush. You may be overengineering your solutions, which is a completely separate problem.
capn_midnight
capn_midnight
Quote:
Original post by OluseyiWhy? A logging interface needs very few exposed APIs: create/close the log stream, specifying the file; set buffered/unbuffered writes; write; flush. You may be overengineering your solutions, which is a completely separate problem.

I think this is one of the main benefits of Test Driven Development and Agile Development, you only implement what you actually need, because it is completely driven by only need.
flangazor
flangazor
Obviously, the more specific your code is, the less reusable it will be. One thing to keep in mind is that in order to reuse your components, you need to have a protocol that is easy to reuse. For example, you might think about it in terms of genericity: having a very general interface requiring too many resources to be passed to it is irritating because you end up with your resource boiler plate code all over the place. Having an interface try to take care of this stuff ends up being less reusable because it's assuming too much.

To get around this requires some clever design. In my experience, it usually involves inversion of control (i.e. Strategies and Commands (or Visitors)), which I think equates to Combinators in functional languages. Inversion of control ends up giving you a combinatorial algebra which is reusable based on how you compose your code. The downside is that Java isn't very good at inversion of control (it takes a lot of faffing about/overhead code).

Python and Smalltalk will do inversion of control very easily in OOP, but they tend to be slowed down by compilers not being clever enough to optimise the passed in functions with the code they are acting upon. C++'s STL is a great example of a combinatorial library that is reusable without giving you a runtime overhead (a design sweet spot). hplus is right to point you in that direction!
RAZORUNREAL
RAZORUNREAL
Hmm. How I decide whether something belongs to the engine or to a specific game, is I check how much it depends on, and what it assumes. Engine classes should be as self contained as possible.

I have a lot of things that depend only on external libraries (if that) like:
vector, quaternion, window, resource, timer, image, texture, inputdevice, etc.

Often they have a couple of things inheriting from them, like GLWindow or PngImage, but it doesn't get too out of hand. I started a raytracer using the window, vector and image classes once. So it doesn't have to be a pipe dream, but under the pressures of real life bad code tends to appear. And snowball.

And really, you can't expect to re-use some code. Can you expect your ai code to apply to your next game?
___________________________________________________David OlsenIf I've helped you, please vote for PigeonGrape!
Xai
Xai
analyze for obvious need, and likely (not just possible) changes

design for clarity (keep your design clean and understandable)

code for use (agile stlye)

maintain for consistency and stability

refactor for increased flexibility and clarity, and reduced dependencies

use copy-paste-edit reuse for second projects with slightly different needs

use extract-and-componentize reuse for ongoing (multiple) project reuse

One thing worth pointing out is that there are at least 3 types of "use" that you would like to get out of good low level components ... initial correct funcionality "first use", ongoing usefullness in a continously expandeding product (temporal and functional use/reuse), partial or complete relocation into seperate products/projects (reuse). The middle type of "use" is really a form of "reuse" in disguise. The type of situation where code written in the original Unreal Engine exists to some extent in the Unreal 2 Engine, and then the Unreal 3 engine. Both the component and the clients have actually changed somewhat every year, but neither one ever had to be scrapped and recoded from zero. So each version the "logging" subsystem gets more complex and featurefull (or perhaps cleaner and more efficient instead), but the aspects of the problem that the programmer had spent time solving in version 1, stayed solved as the starting point for version 2 ... so it is idea / solution reuse, the most important kind.

And I have a logging library that is reusable as hell, having needed to change only in minor details for over 5 years, and supporting additional implementations for rotating files, on screen display, network loggers, various db backends, C# .net implementation and use, xml, pop-up notifications, email, web use, etc ... without changing the core interfaces designed for a BeOS native C++ application 5 years ago. That's some serious reuse. In fact, it is so good in its design that all 4 companies I've worked for since then have adopted it for their projects.

The key to reuse is, you can't design a version that is good for general purpose reuse until you invest the time to truely understand the general purpose needs ... and this is usually not worth doing up front except in cases where you personal intuition already grasps the better situation that is possible. Normally you just start seeing places for "better" reuse as new needs arise similar to previous needs ... and about that 3rd or 4th "use" you start to see what truely is general, verses special, in your applications needs.

Realize that "factories" and "replacement parts" weren't even invented 500 years ago - unless perhaps you count basic bricks, boards, cloth, and pyramid stones. Higher level reuse really didn't come into its own until approximately the time of Henry Ford. In software, staticly typed OO languages don't seem to have really come into their own for really killer reuse until the advent of interface-based component programming - which just adds that last level of binding seperation needed to get amazingly usefull reuse while still having strong typing.


dv
dv
It is true that reusability is usually hard to achieve. You might want to look into generic programming, which separates data from algorithms (these two are glued together in traditional OOP). Boost and STL are good examples of generic programming. std::list is a generic list, since the data is not strongly coupled to the algorithms (you can use whatever variables you want in the list, and can reuse the list algorithms). However, generic programming is NOT easy, and sometimes just not feasible.
~dv();
Xai
Xai
One more thought. If "reuse" wasn't being used to some extent by pretty much every company out there, then existing entrinched software products would have no advantage over brand-new companies and teams. It is specifically because Firefox reused pieces of Mozilla that it could exist in the first place. Or Photoshop CS reused the core plugin design from earlier versions. Game engines reuse pieces originally written for specific games (Quake anyone). These are the some of the reasons why indy developers don't compete at the same level as big houses, they don't have enourmous code bases and histories to draw from.
johdex
johdex
Quote:
Original post by flangazor
Obviously, the more specific your code is, the less reusable it will be.


I think the sentence should be "The more specific your code is, the less generic it will be". Now that's an obvious statement.
I don't know if that's what you meant, but IMHO reusability is not genericity.

Consider for instance adapters, a wonderful invention that generic software can hardly do without. Unless they are coupled with up-to-date documentation and examples that actually work, they make it almost impossible to reuse software. When documentation cannot be relied on (and often it cannot), your only alternative is to read source code. In that case, adapters come in the way. The very purpose they serve, loosening coupling, makes it harder to walk through the code. Even when the documentation is fine, you might (will) still have to chase bugs or memory leaks in the code, in which case you also have to read and understand the code.

In other words, genericity helps reusability only when the documentation is perfect and the code has no bug. Perfect examples for this are the STL and BOOST. Highly reusable, few bugs, good documentation, source code almost no one can understand. Unfortunately, it's hard to implement software as good as those mentioned above. They are the exception, not the rule.
-- Top10 Racing Simulation needs more developers!http://www.top10-racing.org
T1Oracle
T1Oracle
Just look at how often game engines are reused. Code reuse is not only possible it is what everyone does. You will never write everything from scratch. Chances are that you will download a library and put a wrapper around it. That wrapper will likely work for one project. However, if you consider that:
Quote:
Original post by Oluseyi reuse of narrowly-defined objects is a far more attainable goal.
(thank you)
...and you apply proper layering to your OOP design you can greatly reduce the chances of having to refactor everything for the next project. I would say that to optimize the chance for code reuse with minimal refactoring you should take the following steps:

1) Draw from established practices: Standardized libraries are always reused, increasing compatibility with them can increase compatibility with future projects using the same libraries.

2) Narrow definitions: If a given construct fits optimally within a narrowly defined problem, then the chances are that it will do so again then next time you have that same problem. Good OOP requires a well understood and thorougly resovled problem space, narrowly defined problems are easier to solve.

3) Design correctly from the start: An approach to this is TDD (although I have yet to be won over by TDD) or the use of tools such as Alloy to verify that your design is bug free. A good design process where requirements, responsibilities, pre-states, and post-states are drawn out also helps to eliminate bugs. If the design of a given construct is valid and robust, then the chances that bugs will appear when it is used in another project are minimized. Eliminating bugs takes bugs fixes out the picture of possible refactors.

4) Layer object responsibilities: This helps enfoce suggestion #2. By dividing responsibilities of constructs into layers, you can enforce restictions on the responsibilities of those constructs. With less responsibility those constructs can be more narrowly defined.
Programming since 1995.
ToohrVyk
ToohrVyk
To ensure reusability, I usually strive to make my classes correspond to the following requirement: I should be able to create a new 100-line program, include the appropriate libraries and headers, and be able to use the classes in an useful way without the hassle of initialization or setup of large amounts of objects.

This is usually not ensured as-is by object-oriented programming (because in itself, it does not necessarily reduce coupling). Some elements (generic programming, polymorphism, encapsulation) are, however, excellent language-level tools to that end.

Test-Driven Development, however, is an excellent way to reach this requirement, since in essence any unit test is a new 100-line program that must use the classes. If you can whip up a 100-line program and be able to use the object thoroughly, chances are that you can use the object in a 100k-line program with about the same amount of work.

Writing correct unit tests will force your objects to be self-sufficient, easily initialized and uncoupled with other components of the program. In short: reusable.

TheTroll
TheTroll
I have classes that I re-use all the time. My dice class is one example. I use it in any program that I need dice rolls or random numbers. I also have a TreeNode class that I use a lot.

The trick is making a class generic and complete enough that it is useful. It takes more time when creating the class but helps each time you reuse it.

theTroll
Zahlman
Zahlman
Quote:
Original post by Oluseyi
Also, note that many of the interface difficulties you perceive relate to static typing problems, not necessarily object design. With type inference or dynamic typing, plus introspection to determine the available interface of an object, reuse of narrowly-defined objects is a far more attainable goal.


I feel compelled to point out that type-inferred languages normally offer typing that is considered static; the term for "the stuff that causes the interface difficulties" is manifest typing.

OTOH, the supposed superiority of type inference to manifest typing is very much debated.

Oh, and this page is quite relevant to the discussion in general.

Quote:
You may be overengineering your solutions, which is a completely separate problem.


QFE.

Quote:
Original post by capn_midnight
I think this is one of the main benefits of Test Driven Development and Agile Development, you only implement what you actually need, because it is completely driven by only need.


I find, when I read examples of / tutorials on TDD, that I am mentally restraining myself from thinking about what I will need in the foreseeable but not immediate future (And sometimes you just "know", though you may be wrong; but you could be wrong in the other direction, too, and sometimes adding the stuff later necessitates interface changes). This totally destroys my "flow"; maybe I'm unusual in that regard.

Taht said, there are obvious advantages to the method; it just doesn't feel right for me.

Quote:
Original post by Xai
analyze for obvious need, and likely (not just possible) changes

design for clarity (keep your design clean and understandable)

code for use (agile stlye)

maintain for consistency and stability

refactor for increased flexibility and clarity, and reduced dependencies

use copy-paste-edit reuse for second projects with slightly different needs

use extract-and-componentize reuse for ongoing (multiple) project reuse


A fairly good policy overall, I dare say.
capn_midnight
capn_midnight
Quote:
Original post by Zahlman
Quote:
Original post by capn_midnight
I think this is one of the main benefits of Test Driven Development and Agile Development, you only implement what you actually need, because it is completely driven by only need.


I find, when I read examples of / tutorials on TDD, that I am mentally restraining myself from thinking about what I will need in the foreseeable but not immediate future (And sometimes you just "know", though you may be wrong; but you could be wrong in the other direction, too, and sometimes adding the stuff later necessitates interface changes). This totally destroys my "flow"; maybe I'm unusual in that regard.

Taht said, there are obvious advantages to the method; it just doesn't feel right for me.


it's true, it's a huge change in mindset to TDD. But TDD will also help you if you do change the interface, as broken tests are essentially an autogenerated TODO list for fixing all the problems.
ApochPiQ
ApochPiQ
Care to contribute some actual logic to the discussion, or are you just interested in slinging ad hominems behind your thin curtain of anonymity?

Do you even understand the notion of Wiki? The fact that Ward Cunningham wrote the software is unimportant - the actual content is created by dozens of highly respected and very intelligent people in the software development community. Casting aspersions on Ward is about as pointless as questioning the quality of Lamborghinis because an Italian once tried to take over the world.
OrangyTang
OrangyTang
Quote:
Original post by Anonymous Poster
Quote:
Original post by Zahlman
I feel compelled to point out that type-inferred languages normally offer typing that is considered static; the term for "the stuff that causes the interface difficulties" is manifest typing.

OTOH, the supposed superiority of type inference to manifest typing is very much debated.

Oh, and this page is quite relevant to the discussion in general.


Wow, Those links are REALLY bad.

What? The c2 wiki is a goldmine of useful, practical and interesting discussion on a huge range of programming topics. Almost every time I go back and browse it I learn something new or think about an existing concept in a different way.
owl
owl
Quote:
Original post by Extrakun
But if my Entity class uses the logging singleton, it won't work in a project which doesn't use the same logging singleton.


If your Entity class uses the logging class, the logging class will be required wherever you use your Entity class, therefor it will always work.

You can singletonize a class by implementing a Singleton template, this way you don't have to define classes as singletons per-se.
[size="2"]I like the Walrus best.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.