Sign in to follow this  

On the merits of smaller objects

This topic is 4856 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

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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)

Share this post


Link to post
Share on other sites
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].

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 :)

Share this post


Link to post
Share on other sites
Quote:
The underlying implementation may be, but the end user usage is somewhat more abstract.

No it isn't.

Quote:
The methodologies are quite distinct.

No they aren't.

Quote:
The most common langauge, C++, enforces no such restrictions [on how operations are organised].

It certainly does. It has destructive assignments; this means it must have restrictions on the organization of operations. We see this in, for example:

int a = 0, b = 1;
a = 2; // 1
b = a; // 2
a = b; // 3

These operations *must* be performed in an order that gives the right results:

123: a == 2, b == 2: ok
132: a == 1, b == 1: not ok
213: a == 2, b == 2: ok
231: a == 2, b == 0: not ok
312: a == 2, b == 2: ok
321: a == 2, b == 1: not ok

When you have destructive assignment you are not free to arbitrarily reorder.


Quote:
Because OOP was developed to minimise the potential failures that such a linear structure can have.

But it doesn't remove those "potential failures". It's not clear that it even reduces them.

Share this post


Link to post
Share on other sites
Quote:
Original post by DrPizza
Quote:
The underlying implementation may be, but the end user usage is somewhat more abstract.

No it isn't.

Yes it is.

Quote:
Original post by DrPizza
Quote:
The methodologies are quite distinct.

No they aren't.

Yes they are.
[wink]
Quote:
Original post by DrPizza
Quote:
The most common langauge, C++, enforces no such restrictions [on how operations are organised].

It certainly does. It has destructive assignments; this means it must have restrictions on the organization of operations. We see this in, for example:

int a = 0, b = 1;
a = 2; // 1
b = a; // 2
a = b; // 3

These operations *must* be performed in an order that gives the right results:

When you have destructive assignment you are not free to arbitrarily reorder.

Order isn't the be all and end all or organisation. Assignment is a special case: always a method, but other operations can usually be factored any way the programmer would like.

Quote:
Original post by DrPizza
Quote:
Because OOP was developed to minimise the potential failures that such a linear structure can have.

But it doesn't remove those "potential failures". It's not clear that it even reduces them.


As said, it minimises these failures. Constructors and destructors are a blessing, and have made many a design feasable in C++.

Share this post


Link to post
Share on other sites
Quote:
Yes they are.

No, they really aren't. This is why you can have both OO imperative languages (e.g. C++, Java, etc.) and OO declarative languages (e.g. o'caml). The former tend to greatly resemble procedural programming; where they vary is for the most part in their syntax (they make function calls look a bit different).

The code in C++ or Java etc. is still procedural; it breaks problems up into modules and subroutines (it might call them "packages" instead of "modules" and "methods" instead of "subroutines" but it amounts to the same thing) with scoped variables.

What makes these languages "OO"? Well, they allow one to define a record type (encapsulating some state) and a number of subroutines to manipulate that state. They'll call these things "classes" and "methods", but they're not doing anything that procedural code doesn't. What they might do that's a little different is make the subroutines subordinates to the records. But even then, the differences are just syntactic; they merely mean one writes "foo.bar(baz)" instead of "bar(foo, baz)". This is not really a great difference; there's little in practical terms between, for example:

// "OO"
fstream myfstream("path/to/file", ios_base::in | ios_base::out | ios_base::binary);
myfstream.write(arr, arrSize);
myfstream.close();

and

// "non-OO"
FILE* myfilestar = fopen("path/to/file", "rb+");
fwrite(arr, 1, arrSize, myfilestar);
fclose(myfilestar);


In procedural and imperative OO alike, programs are broken down into lexically scoped sequentially evaluated subroutines. "OO" and "procedural" are orthogonal concepts; "OO"--if it means anything at all--makes state manipulation subordinate to state. But that's the extent of it. It implies nothing greater about a program.

Quote:
Order isn't the be all and end all or organisation. Assignment is a special case: always a method, but other operations can usually be factored any way the programmer would like.

The compiler can't in general reorder anything in an imperative language. Order of evaluation *matters*. Sequential evaluation is *important*.

Quote:
As said, it minimises these failures.

Not demonstrated in practice; OO software is not cheaper to produce or less buggy than mere "procedural" code.

Quote:
Constructors and destructors are a blessing, and have made many a design feasable in C++.

Constructors and destructors do not make any design feasible that would not otherwise be feasible. They're a convenient shorthand, but no more.

Share this post


Link to post
Share on other sites

This topic is 4856 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