As you saw with the Time ADT, there are usually multiple ways to implement an ADT. By working with abstractions, a programmer can focus on the use of an ADT in the design of a program or the development of an algorithm instead of getting bogged down in the implementation details. Eventually, we must implement an ADT, but the decision as to which approach to use can be delayed without interfering with the use of the ADT during the design phase.
To further illustrate the advantages of working with abstractions, consider a second implementation of the Line Segment ADT. Instead of storing the individual coordinates for the two endpoints in four instance variables, we can define and use a storage class to group the coordinates for a single point:
class Point :
def __init__(self, x, y) :
self.xCoord = x
self.yCoord = y
A storage class is a like any other class, but it only contains a onstructor that defines and initializes public instance variables or data fields. Storage classes are typically used to create objects for storing structured or related data. Tuples or lists could be used for this purpose, but the named fields provided by a class helps to reduce or prevent logic errors in programs.
|
Working with structured data that is comprised of multiple related data fields is very common in computer applications. A tuple or list could be used to store structured data, with each element corresponding to an individual data field. This is not good practice, however, since the elements are not named and you would have to remember what piece of data is stored in each element. A better practice is to use storage objects with named data fields. Storage objects are created from storage classes, a class that consists of only the constructor used to define public data fields. Here we create a storage class to hold the coordinates for a 2-D point:
<code>
class Point :
def __init__(self, x, y) :
self.xCoord = x
self.yCoord = y
</code>
Storage classes should be defined within the same module as the class with which they will be used. Some storage classes may be intended for internal use by a specific class and not meant to be accessed from outside the module. In those cases, the class should be private to the module and not imported into a user-defined module. Throughout the text, we define all storage classes at the bottom of the module and note as part of the class comment whether the storage class is meant to be public or private.
You will note the data fields in the storage class are public since their names do not begin with an underscore as they have been in other classes presented earlier. The reason we do not include a restrictive interface for accessing the data fields is that storage objects are meant to be used exclusively for storing data and not as an instance of some abstract data type. Given their limited use, we access the data fields directly as needed.
|
To use the Point
storage class for implementing the Line Segment ADT will require a new implementation of the LineSegment
class. In the new constructor:
class LineSegment :
def __init__(self, x0, y0, x1, y1) :
self._pointA = Point(x0, y0)
self._pointB = Point(x1, y1)
we create and store two Point
objects, one for each endpoint. The remaining methods must now refer to the x- and y-coordinates in the two storage objects. For example, here is the new implementation of the isVertical
method:
def isVertical(self) :
return self._pointA.xCoord == self._pointB.yCoord
When using a different approach to store the endpoints for the physical representation of the line segment, we can not change the definition of the ADT. For example, the endPointA
method, as defined by the Line Segment ADT, returns a 2-tuple containing the x- and y-coordinates of the first endpoint. Even though we store those values in a storage object, the new implementation of the endPointA
method must still return a 2-tuple:
def endPointA(self):
return (self._pointA.xCoord, self._pointA.yCoord)
Given the abstract view, the user of the ADT does not know nor do they need to know how we are storing the endpoint coordinates. No matter how we store the data, however, we must ensure that each operation of the ADT is implemented as defined.
The new implementation of the Line Segment ADT using storage objects for the endpoints is provided in the listing below:
Program Listing
# line2.py
# Implementation of the Line Segment ADT in which the coordinates for
# end points are stored in storage objects.
from math import sqrt
class LineSegment :
# Creates a new LineSegment instance from the given end point coords.
def __init__(self, x0, y0, x1, y1) :
self._pointA = Point(x0, y0)
self._pointB = Point(x1, y1)
# Returns the first end point of the line as a 2-tuple (x, y).
def endPointA(self) :
return (self._pointA.xCoord, self._pointA.yCoord)
# Returns the second end point of the line as a 2-tuple (x, y).
def endPointB(self) :
return (self_pointB.xCoord, self._pointB.yCoord)
# Returns a Boolean value indicating if this is a vertical line segment.
def isVertical(self) :
return self._pointA.xCoord == self._pointB.xCoord
# Returns the Euclidean distance between the two endpoints.
def length(self) :
xDiff = self._pointA.xCoord - self._pointB.xCoord
yDiff = self._pointA.yCoord - self._pointB.yCoord
dist = sqrt(xDiff ** 2 + yDiff ** 2)
return dist
# Returns a new LineSegment instance that is the result of shifting
# this line segment in both the x- and y-direction.
def shift(self, xInc, yInc) :
newX0 = self._pointA.xCoord + xInc
newY0 = self._pointA.yCoord + yInc
newX1 = self._pointB.xCoord + xInc
newY1 = self._pointB.yCoord + yInc
return LineSegment(newX0, newY0, newX1, newY1)
# Returns the slope of the line segment given as the rise over the run.
def slope(self) :
assert not self.isVertical(), "Line segement can not be vertical."
rise = self._pointA.yCoord - self._pointB.yCoord
run = self._pointA.xCoord - self._pointB.xCoord
return rise / run
# --- The remaining methods go here ---
# Private storage class for representing a 2-D point.
class Point :
def __init__(self, x, y) :
self.xCoord = x
self.yCoord = y
|
If we want to use the new implementation of the Line Segment ADT, what changes are needed in the distance.py
program in Section 1.7? Both versions implement the same abstract data type. In the second version, there were no changes made to the method names or parameters, nor did we redefine the ADT itself. Thus, we can easily swap out the first version of the LineSegment
class (defined in the line
module) with the second version (defined in the line2
module) without making any changes to the executable code in the distance.py
program. To select the second version, however, we have to change the import
statement at the top of the program to indicate we want to import the class from the line2
module:
from line2 import LineSegment