I saw Jason's post some time ago and finlly got the time to respond.
I'm taking an OO Engineering course, and I brought up the question about so-called "utility classes", for example the Math class and all it's static methods, private contstructor, etc. My question was basically how common are utility classes in the real-world, and do they not go against the whole OO principle.
Is there a specific OO principle (encapsulation, inheritance,
polymorphism, etc.) or "good design practice" (Liskov substitution, programming to an interface, etc.) that you think such utility classes violate?
I'm not trying to be combative -- it's just that there seems to be so much fuzziness around what OO is that I think it's sometimes helpful to try to articulate which specific principles are being upheld or violated.
In other words, if I'm working on a program, and I have some methods that I can't really fit into a class, is it common to make a class that can't be instantiated or extended, but have methods that you may need?
The Math class holds a bunch of related math related functions, so that at least has some cohesiveness. What about just general maintenance methods, or something to that extent? In my opinion, and I avoid such thoughts and practices.
In
Effective Java Joshua Bloch deals with this topic in the section "Item 3: Enforce noninstantiability with a private constructor" (page 12). Bloch says that though the idiom is often abused (used to write essentially procedural programs in OO languages), there are valid uses. He goes on to recommend that such classes be made noninstantiable and nonextendable by including a single explicit private constructor.
The example from
Effective Java:
Here's what Bloch gives as some valid uses:
They can be used to group related methods on primitive values or arrays, in the manner of java.lang.Math or java.util.Arrays, or to group static methods on objects that implement a particular interface, in the manner of java.util.Collections. They can also be used to group methods on a final class, in lieu of extending the class.
Yeah, I know he wrote Arrays and Collections and I'll bet he was involved with Math. All three do use the private constructor like you pointed out for Math.
The main point he seems to be making is that instances of such classes don't make sense, so
you should prevent their creation. (This is a sort of special case of advice he makes elsewhere -- either to design for inheritance or to prevent it.)
I think it's interesting Bloch mentions using such utility classes and methods on primitives and arrays.
Some of the static methods you find in java.lang.Math you also find as instance methods on BigInteger and BigDecimal (abs(), max(), min(), pow()). That is (ignoring for a second the issues about range that would lead you to choose between a primitive and BigInteger or BigDecimal), when you have an object to work on, you use instance methods for that object, but when you have a primitive you have to use the statics on java.lang.Math. If Java didn't have primitives for numbers, these static methods on java.lang.Math wouldn't be necessary. They'd be instance methods on whatever class(es) represented numbers.
Arrays are a bit flaky in Java too. Apparently they're "full-fledged" objects, but you create instances of them a bit differently from other objects. Plus they don't have any methods you can call on the array structure itself ("length" is apparently a public field). So in lieu of instance methods to call on arrays, you have all these statics in java.util.Arrays.
(I see the similarities between Collections and Arrays, but I'm not sure I understand what Bloch is getting at regarding the relevance of interefaces here. And I don't get the point about grouping methods on final classes either.)
Also, another thing that struck me as odd is he mentioned that even simple data types have behaviors.
I think "simple data types" muddies the
water a bit. Do you mean primitives? Java makes the distinction between primitives and objects (objects as instances of classes or "Abstract Data Types"). Not all OO languages do.
Not to turn this into SmalltalkRanch, but some comparison with Smalltalk might be interesting. In Smalltalk, numbers are full-fledged objects and have all sorts of behavior.
Since I've always thought as behaviors as meaning methods/functions, this didn't sound right to me, but he was coming from the idea that you can add, subtract, etc things like int's, so that is a behavior.
Sure. I think you're coming at this with a Java-language bias. In Java, the typical way of doing math is to have operators act on primitives and produce a result. In Smalltalk, arithmetic operations such as "+", "-", aren't even "built into" the language; they're simply method calls on objects.
First consider the following Java:
Suppose Java didn't have primitives or operators, so these objects were the only way to represent numbers. Wouldn't line (A) be a fairly convincing argument that numbers have behavior?
Incidentally, the syntax in Smalltalk is much cleaner:
result := 3 + 5
"3" and "5" are both numeric literals (instances of the class SmallInteger). "+" is a method on SmallInteger. The method "+" is being called on the number "3" and is passing the parameter "5" into the method body. (Beware: having arithmetic operators as method calls wreaks havoc with traditional operator precedence.)
(Another behavior associated with numbers is looping. In Smalltalk loops aren't really built into the language either; they're just method calls on numbers. The following two code examples both print "Hello world" to the console 3 times.
1 to: 3 do: [:n | Transcript show: 'Hello world' ; cr ].
3 timesRepeat: [ Transcript show: 'Hello world' ; cr ].
Note that the stuff in square brackets makes up a block, which is a kind of object (which is being sent to the methods as a parameter and then code in the method says "execute the block passed in"). timesRepeat: and to:do: are both method calls on the SmallIntegers. (Smalltalk combines the method name and parameter list into a single
unit, so to:do: is a method that takes two parameters.)
Consider the following Ruby code snippets, which do the same as the Smalltalk:
result = 3 + 5
1.upto( 3 ) { print "Hello World\n" }
3.times { print "Hello World\n" }
This is about where my knowledge of Smalltalk and Ruby tops out...
)