We have neglected a large part of our initial inheritance hierarchy in favor of focusing on a few key implementation details. Now that we have made it possible to properly modify the color, line width, and visibility of Points
and Lines
, we can turn our attention to adding new graphical objects. You will see that inheritance will make it easy to add new objects that are quite powerful without writing a lot of code.
Let’s begin by working down the inheritance hierarchy, starting with Shape
. Shape
is an abstract class that will hold an instance variable that will let us know whether an instance of Shape
should appear as an outline or whether the shape should be filled in with a color. LISTING 12.10 shows that the Shape
class also inherits from the GeometricObject
class. Like the setColor
and setWidth
methods in the GeometricObject
, the setFill
method triggers a call to redraw all visible objects. Note that because the Shape
class inherits from the abstract GeometricObject
class and does not implement the _draw
method, the Shape
class is also abstract. In consequence, an attempt to create an object of the Shape
class will generate a type error.
The next class to consider is Polygon
, which can be either abstract or concrete. That is, we can probably imagine creating an instance of a Polygon
as representing some irregular multisided closed shape. In fact, we can just stop with Polygon
and implement squares, triangles, octagons, and similar shapes as polygons. This is one of those design decisions that has no right or wrong answer. If we choose to make Polygon
abstract, then we need to add explicit subclasses for every possible polygon we might want to use in an application. If we choose to allow Polygon
to be concrete, then we can implement some subclasses to make it easy to construct the most commonly used polygons. We will choose the latter approach and make Polygon
a concrete class—that is, we will implement a _draw
method.
For each of our shapes, the most important method we must develop is _draw
. If we think carefully, we can implement a single _draw
method to draw any polygon. But how can we represent the shape of a polygon? The answer is that we can represent any polygon using a list of points. A triangle has three points corresponding to its three corners; a square has four points corresponding to its four corners; and so on. In addition to knowing the positions of the corners of our polygon, it is important to specify them in some order, so that we can go from corner to corner and get an outline of the shape. If the corners are randomly ordered, we may just get a bunch of random lines.
There are two logical choices for the order in which to specify the corners: clockwise and counterclockwise. Most graphics systems use a counterclockwise ordering for the corners of a polygon.
A counterclockwise ordering means that we can draw any polygon by drawing lines between points that are adjacent in our ordered list of points. FIGURE 12.10 shows an example parallelogram with four points. These four points could be stored in a list such as [p0, p1, p2, p3]
. We can then draw lines from p0
to p1
, from p1
to p2
, and from p2
to p3
because they are next to one another in the list. However, this gives us only three of the four sides: We must also draw a line from p3
to p0
to complete the shape.
If we had specified the corners of our polygon in the order [p0, p1, p3, p2]
, we would get a completely different shape that is not a polygon at all.
To implement the _draw
method for any polygon, you can simply iterate over the list of corners and have the turtle go from point to point. When you run out of points in the list, you must have the turtle draw one final line back to the first point on the list. If a polygon has a _ _fillColor
other than None
, then you will need to call the begin_fill
and end_fill
methods of the turtle before and after you start drawing lines.
Now that you know how to represent and draw any polygon, what about Rectangle
, Triangle
, and Square
? The key for these three classes is how we write our constructor. For an arbitrary polygon, the constructor must take a list of points. For a specific polygon, we can make it a bit more user friendly. For example, the constructor for Triangle
can take three points, and the constructors for Square
and Rectangle
can each take just two points.
The points needed to specify a rectangle are the lower-left corner and the upper-right corner. If you know the lower-left and upper-right corners, you can figure out the position of the other two corners quite easily, as shown in FIGURE 12.11 (where the lower-left coordinates are prefaced with LL and the upper-right coordinates are prefaced with UR). Listing 12.1 provides examples of the constructors for a rectangle. The constructor for a square is identical to the constructor for a rectangle except that it should check that the lengths of all sides are the same.
FIGURE 12.12 depicts the new version of our class hierarchy. We will not show you the Python implementation for Polygon
; instead, implementing the rest of the hierarchy is left as an exercise for you.
18.119.125.120