Summary:
Ken Arnold, the original lead architect of JavaSpaces, talks with Bill Venners about whether to prohibit subclassing, whether to use
Cloneable
or copy constructors, and when to use marker interfaces.
The ability to add new comments in this discussion is temporarily disabled.
Most recent reply: December 15, 2003 2:14 PM by
|
"I am not fond of copy constructors. In fact, I'm not very fond of constructors at all. The problem is that the code that creates the object with a constructor is defining the object's type. In all other operations, the code that uses an object effectively only defines that the object is at least a certain type. Constructors are an exception to that rule. I don't think that exception should exist," says Ken Arnold in this Artima.com interview: http://www.artima.com/intv/issues.html
|
|
|
I brought up many of the design issues I asked Ken Arnold about in my interview with Josh Bloch a few months back. The relevant URLs in which Josh gives his opinions are: Documenting for Inheritance or Disallowing It http://www.artima.com/intv/bloch12.htmlCopy Constructor versus Cloning http://www.artima.com/intv/bloch13.html
|
|
|
> Copy Constructor versus Cloning > http://www.artima.com/intv/bloch13.htmlBloch is right about clone being broken, but he doesn't address the problem that copy constructors are not polymorphic in his answer. This is a definite limitation when, say, the client knows only of an interface, rather than of a concrete class. I have never created a copy constructor; rather, I prefer a "Copyable" interface with public copy() method as Ken Arnold describes. I think Bloch is correct regarding subclassing, though. Subclassing, especially from one concrete class to another, frequently leads to more brittle and less understandable code, in my opinion. (Subclassing of abstract classes is certainly better, because the base class is explicitly designed to be polymorphic.) There is a paper out there somewhere ("Inheritance Decomposed" by Peter Frohlich) that describes pretty nicely how subclassing can be replaced with better techniques in every case.
|
|
|
> > Copy Constructor versus Cloning > > http://www.artima.com/intv/bloch13.html> > Bloch is right about clone being broken, but he > doesn't address the problem that copy constructors > are not polymorphic in his answer. This is a > definite limitation when, say, the client knows only > of an interface, rather than of a concrete class. > > I have never created a copy constructor; rather, I > prefer a "Copyable" interface with public copy() > method as Ken Arnold describes. > I asked Josh Bloch about Ken's point, and Josh basically clarified to me that he considers copy constructors an alternative to cloning, not an exact equivalent replacement. He wasn't thinking of copy constructors that use reflection to find and invoke a copy constructor of the same exact type as Ken described, but just a simple copy constructor that converts the passed object to the class's type, doing type truncation if the passed object is actually a subclass. Josh seemed to feel that a conversion constructor would suffice for what most people wanted most of the time. I'm not sure what people would want most of the time. What I think I want is a method like clone that polymorphically gives me a duplicate object of the same class, and an interface (such as Copyable) that has the clone method in it. But on the other hand, in 5 years of C++ I always made copy constructors per Scott Meyers' advice in Effective C++, and I never once encountered a situation in which type truncation was a problem.
|
|
|
> I think Bloch is correct regarding subclassing, > though. Subclassing, especially from one concrete > class to another, frequently leads to more brittle > and less understandable code, in my opinion. > (Subclassing of abstract classes is certainly better, > because the base class is explicitly designed to be > polymorphic.) > > There is a paper out there somewhere ("Inheritance > Decomposed" by Peter Frohlich) that describes pretty > nicely how subclassing can be replaced with better > techniques in every case. I've really gotten this same message again and again from lots of different sources. I think it would be interesting to see a language that didn't actually have implementation inheritance, just interface inheritance. It would also be interesting to see a language, perhaps the same one, that does away with publicly accessible constructors in favor of static factory methods. Both Ken Arnold and Josh Bloch kind of praise the merits of factory methods in their interviews. Even James Gosling himself has sometimes talked about a language without implementation inheritance. I asked him about that here: http://www.artima.com/intv/gosling34.html
|
|
|
Regarding declaring classes as final:
It seems to me that users of Java fall into two very distinct camps - the "frameworker builders" and the "application builders"; the former build APIs to be used by the latter. I would guesstimate that application builders form the majority - say ~80% - of the community of developers.
A framework builder needs to be very concerned about designing for subclassing, of course, while an application builder *much* less so. I would suggest that an application builder should keep classes final unless there is a compelling reason to do so, since it simplifies the model, and avoids the pitfalls admirably illustrated by Bloch. Changing this decision later on is just another refactoring, which does not affect the consumer of the application in any way...
|
|
|
I enjoyed this point:
"If you look at a method contract, you'll see that not everything can be spelled out in the programming language. Some things expressed in the contract must be expressed in human language. String's equalIgnoreCase is a method, but nothing in the programming language enforces that case is ignored; that requirement is only expressed in the human language text."
It emphasizes that a certain amount of respect (for want of a better word) needs to be accorded to the English description of a method - its javadoc. One often hears references to the metric of "uncommented lines of code", for instance. The message of such a phrase is that "what really matters is the code, and the comments are really secondary." But I would disagree with that sentiment. Javadoc forms the contract of a method. What could be more important than that? One might even take the contrary point of view : "what really matters is the spec(the javaodc) of the method; all the rest is just a detail of implementation."
What do you think?
|
|
|
> I think Bloch is correct regarding subclassing, > though. Subclassing, especially from one concrete > class to another, frequently leads to more brittle > and less understandable code, in my opinion. > (Subclassing of abstract classes is certainly better, > because the base class is explicitly designed to be > polymorphic.)
Although I agree that subclassing is widely overused (just think -- a JButton is a Container!), I have yet to see a language that does delegation well enough to make it easy to solve the problem. Haskell has some nice features here. For example, it allows you to assert that some other type -- one you didn't write -- is (say) a Point because it fulfills the contract, possibly by some adaptor code you've written. If you do this, the thing *is* a Point, and can be used anywhere a Point is needed. This allows you to be anything you need to be without binding it into the definition of the type that everyone must share. If you need that thing to be a Point, but others don't, then it will only be a Point in your code.
It's an interesting feature. But without something like this, and probably several other features besides, working without inheritence is possible but really ugly, and so will not be done. "No rules without tools." Demanding that people reliably do painful things is not a recipe for success.
> There is a paper out there somewhere ("Inheritance > Decomposed" by Peter Frohlich) that describes pretty > nicely how subclassing can be replaced with better > techniques in every case.
Well, I wouldn't go that far, but I suppose it depends how you define "inheritence". In any case, doing this without language support would be awful. (I mean, you *can* do OO programming in C; just look at the X toolkit. But without the language support it's pretty intolerable.)
|
|
|
> than that? One might even take the contrary point of > view : "what really matters is the spec(the javaodc) > of the method; all the rest is just a detail of > implementation." > > What do you think?
I think whoever takes that point of view will be attacked by an agry mob of eXtreme programmers.
|
|
|
> There is a paper out there somewhere ("Inheritance > Decomposed" by Peter Frohlich) that describes pretty > nicely how subclassing can be replaced with better > techniques in every case.
Hmm... Sounds like this fellow needs to get a leash for his dogma.
Of course, you could also replace all fors and whiles with labels and gotos and write a paper about that, too. Well, fortunately, you couldn't do that will Java.
Essentially, it is ridiculous to say there is one right way to do everything. Especially when that one right way is more difficult.
It can be argued that composition in many cases is less flexible than extension, simply because composition requires more work; this cements it in, making it less flexible.
|
|
|
> Essentially, it is ridiculous to say there is one > right way to do everything. Especially when that > one right way is more difficult.
Maybe. But I find that whenever I push myself to think of a composition-based alternative to a design that I've come up with that's extension-based, I end up with a better design. The components I end up creating, rather than simply being the abstract basis for subclasses, become reusable in other contexts.
> It can be argued that composition in many cases is > less flexible than extension, simply because > composition requires more work; this cements it in, > making it less flexible.
When you say "this cements it in", I'm not sure what you mean. The cementing that I dislike is that of a subclass to it's superclass parent; surely that relationship is as cemented as can be.
Composition is by nature a looser coupling. One object is dependent only on the interface of another. I can plug-in a different object that will suffice as long as it satisfies the same interface; such is not the case with a parent-child relationship. Composition also lends itself to a much richer set of patterns that subclassing.
Jared
|
|
|
> Although I agree that subclassing is widely overused > (just think -- a JButton is a Container!), I have yet > to see a language that does delegation well enough to > make it easy to solve the problem. Haskell has some > nice features here. For example, it allows you to > assert that some other type -- one you didn't write > -- is (say) a Point because it fulfills the contract, > possibly by some adaptor code you've written. If you > do this, the thing *is* a Point, and can be used > anywhere a Point is needed. This allows you to be > anything you need to be without binding it into the > definition of the type that everyone must share. If > you need that thing to be a Point, but others don't, > then it will only be a Point in your code.
Interesting -- if by "fulfills the contract," you mean that it not only satisfies all the method signatures of a Point but also the *behavior* of a Point, then that sounds good. But of course I would be wary of a tool that told me that Class A satisfied the contract of Interface B simply because it had all the correct method signatures -- that might be mere coincidence. I am not sure how a tool could enforce anything else, though.
Jared
|
|
|
> I've really gotten this same message again and again > from lots of different sources. I think it would be > interesting to see a language that didn't actually > have implementation inheritance, just interface > inheritance. It would also be interesting to see a > language, perhaps the same one, that does away with > publicly accessible constructors in favor of static > factory methods. Both Ken Arnold and Josh Bloch kind > of praise the merits of factory methods in their > interviews. Even James Gosling himself has sometimes > talked about a language without implementation > inheritance. I asked him about that here: > > http://www.artima.com/intv/gosling34.htmlAh! I am excited to see him say exactly what I've been trying to say: "[A language] that just does delegation... without an [implementation] inheritance hierarchy." Granted, he says that we don't know yet all the problems that might crop up with a delegation-based language. But I think it'd be worth the investigation. I hope he pursues it. Jared
|
|
|
> Interesting -- if by "fulfills the contract," you > mean that it not only satisfies all the method > signatures of a Point but also the *behavior* of a > Point, then that sounds good.
Well, what *else* could it mean? This is why I said that the contract is in English, not just the method signatures.
> But of course I would > be wary of a tool that told me that Class A satisfied > the contract of Interface B simply because it had all > the correct method signatures -- that might be mere > coincidence. I am not sure how a tool could enforce > anything else, though.
The Haskell idea is that some human would make the assertion by providing any necessary adaptor code. For example, if package A's "Point" class took parameters in (x,y) order, and package B's "Point" class took parameters in (y,x) order, someone could make A's points usable in the B package by providing methods that swapped the order. Then anyone, anywhere in the code could simply use an A point wherever a B point was required.
Obviously this could be done for more sophisticated mappings, but you get the idea.
Ken
|
|
|
> Well, what *else* could it mean? This is why I said > that the contract is in English, not just the method > signatures.
Well, you didn't mention English. But yes, it couldn't really mean anything else and still be useful.
> The Haskell idea is that some human would make the > assertion by providing any necessary adaptor code. > For example, if package A's "Point" class took > parameters in (x,y) order, and package B's "Point" > class took parameters in (y,x) order, someone could > make A's points usable in the B package by providing > methods that swapped the order. Then anyone, > anywhere in the code could simply use an A point > wherever a B point was required.
OK -- this sounds like something Java could pretty easily do if every class had a corresponding interface, because then the class that wanted to be a "Point" would simply implement the Point interface and delegate (or act as the "adaptor" as you describe) to the other object, translating, e.g., parameters (y, x) into (x, y). Is that a correct statement?
Jared
|
|