The solution to the problem outlined previously is valuable to understand because it will help you recognize the principles behind many professional graphics systems, including the TKinter graphics system used by Python. It also helps you understand what is happening in the graphics systems used in the Java programming language. Another benefit of the solution is that once you have the mechanism in place to solve the current problem, you will be able to implement a move
method for all graphics objects very easily.
The solution to the problem begins with two additional instance variables for the GeometricObject
class and one additional instance variable and one more method for the Canvas
class. For the GeometricObject
, one variable will keep track of whether the object has been drawn. We might call this instance variable _ _visible
. The second instance variable, _ _myCanvas
, will keep track of the Canvas
on which the object is drawn. The additional instance variable for the Canvas
object, _ _visibleObjects
, will allow the Canvas
to remember an ordered list of the objects it has drawn.
To better understand the importance of these three instance variables, let’s think about what will happen when we make a call like the following: theCanvas.draw(myLine)
.
Set the value of the _ _visible
instance variable of the line to True
.
Set the value of the _ _myCanvas
instance variable of the line to point to theCanvas
. At the moment this may seem circular to you, but it should become clear when we talk about what happens when you call setColor
.
Call the line’s _draw
method.
Add the line object to the _ _visibleObjects
instance variable of the canvas.
At the end of this sequence, the line will be drawn on the canvas. More importantly, the line will now “remember” which canvas it was drawn on, and the canvas will know all the objects that have been drawn on it.
Next, let’s consider the sequence of events that occurs when we make a call to change the color or width of the line such as myLine.setColor('red')
.
Modify the _ _lineColor
instance variable of the line to have the new value of 'red'
.
If the _ _visible
instance variable is True
, then use the _ _myCanvas
instance variable to call the method drawAll
on the canvas.
In the Canvas
class, the new drawAll
method clears the canvas and redraws all objects on the canvas in the same order as they were drawn originally. Although we may not need to redraw all the objects on the canvas, it would be more work to figure out which objects need to be redrawn than it would be to just redraw everything. The new design for our classes is shown in FIGURE 12.8. This figure includes all the new instance variables and methods we have discussed. In UML, abstract classes and abstract methods are indicated by putting the names in italics. Thus, in Figure 12.8, GeometricObject
and the _draw
method in that class are italicized.
Even though we have made some major improvements to the functionality of the Point
and Line
classes, we have actually changed only the Canvas
and GeometricObject
classes. LISTING 12.8 presents the new version of the Canvas
class. Relative to our earlier implementation in Listing 12.3, we have added two new methods: drawAll
and addShape
. In addition, we have made small modifications to draw
and _ _init_ _
.
The modifications to _ _init_ _
and draw
affect the initialization and updating of the _ _visibleObjects
instance variable. When a canvas is first created, the list of objects drawn on the canvas is initialized to an empty list. Whenever draw
is called, the gObject
passed as a parameter is appended to the end of the list. Consequently, all objects that are visible on the canvas are stored in the _ _visibleObjects
list, and the order in which they were drawn is preserved.
The addShape
method is simply a convenient way to add an object to the _ _visibleObjects
list. The reason we do not call _ _visibleObjects.append
directly is that by consistently using the addShape
method, we limit any dependencies on how we represent the objects drawn on the canvas to one method. Imagine that sometime in the future you decide to use a dictionary, rather than a list, to keep track of the objects on the canvas. If you had used _ _visibleObjects.append
in several places, you would need to change each of those places to accommodate the new representation. However, because we have been clever and used the addShape
method, we would need to change only the way we add an object to the canvas in one place.
The biggest addition to the Canvas
class is the drawAll
method. This method iterates over all the GeometricObject
objects on the _ _visibleObjects
list and has each object perform its _draw
method. In this way, we can re-create whatever picture is visible on the canvas by calling a single method. This loop demonstrates polymorphism in object-oriented programming in a very powerful way. Notice that we do not need to keep a separate list of lines, points, and other shapes; instead, we need only one list of objects. Because each object inherits from GeometricObject
and provides a _draw
method, Python finds the correct _draw
method for each object.
Also notice that the first line of the method calls self._ _screen.reset()
. The reset
method of the screen creates a blank canvas by erasing anything that the turtle has previously drawn, and puts the turtle in the center of the canvas again.
LISTING 12.9 shows the new GeometricObject
class, which has fairly small changes. We have added two new setter methods: setVisible
and setCanvas
. In addition, the setColor
and setWidth
methods have been modified to call the drawAll
method whenever the object being changed is visible.
Now, if we run the test2
code in Listing 12.7, FIGURE 12.9 shows that the changes made to our lines after being drawn do take effect: line1
is red and line2
has a width of 4.
18.225.95.216