File APIs for Java Developers
Manipulate DOC, XLS, PPT, PDF and many others from your application.
http://aspose.com/file-tools
The moose likes OO, Patterns, UML and Refactoring and the fly likes Good design? Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login
JavaRanch » Java Forums » Engineering » OO, Patterns, UML and Refactoring
Bookmark "Good design?" Watch "Good design?" New topic
Author

Good design?

jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Have a homework "problem" where I am supposed to define the typical class hierarchy for Shapes. I start with an abstract Shape class, then have abstract 2-D and 3-D shape classes that inherit from it, then each have abstract classes that inherit from those, and so on until I get to concrete classes like circle, square, rectangular prism, etc. Each class is to have a print() method, as well as a area() or volume() method, depending on if it is 2 or 3 dimensional.
Easy enough, my problem comes from what is the best way to define these classes. For example, my Polygon class inherits from TwoDimensionalShape, which inherits from Shape. All of these classes are abstract (not going into enough detail in this assignment to actually implement a method to calculate the area of an irregular polygon). The definition for this class would basically be an empty class since I'm not providing an implementation for the methods. When I get all the way down to my Square class (which inherits from Rectangle, which inherits from Parallelogram, which inherits from Quadrilaterial, which inherits from Polygon...), it just seems to me that creating an object of Square is going to take a lot more work than is necessary (having to work all the way up the hierarchy).
Is it normal to have empty abstract classes just to conform to what the real world structure of that hierarchy would be like? And would it make sense to call super() in the "bottom" most classes all the way up the chain, or just hold their own data values (attributes would be length, width, and height)?
Hope all that makes sense
Jason
[This message has been edited by jason adam (edited December 03, 2001).]
Junilu Lacar
Bartender

Joined: Feb 26, 2001
Posts: 5288
    
  10

Originally posted by jason adam:
When I get all the way down to my Square class (which inherits from Rectangle, which inherits from Parallelogram, which inherits from Quadrilaterial, which inherits from Polygon...), it just seems to me that creating an object of Square is going to take a lot more work than is necessary (having to work all the way up the hierarchy).

There's no escaping the call to super() anyway so I would just use that to my advantage.

Junilu


Junilu - [How to Ask Questions] [How to Answer Questions]
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Just dealing with lengths and heights in this program, but that is basically how I have it, all the way up to quadrilateral (where the protected data members are housed). My print() method (defined as abstract in the Shape class, which is the parent of all the rest) climbs all the way up the chain too, to the TwoDimensionalShapes class, where I define once:
return this.getClass().getName();
Wasn't sure if that was going to work, but it does. This forces me to put a print() method in every class up to the 2D one, each calling super.print(); but this way I can have a Shape[] and know I'll get what I want. Works nice, just wanted to make sure there was not a better way to optimize.
Thanks
Jason
Ilja Preuss
author
Sheriff

Joined: Jul 11, 2001
Posts: 14112
Hi Jason,
take a look at http://ootips.org/ellipse-circle.html


The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Great link, Ilja!
I'm pretty sure I've read Robert Martin making pretty much the same argument on comp.object about squares and rectangles -- that when modeling them in software you should focus on capturing the behavior, which would lead to having them both inherit from the same superclass, instead of focusing on their realer-world/mathematical relationship, which would lead to having square as a subclass of rectangle.
(One problem arises with the contracts of mutators -- Rectangle's setHeight() has the contract that it changes the height, but does not affect the width, and setWidth() has the contract that it changes the width, but does not affect the height. Square, as a subclass of Rectangle, would have to override those methods so that both setHeight() and setWidth() affect both height and width.
Since in this model squares are rectangles, any square object can be upcast to a rectangle, so code such as this is valid:
Rectangle r1 = new Rectangle() ;
Rectangle r2 = new Square() ;
Suppose you have a collection of rectangle references (which may contain squares!) and you iterate over all of them increasing their height:
r.setHeight( r.getHeight() + 10 ) ;
Any squares in this collection of rectangle references will get their width increased too, which may not be what you want.
(Should the constructor for a Rectangle barf if you send in a height and width that are equal? Are such degenerate rectangles allowed? What should equals() do for the following?
Rectangle r = new Rectangle( 5 , 5 ) ;
Rectangle s = new Square( 5 , 5 ) ;
boolean huh = r.equals( s ) ;
)
Suppose you're writing software that optimizes how boxes can be packed into a certain space. You want to add 6 inches to the height of all your boxes to test some aspect of the program. If squares are rectangles, you end up adding 6 inches to the height too. (Suppose you're starting with a square box, but you want to add 6 inches only to the height.)
(OK, I just realized I switched from 2D to 3D shapes for the box example and a cube would get its depth increased too, but I think the idea is still the same.)
However, one way round the mutator issue may be to make these objects immutable, which is a whole nother conversation.)
Jason -- along these lines, a question that may be useful in deciding whether such a detailed hierarchy is necessary would be whether you would want to upcast any concrete shape objects to any of the abstract classes.
In other words, would you be writing code like:
Parellelogram p1 = new Square() ;
Parellelogram p2 = new Rectangle() ;
Quadrilateral q1 = new Rectangle() ;
etc.
Michael
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Originally posted by jason adam:
My print() method (defined as abstract in the Shape class, which is the parent of all the rest) climbs all the way up the chain too, to the TwoDimensionalShapes class, where I define once:
return this.getClass().getName();
Wasn't sure if that was going to work, but it does. This forces me to put a print() method in every class up to the 2D one, each calling super.print(); but this way I can have a Shape[] and know I'll get what I want. Works nice, just wanted to make sure there was not a better way to optimize.

Unless I'm missing something, I don't see why you need

in so many classes. (In fact, I can't see why you'd ever write something like super.methodCall() without any other code in the method.)
Put your definition

in the highest concrete class and let "this" do its work.
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Thanks for the link Ilja, though I have almost no understanding of C++, so I kind of got lost . I can see the logic, though. It kind of reflects what I have in my square/rectangle class. Square inherits from Rectangle, but instead of stating area as length * length, I simply have my constructor for Square call super( length , length ); and my area() method call super.area(); Does it make sense to have this? Doesn't really, other than for the sake of having a real world hierarchy.
Didn't try that Mike, will have to. I figured that you would need the chain of super.print() so that "this" knew that you were talking about the object you just instantiated. Never had to deal with a group of classes this deep, so kind of new territory.
Basically what I have is an array of Shapes, and have a switch block (ok, I HATE switch, no reason why other than it just personally bothers me, but in this case it actually seems decent to use) that randomly generates a number, and based on the case creates a certain class. I have absolutely no accessors or mutators, so I pretty much bipass all the problems that you brought up (this is a beginners Java course, what I am doing is well above and beyond the scope of the class).
My biggest "problem" is that I like to create my classes and hierarchies based on how I envision them in real world examples. Also, I come from a zoology background, so I love placing things in hierarchies
Thanks for all the suggestions/info.
Jason
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Ok, it's late, but I remembered why I called super.print() in all my classes. Tried your way Mike, and it fails for this reason. My print() method is supposed to display not only what the class is, but what the dimensions are. So for a circle, I need to show what the class is, which makes the call to super.print(), but also I concat a literal stating what the radius is. A trapezoid states that there are two lengths, and one height. A square states the length of a side. You get the idea.
I could just put the name of the class in the literal, would that make more sense than calling super.print() all the way up to TwoDimensionalShape? Thinking I actually should put the implementation in Shape, since both 2D and 3D classes will be using it.
Is this just a really funky way to do this? Remember, the whole basis for the assignment is to learn inheritance. I've got the concept of inheritance down, now the hard part is well designed inheritance!
Jason
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Originally posted by jason adam:
I could just put the name of the class in the literal, would that make more sense than calling super.print() all the way up to TwoDimensionalShape? Thinking I actually should put the implementation in Shape, since both 2D and 3D classes will be using it.

GoF's Template Method seems the way to go to me. (Also see Martin Fowler's Form Template Method.
Broadly speaking, your print() method in all cases does two things: (1) prints the class name, and (2) prints some data specific to the particular class, right? Abstract that behavior out into a generic print() method in your highest class. Have the print() method print the class name then call another method printDetails() that you define in each concrete class.
For example:
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
A slightly fussier version would be:
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Ok, I'm leaving all kinds of requirements out until the end, but the print() method at the top HAS to be abstract, so no implementation there. However, I will try that at the second level in the hierarchy. I like what you have shown though, Mike, makes a lot of sense!
What if I wanted to return a String? So that all the printing is handled in my driver class/program instead of by the actual objects (this is the way I prefer).
Jason
[This message has been edited by jason adam (edited December 04, 2001).]
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Been looking over both my model and my code, and I can't see a way to simplify if I want to return the String to the program to print, instead of printing in the actual shape classes. I have to send the call to super.print() up the chain until it gets to TwoDimensionalShape, and then return that String back down to the calling class. Question is, is that good design (be blunt)? Seems like a lot of method calls and returns.
Another thing, here's my Square and Rectangle code:

I think this goes back to the whole discussion above and the link provided. If I wanted to develop this whole program further to use stuff like extending in a certain direction or whatever (like the link discusses), I think I would say the Square class is needed, as long as you wanted to keep the object a square. If you wanted to extend the length but didn't care about the height, I wouldn't use a subclass, I'd just say a square is a rectangle with equal length and height and leave the subclass part out.
Jason
[split overlong line in code - Jim]
[ May 25, 2002: Message edited by: Jim Yingst ]
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Originally posted by jason adam:
What if I wanted to return a String? So that all the printing is handled in my driver class/program instead of by the actual objects (this is the way I prefer).

String-a-ma-fied version coming up. I changed the method names because I don't like having a print() method that doesn't actually print but returns a String.
Didn't use toString() because I feared your assignment had some use for it already.

jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Ah, let me tinker with it and see if I can incorporate that in there. Ya know, I never tried calling an abstract method from an implemented method in an abstract class, seems odd. Is this something you see normally?
I wish toString() was being used. I would be MUCH more comfortable with that for some reason, it's logical to use that if I want to get the state of an object. But no, we're restricted to using just print().
Let ya know how it goes, and thanks for all the help
Jason
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
Absolutely beautiful, you da man Mike. Probably using print() differently than what the teach is expecting, but he never said how to implement, just that it needs to be there
Jason
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Originally posted by jason adam:
Ya know, I never tried calling an abstract method from an implemented method in an abstract class, seems odd. Is this something you see normally?

It's a fairly typical idiom for the Template Method pattern. The template method (here, print() or getString()) is concrete. The methods that the template method calls can be either concrete or abstract in the class defining the template method, depending on where the most suitable place to implement them is.
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Originally posted by jason adam:
Another thing, here's my Square and Rectangle code:
[CODE]
public class Rectangle extends Parallelogram
{
.
.
.
public float area()
{
return super.area();
}

public String print()
{
return super.print();
}

If area() and print() are defined in Parellelogram (or a superclass of Parellelogram), then these two methods that consist only of a call to the superclass method don't do anything that polymorphism wouldn't already take care of. Comment them out if you don't believe me.
Michael Matola
whippersnapper
Ranch Hand

Joined: Mar 25, 2001
Posts: 1757
    
    3
Here's another way to use print() in a way teach prolly doesn't expect:

To print to a file, pass in a FileOutputStream or somesuch instead of System.out. Can't rememeber off the top of my head whether servlets use a PrintStream...
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
I knew there was something odd about that. Need to keep in mind that with inheritance, the code from the super class is "there" in the subclass, in a sense, unless you override it. When I originally built all this out, I was going to try and calculate the area of the parallelogram given just the length of the base and the length of a side. However, I decided not to get all into the trig stuff and just made it so you are given the height already. Took out the super calls in Rectangle, works perfectly. Of course, if I decided later to come back and implement calculating the area like I said, I would have to add area() and print() back... but I don't see that happening
Jason
jason adam
Chicken Farmer ()
Ranch Hand

Joined: May 08, 2001
Posts: 1932
LOL, as much as I like that solution, don't think I need to go that crazy with it.
Servlets use PrintWriter instead of PrintStreams
Jason
[This message has been edited by jason adam (edited December 04, 2001).]
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Good design?