I have an exercise where the following class hierarchy must be adhered to:-
Base class - Shape
then 2 derived classes
2d shape
3d shape
then we have
circle, square, triangle all inherited from 2d shape class
cube, cylinder, cuboid all inherited from 3d shape.
Each TwoDimensionalShape should contain function getArea to calculate the area of the two-dimensional shape. Each ThreeDimensionalShape should have mem- ber functions getArea and getVolume to calculate the surface area and volume, respectively, of the three-dimensional shape. Create a program that uses a vector of Shape pointers to objects of each concrete class in the hierarchy. The program should print the object to which each vector element points. Also, in the loop that processes all the shapes in the vector, determine whether each shape is a TwoDimensionalShape or a ThreeDimensionalShape. If a shape is a TwoDimensionalShape, dis- play its area. If a shape is a ThreeDimensionalShape, display its area and volume.
My issue is I am having trouble working out how to put what where so to speak. Is it right to put length, breadth, height in my base class with virtual functions for area volume and shape type?
So when the program is run it will show the area and/or volume for each shape with the correct type of shape.
The base class should have the methods common among all the sub-classes, so:
1 2 3 4 5
Shape {getArea()const, printObject()const}
2DShape : Shape {/*can't think of any methods or attributes particular to this class that can't
be put in Shape itself that therefore raises the question if this class is really necessary
since getArea()const can just as well be put in Shape*/}
3DShape : Shape {getVolume()const}
Is it right to put length, breadth, height in my base class
No, because for one what are you going to call them? It'd be a stretch to have one term encompass the radius of a circle, the side of a square and the length/width of a rectangle. I suppose you could all them dimension1, dimension2 etc then these terms would be meaningless when read from the program and there'd be unused variables as well
Thats exactly what I was having trouble with. Why bother with the 2d shape class?
Is it enough to omit that class and say all shapes are 2d unless they explicitly say otherwise?
A "3D Shape" is not really a shape in the true sense of the word, rather a "3D Model". So one could argue that "3D Model" IS NOT A (as opposed to IS A) "3D Shape". The IS A is one of the important relationships: the others are HAS A, and USES A.
Also, in the loop that processes all the shapes in the vector, determine whether each shape is a TwoDimensionalShape or a ThreeDimensionalShape.
That statement is also a pointer to bad OOP design. One should be able to traverse the vector without having to do any casting or RTTI.
Also, to do polymorphism, one needs to have an interface of pure virtual functions which cover all of the derived classes. The same functions should apply to all the derived classes - this goes back to the IS A concept.
Thanks! Can you explain
"That statement is also a pointer to bad OOP design. One should be able to traverse the vector without having to do any casting or RTTI."
in more detail please!
Those are symptoms of bad design. The user interface functions should apply to all the derived classes: one shouldn't have to "shoe horn" to force the code to work. By casting I mean dynamic_cast, and RTTI is Run Time Type Identification
The problem here is the volume function: what happens when a volume function is applied to a circle? It doesn't exist. Having a volume function for the 2D objects that returns 0.0 is a cop out, even though it might be a virtual function defined once. That might be a strategy for a "conforming" assignment though :+) I would ask your Tutor.
Thanks for your input!! I appreciate it. Unfortunately I don't have a tutor - I am working through C++ How to Program 8 on my own, and have managed to get this far with the help of this excellent forum. I am at polymorphism... lol
I understand what you are saying, maybe I should take a different approach to the question. Have a shape baseclass, then maybe have derived classes onesdided, 3sided, 4sided. then inherit from these circle(onesided), triangle(3sided) square, rectangle(4sided)
The Fubar is not all abstract. All Fubar can bah, so it makes sense to implement it there.
(In your case the bah, or area, would be pure virtual, because you do implement it in the derived classes.)
I reckon there are several areas of confusion in all this.
In AutoCAD parlance, there is the concept of a Solid - that is something which can have a volume. 2d shapes (that is, planar) can exist in a 3d coordinate system. Normally a system is either all 2d or all 3d.
Also in AutoCAD there is a DrawingEntity concept. So this is the base class; derived from it are Shape, Arc, Point, Line, Solid, Text, Dimension etc. Further derived concrete classes come from those.
The base class should only have functions which apply to all derived classes. The existence of a Print or Draw functions justifies the existence of the DrawingEntity class (or in the Shape class as it stands now). The DrawingEntity class also has Layer, Colour etc, because all the Entities have these.
Even the Area function is problematic in the Shape class (as it stands now, like having Area in the DrawingEntity class), because even if it were applied to 2D and 3d objects (as we could using a Shape pointer), it's not quite the same concept: for example it is probably wrong to sum all the areas of 2D and 3d objects. So we could have a pure virtual function Area() in the Shape class (in my version of the inheritance tree) ; and the Solid class would have SurfaceArea() and Volume() .
Another problem with Area() is we now have various Arc classes, so that is another violation: we added something new, and now we need to change the base class.
One of the things about class design is the concept of pushing variables and functions as high up the tree as possible. Sometimes it makes sense to create a new abstract class to get the most out of that idea. For example in a Role Playing Game, we have Player and Enemy; they both have a Health value. So we create the base class Actor which has the health value, and Player and Enemy derive from it. I did the same thing with DrawingEntity.
Another important thing is being careful naming things. IMO having 2D shape and 3D Shape leads to bad design; I made the distinction between Shape and Solid. The other distinction was Area (2d) and SurfaceArea (3d)
then maybe have derived classes onesdided, 3sided, 4sided. then inherit from these circle(onesided), triangle(3sided) square, rectangle(4sided)
Once you learn about templates, you could have the type RegularPolygon<N, Radius> where N refers to the number of sides. I wouldn't use it for circles though.
Hmmm sounds like it is particularly difficult. My base class should have functions that apply to all derivatives.
Clearly this is difficult.
Circle - perimter PI*D, area PI r^2
Triangle - perimeter side1+side2+side3, area 1/2 side1*side2
Square - perimeter 2*lenght + 2*breadth, area lenght * breadth
And that's just 3! So from above there isnt much in common to put in my base class to inherit?
The RegularPolygon<N, Radius> constructor uses the same code no matter how many sides there are, up to a limit: say 3 to 1024.
A regular polygon has points which all lie on a circle. The polygon can be divided into N isosceles triangular segments with the angle 360/N at the centre, so with some trigonometry one can work out the co-ordinates of each point and put them in a vector. The area and perimeter are easy to understand if you work from the same segment idea.
Note that one doesn't use this for a circle.
The common things to put in the Shape class are pure virtual Area() and Perimeter() functions. RegularPolygon is a concrete class, which has the actual implementation of those functions.
Thanks everyone. I ned to go and think the best way to implement it. Ideally I want to create a vector of pointers to bas class and implement it that way...
I am working through C++ How to Program 8 on my own
Just because it's in a book, doesn't mean it's correct.
I can sympathize with the Dietels: even if they actually know what they are doing (wouldn't bet on it), it is difficult to come up with simple correct examples of dynamic polymorphism. They probably want to demonstrate some syntax basics and needed a strawman class hierarchy. If you ignore that it makes no sense, you can still learn how to define and call a virtual function or how to identify a polymorphic type at runtime.
I reckon it's quite constructive to take bad examples from books, and have the members here tear them to shreds, and specify how they would do it instead. As long as the question is posed that way, not trying insist that the answer is sledge hammered until it fits the question.
It's possibly flogging a dead horse but a few major problems I had with this appear to have fairly orthodox and well known solutions if you go into it. Stroustrup tends to make a meal of it in his book spreading the 'sweat' over a couple of chapters and very tersely ( I bet no surprise from Stroustrup on that score). I think if there was any force-fitting it's in the C++ language itself.
Slicing, override, virtual, inherited operator overloading, relevant STL use etc can all be learned from this one simple problem. FWIW regular objects are simply a generalization of what is going on here. gunner alluded to it and it's not so difficult to cater for cuboids and rectangles along with circles all with different number of dimensions. The hierarchy of inheritance is not fixed because the 3d shape can be inherited from the 2d shape just as much as each can be separately inherited from the base shape. That's just a design choice.
Where the criticisms fail, especially the 'attack' on Deitel, is the issues are not really about the nature of the objects being modelled and class names or whether they are strawmen or whether Autocad does this or that. The criticisms go nowhere in terms of addressing the underlying programming issues and wonders of C++ and how that language handles OOP inheritance and polymorphism problems. I haven't read what Deitel says but I think he and his son have been around long enough to know a bit about teaching C++ and the difficulties students have of mastering some of the concepts in C++ which after all is only a programming language. Their books are OK.
Maybe the only reason C++ standards hasn't ironed out some of these things is because the language rapidly collapses into Java. :)