On the merits of smaller objects

Started by
12 comments, last by DrPizza 19 years, 7 months ago
Lets get introspective. If I look back on my long (ha ha) programming career, and more specifically, my OO programming career, we can see a few trends: * I'll use classes, but typically only obvious ones that are directly applicable to a domain (e.g. in a game I'd have an Entity class, an Item class, etc) * RAII and other 'secondary' classes are employed if necessary, but usually only when dealing with a resource * Classes are typically used at the high level of the system as contracts. Within each component it breaks down into procedural programming pretty quickly. It occured to me the other day that perhaps I'm really still skimming the surface of what OO can do. Now I could just be an OO fanboy, but I feel that I could increase the readability of some of my code by introducing smaller objects to ease understanding and testing. Whereas normally you'd have one object that contains everything (and still does one thing, but this one thing could be complicated, such as managing a server connection with a protocol). I've had good success with abstracting the tedium of I/O throughout my app because I am a very lazy person when it comes to working with that sort of thing. I see it as a growth step in terms of OO modeling, but oftentimes it isn't apparent until all the code has already been written. Any thoughts on the matter are appreciated.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
Advertisement
I think the issue here is that of coupling. Smaller objects increase it and larger objects decrease it. Libs are mostly decoupled which fit C++ well. High coupling among objects leads to more maintenance. It then almost feels like passing data around the system which is what C is all about. So if we want to lower coupling and have less maintenance we should embrace large objects that play many roles. That's my take on things right now.
Coupling largely depends on how the small objects are instantiated. Are they instantiated directly inside the owning classes? If so, the owning class is coupled to the class names of the class objects it owns.

There are ways to avoid coupling classes through conditional compilation and templates. The object factory and abstract factory patterns help quite a bit.

Using larger objects doesn't decrease maintenance. You maintain roughly the same amount of procedural code, minus the invocations on the small objects. IMO it's silly to avoid using small objects in situations where they might simplify the code just because it would introduce class name coupling. A car needs an engine; so what if it knows it needs an engine?

Small classes also offer better opportunities for re-use. A small string class will be more useful in many more situations than a large class that may serialize game objects, for example. That's good code re-use.

Building slightly larger components out of the small objects is a trial and error, however... it seems good classes composed of great small classes have to evolve to prove their worth in multiple situations. Thank dog for refactoring! [smile]

[EDIT TO ADD]

Also, when refactoring a large class, think about responsibilities. Well-defined responsibilities will tend to coalesce into separate lists that abstract very nicely. What you'll end up with (if you structure those responsibilities right) is a small list of classes -- some of which delegate to smaller classes... and small classes with well-defined responsibilities.

Much easier to maintain (IMO) because errors will tend to be isolated in smaller sets of code (smaller classes). If you get 15 small classes out of 1 large class, for example... maybe 12 of them delegate and three do all the real work. Much easier (IMO)
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
As with anything, there's a tradeoff. Too large, complication results. Too small, procedural-like code results.

The temptation is to make everything a class. The old adages are 'give a class a single, well defined responsibility' and 'do as the builtins do,' which suggest low-cost/low-functionality pieces, but as we know, this increases the required member functions/operators sigificantly.

IMHO, there is no single true answer, though my experience would suggest several 'medium' sized coop types that are implemented in terms of builtins and types that behave as buitins [I'd go as far as saying vector behaves as a builtin, since it has little abstract functionality].
Large, monolithic objects that attempt to perform many different task leads to the "God class" syndrome and is considered bad design. Each class should represent one abstraction. If you attempt to jam several disparate responsibilities into one class, it loses coherency.
If you're interested in becoming more OO-Like then consider learning Smalltalk! :-)
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote: Too small, procedural-like code results.

And this is bad why?

"OO" does not mean "easy to maintain". "Procedural" does not mean "hard to maintain".

Most OO code is in any case procedural. "OO" and "procedural" are not opposed; "procedural" is just a consequence of using an imperative language; imperative languages are concerned with performing a sequence of operations in a particular order; the use of objects does not alter this.
char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;}  /*** drpizza@battleaxe.net ***/
I think he means that when you pull data out of an object and pass it around then all those places that touch it have to be fixed when that data changes. I rather have only one place I need to change the data and that's in the class that contains it. Same thing that abstract interfaces try to achieve.
Quote:Original post by DrPizza
Most OO code is in any case procedural.

The underlying implementation may be, but the end user usage is somewhat more abstract.
Quote:Original post by DrPizza
"OO" and "procedural" are not opposed;

The methodologies are quite distinct.
Quote:Original post by DrPizza
"procedural" is just a consequence of using an imperative language; imperative languages are concerned with performing a sequence of operations in a particular order; the use of objects does not alter this.

The most common langauge, C++, enforces no such restrictions [on how operations are organised]. This is entirely up to the programmer.
I'm not sure a better use of OO is really important when it comes to readability. The major point of OO is more maintenance than readibiliy - since the later is more easily achieved by good coding convention.

For the big object vs. small object debate, my preference goes to objects that deals with something particular :) If they want to be big (because their field of expertise is rather large) then it's ok for me as long as it do not break the basic OOP rules. A good design do not say wether objects should be big or not.

My 2 euro cents again :)

This topic is closed to new replies.

Advertisement