在面向对象程序设计中,一个基本的问题就是决定在你的程序中各个类之间的相互关系。一种可能性是从你已经定义的基类派生出若千个类来,以建立一个类的结构层次,并为特定的子类增加方法和数据成员。我们的Animal类和由它派生出来的子类就是这样一个例子。另一种可能性是定义一套类,它们没有结构层次关系,但是,有一些属于它们自己的类对象的数据成员。例如,Zoo类可能就有一个从Animal类派生的类对象作为成员。你可以有包含数据成员为类对象的类结构,其实我们已经在类Animal中接触过这种情况,它有一个String类型的成员。到目前为止,这些例子选择的方式都比较明确,但并不是总会这样地明显。你常常需要在把一个类作为层次结构中的一个类定义,还是作为一个类对象的成员的类定义之间进行选择。采用哪种方式最好呢?
与所有这类问题一样,没有一个明确的答案。如果面向对象程序设计是一个过程,而这个过程可以通过你只需盲目遵守的一套固定的规则来进行说明,那么我们就可以把它提交给计算机完成这些事情。尽管有一些准则,但可能解答中的内容可能更明显。
除了反映对象类型之间的现实世界关系的需求以外,使用多态性(或者我们在不久就会看到的接口)也是使用子类的一个主要原因。这是面向对象程序设计的精华,能够被等效处理的一堆相关的对象可以极大地简化你的程序。你已经看到如何从一个公共的基类Animal派生出来的类获得指定的各种动物类型,这将使我们可以对不同类型的动物进行处理,而它们就好像是同一种类型。产生的不同结果将取决于目前正在处理的动物类型,所有这些完全都是自动地。
一个漂亮的例子
许多场合都涉及到要对你的类设计做出判断。判断的方式可能最终归结为人们的偏爱。让我们思考一个简单的例子,试试看如何实际地进行选择。假设我们想定义一个类PolyLine,用来描述由多条相连的线段组成的折线,如下图所示。
这个示意图显示了两条折线,一条由4个点定义;另一条由7个点定义。
使用Point类对象描述点似乎比较合适。被定义好的点对象将会出现在各类几何体中。我们把前面看到的点类放置在Geometry包中。我们需要在此定义框架,而不是重复所有的东西。
public class Point
{
//create a point from its coordinates
public Point(double xVal,double yVall
{
x=xVal;
y=yVal;
}
你可以有三种方式定义ListPaint类,并且有相应的三种自变量来支持。
你可以用显式存储的x和Y定义ListPoint类。对应这种定义的主要自变量已经被囊括在Point类的点属性中,我们为什么不使用它呢?
我们可以把ListPoint对象当作包含一个对Point对象的引用,加上对链表中前驱和后继对象的引用的东西。这并非是不合理的途径。它很容易实现,并且与ListPoint的直觉概念不相矛盾。
你可以把一个ListPoint对象看成是一种特定的Point类型,因此,可以从Point类 派生出ListPoint类。究竟这样做是否合适将取决于你是否把它认为是合法的。我 的想法是进一步延伸点概念的用途,因此,我们不这样使用。
最好的选择看来是第二种方式。我们可以利用一个Point类型的数据成员实现ListPoint类,Point利用点的坐标定义一个基本的点。ListPnint对象还有一个额外的数据成员next.它是ListPoint类型的,并且链表中的每个对象,除最后一个对象外,next都将包含链表中下一个对象的引用。链表中的最后一个对象的变量next为null。
试试看——ListPoint类
我们可以通过类Point使用线面的代码定义ListPoint类:
public class ListPoint
{
//constructor
public ListPoint(Point point)
{
this.point=point; //store point reference
next=null; //Set next ListPoint as null
}
//Set the pointer to the nex ListPoint
public void setNext(ListPoint Next)
{
this.next next; //store the next ListPoint
}
//Gat the next point in the list
public ListPoint getNext()
{
return next; //Return the next ListPoint
)
//Return string pepreesentation
public string tostring()
{
return *{* + point+);
private LisPoint next; //Refers to next ListPoint in the list
private Point point; //The point for this list point
如何操作
ListPoint对象是建立Point对象序列的一种方式,这些Point对象位于其他的位置,因此,不必担心在链表中会出现重复的Point对象。我们可以只在数据成员point中存储传递给构造函数的Point对象的引用。数据成员next应该包含链表中下一个ListPoint的引用,由于在此我们还没有定义,所以把next设置为null.
当把一个新的点添加到序列中时,SetNext()方法可以为现存的最后一个点设置next数据成员。这个新的ListPoint对象的引用将作为自变量传递给该函数。GetNext()方法可以确定链表中的下一个点,因此,这个方法可以用来遍历整个链表。
若要为这个类实现tostring (),我们就可以在必要的时候,自动地为ListPoint对象建立一个String型的描述。这里,我们把point的String描述包含在括号之间以区别于ListPoint对象的String描述。
我们现在可以开始尝试实现PolyLine类。
试试看--PolyLine类
我们可以利用ListPoint类定义PolyLine类,定义如下;
public class PolyLine
{
//construct a polykine from array of points
public PolyLine(Point[] points)
{
if(points !=null) //Make sure there is an array
{
//create a one point list
start=new ListPoint(points[0]; //lst point is the start
end=start; //as well as the end
//Now add rhe other points
for (int i=1;i<point.longth;i++)
addPoint(points(i));
)
)
//Add a point object to the list
public void addPoint(Point point)
{
ListPoint newEnd =new ListPoint(point); //Create a new listpoint
if(start==null!
start=newEnd; //start is same as end
else
end.setNext(newEnd); //set next variable for old end as new end
end=newEnd; //store new point as end
)
//string represetation of a polyline
public string tostring()
{
stringBuffer str-new stringBuffer(Polyline)
ListPoint nextPoint=start; //ser the lst puint as start
while(nextPoint !=null)
{
str.append(nextPoint); //output current point
nexPoint=nextpoint.getNext(); //Make the point current
}
return str.tostring()
}
private listpoint start; //first listpoint in the list
private listpoint end; //Last ListPoint in the list
你可能希望这个类具有指定一对坐标,向序列中添加一个点的能力。你可以重载addPoint()方法来完成这项操作。
我们用表达式建立一个新的Point对象,并作为自变量传递给另一个版本的addPoint().
你可能还想要由一个坐标数组建立一个PolyLine对象。完成这个操作的构造函数是:
//Add a point to the list
publicvoid addpoint(double x,double y)
{
addpoint(new point(x,y));
}
我们用表达式建立一个新的Point对象,并作为自变量传递给另一个版本的addPoint().你可能还想要由一个坐标数组建立一个PolyLine对象。完成这个操作的构造函数是:
// Construct a polyline from an array of coordinate
public PolyLine(double[][]coords)
{
if(coords !=null)
{
//Create a one point list
start=new ListPoint(new point(coords[0][0],coords[0][1]);
//First is start
end=start; //as well as end
//New add the other points
for(int i=1;i<coords.length;i++)
addPoint(coords[i][0].coords[i][l]};
}
}
如何操作
PalyLine类有我们在示意图中看到的两个数据成员start和end.它们将引用序列中的第一个点和最后一个点,如果序列为空,它就为null.构造函数接收一个Point对象的数组,并且启动装配对象的过程,即首先建立一个包含由数组中第一个元素生成的ListPnint对象,然后再使用方法addPoint()把数组中其余的所有点添加进去。
把点添加到序列中出人意料地简单。所有的addPoint()方法都要首先利用自变量传递过来的Point对象建立一个ListPoint对象,然后设置序列中刚才的最后一个点的next成员内容,让它引用这个新点,最后,再把新的最后一个点的引用存储在end成员中。
方法tnString()同点坐标序列一样返回Po吵pint对象的字符串描述。注意,ListPoint对象next成员如何控制循环遍历这个序列。当到达最后一个点时,next成员将返回null,此时while循环结束。
现在,我们可以尝试着给出PolyLine类。