Arne said:
[...]
It's debatable whether one really needs methods in the type that
return modified versions of the original. But assuming one does,
It is rather common to have such. The Java API itself has some
important ones.
Really? Where is the Square type in the Java API that inherits
Rectangle, never mind the immutable version that returns modified
versions of the original?
What should the formal return type be?
Whatever you want it to be. One option is Rectangle. If it matters
whether the resulting type is a Square (e.g. Rectangle.FitInSquare()),
you can check and cast.
If you want the return type to be Square, you have to throw an exception
if someone tries to return a non-Square from a Square. Or you provide
an API that only returns a Square (e.g. adjusts both dimensions by
identical amounts).
The sub class can return the super class but not the other way around.
Rectangle can return a Square instance and Square can return a
Rectangle. I have no idea why you think you can't do it that way.
Of course, whatever API is chosen, it needs to make sense in context.
But there's no fundamental reason Rectangle can't return a Square.
Difficult to avoid if the classes should have rich functionality.
Now you are adding requirements not originally stipulated. No one said
anything about "rich functionality", nor is it even clear what is meant
in the case of the Square/Rectangle example.
Pete
How about making the example more concrete, then.
public class Rectangle {
protected final double l;
protected final double t;
protected final double w;
protected final double h;
public Rectangle (double left, double top, double width,
double height) {
if (width <= 0.0) throw new IllegalArgumentException();
if (height <= 0.0) throw new IllegalArgumentException();
l = left; t = top; w = width; h = height;
}
// getters go here, and getRight and getBottom
public Rectangle getScaledInstance (double scaleFactor) {
if (scaleFactor == 0.0) throw new IllegalArgumentException();
// top left corner is center of scaling transform's effect
if (scaleFactor < 0.0)
return new Rectangle(l + w*scaleFactor, t + h*scaleFactor,
-w*scaleFactor, -h*scaleFactor);
return new Rectangle(l, t, w*scaleFactor, h*scaleFactor);
}
public Rectangle getWithWidth (double newWidth) {
if (newWidth <= 0.0) throw new IllegalArgumentException();
return new Rectangle(l, t, newWidth, h)
}
public Rectangle getWithHeight (double newHeight) {
if (newHeight <= 0.0) throw new IllegalArgumentException();
return new Rectangle(l, t, w, newHeight)
}
public Rectangle getMoved (double newLeft, double newTop) {
return new Rectangle (newLeft, newTop, w, h);
}
}
public class Square extends Rectangle {
public Square (double left, double top, double size) {
super(left, top, size, size);
}
public Square getScaledInstance (double scaleFactor) {
if (scaleFactor == 0.0) throw new IllegalArgumentException();
// top left corner is center of scaling transform's effect
// w = h = size
if (scaleFactor < 0.0)
return new Square(l + w*scaleFactor, t + h*scaleFactor,
-w*scaleFactor);
return new Square(l, t, w*scaleFactor);
}
public Square getMoved (double newLeft, double newTop) {
return new Square (newLeft, newTop, w);
}
}
The "copy-mutator" methods that would preserve squareness return Squares.
The ones that could produce non-square rectangles aren't even overridden,
and return Rectangles. This works, though it's possible to get square
Rectangles that are not Squares. Making the constructor protected, adding
a public factory method that calls it normally but only after if (width
== height) return new Square(left, top, width);, and changing the "copy-
mutators" to call this factory method would address this, if one felt it
necessary.
Of course if there's any problem with the above let me know and I'll try
to address it.