12.8 Implementing Polygons

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.

Image

LISTING 12.10 The Shape class

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.

A figure shows a parallelogram with four points p1, p2, p3, and p4, marked at the four corners. The corners p1, p2, p3, and p4 are specified in counter clockwise order.

FIGURE 12.10 A correctly drawn polygon with corners specified in counterclockwise order.

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.

A figure shows a rectangle whose corners are marked (LLx, URy), (URx, URy), (LLx, LLy), and (URx, LLy).

FIGURE 12.11 Using the information for the lower-left and upper-right corners to determine other corners of a rectangle.

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.

A class diagram shows an inheritance hierarchy including details for polygons.

FIGURE 12.12 Inheritance hierarchy including details for polygons.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.125.120