8.2 通过继承扩展—添加新方法
继承通常用于扩展一个类。举个例子,假设你刚接受一项任务:开发一些处理2D图形对象(如:矩形、圆形和三角形)的类。现在,只考虑矩形。实际上,回顾第4章“数据类型和表达式”的练习7,从以下例子开始@interface部分:
@interface Rectangle:NSObject
{
int width;
int height;
}
@property int width, height;
-(int)area;
-(int)perimeter;
@end
当前的方法可以设置矩形的宽与高,返回它们的值并计算其面积与周长。再添加一个方法,它允许你使用相同的消息调用来设置矩形的宽度与长度值,如下:
-(void)setWidth:(int)w andHeight:(int)h;
假设将新类声明键入名为Rectangle.h的文件。实现文件Rectangle.m将如下面所示:
import“Rectangle.h”
@implementation Rectangle
@synthesize width, height;
-(void)setWidth:(int)w andHeight:(int)h
{
width=w;
heighe=h;
}
-(int)area
{
return width*height;
}
-(int)perimeter
{
return(width+height)*2;
}
@end
每个方法定义都很直观。代码清单8-2显示了一个测试它的main例程。
代码清单8-2
import“Rectangle.h”
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Rectangle*myRect=[[Rectangle alloc]init];
[myRect setWidth:5 andHeight:8];
NSLog(@“Rectangle:w=%i, h=%i”,
myRect.width, myRect.height);
NSLog(@“Area=%i, Perimeter=%i”,
[myRect area],[myRect perimeter]);
[myRect release];
[pool drain];
return 0;
}
代码清单8-2输出结果
Rectangle:w=5,h=8
Area=40,Perimeter=26
给myRect分配内存并将其初始化,然后将其宽设为5、高设为8。输出的第一行验证了这一点。然后,使用合适的消息调用,计算矩形的面积和周长,返回值由NSLog进行显示。
处理矩形之后,假设现在需要处理正方形。可以定义一个名为Square的新类,并在其中定义同Rectangle类相似的方法。或者,认识到正方形只是长方形的特例,长和宽恰好相同的长方形。
因此,简单的处理方法就是定义一个名为Square的新类,并使它成为Rectangle的子类。这样除了定义自己的方法和变量之外,可以使用Rectangle类中的所有方法和变量。现在,可能要添加的唯一方法可能是将正方形的边设置特定的值,并检索该值。代码清单8-3显示了Square类的接口文件和实现文件。
代码清单8-3 Square.h接口文件
import“Rectangle.h”
@interface Square:Rectangle
-(void)setSide:(int)s;
-(int)side;
@end
代码清单8-3 Square.m实现文件
import“Square.h”
@implementation Square:Rectangle;
-(void)setSide:(int)s
{
[self setWidth:s andHeight:s];
}
-(int)side
{
return width;
}
@end
注意此处所做的工作。你将Square定义为Rectangle的子类,这是在头文件Rectangle.h中声明的。这里不必添加任何实例变量,但是添加了两个名为setSide和side的新方法。
虽然正方形只有一个边,但在内部也可用两个数表示,这样也可以。这对于Square类的用户是隐藏的。如果需要,以后随时可以重新定义Square类。根据前面介绍的封装概念,任何用户都无须担心内部的细节问题。
setSide:方法利用从Rectangle类继承的方法设定矩形宽度与长度的值。所以setSide:调用Rectangle类的setWidth:andHeight:方法,同时传递参数作为宽度与长度值。实际上不必再进行其他任何操作。处理Square对象的人通过利用setSide:方法,可以设置正方形的大小,并利用Rectangle类的方法计算正方形的面积、周长,等等。代码清单8-3展示了新的Square类的测试程序和输出。
代码清单8-3测试程序
import“Square.h”
import<Foundation/Foundation.h>
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Square*mySquare=[[Square alloc]init];
[mySquare setSide:5];
NSLog(@“Square s=%i”,[mySquare side]);
NSLog(@“Area=%i, Perimeter=%i”,
[mySquare area],[mySquare perimeter]);
[mySquare release];
[pool drain];
return 0;
}
代码清单8-3输出结果
Square s=5
Area=25,Perimeter=20
定义Square类的方式是在Objective-C中使用类的基本技巧:扩展自己或其他人以前实现的类,使其适合自己的需要。另外,所谓的分类(category)机制允许你以模块方式向现有类定义添加新方法,也就是,不必经常给同一接口和实现文件增加新定义。当想要对你没有源代码访问权限的类添加新定义时,这特别方便。在第11章“分类和协议”中将学到分类。
8.2.1 Point类和内存分配
Rectangle类只存储矩形大小。在实际的图形应用中,可能需要保存各种附加消息,如:矩形的填充色、线条颜色、窗口中的位置(原点),等等。可以方便地扩展这些类来处理这些情况。现在,处理矩形原点的概念。假设“原点”是指笛卡尔坐标系(x, y)中矩形左下角的位置。如果正在编写绘图应用程序,这一点可能代表矩形在窗口中的位置。如图8-4所示。
图 8-4 窗口中绘制的矩形
在图8-4中,矩形的原点是(x1,y1)。
可以扩展Rectangle类,将矩形原点(x, y)保存为两个不同的值。或许你已经认识到在开发图形应用程序的过程中,要处理很多坐标,因此要定义一个名为XYPoint的类(在第3章的练习7中出现过这个问题):
import<Foundation/Foundation.h>
@interface XYPoint:NSObject
{
int x;
int y;
}
@property int x, y;
-(void)setX:(int)xVal andY:(int)yVal;
@end
现在,回到Rectangle类。希望能够存储矩形的原点,所以,必须向Rectangle类的定义添加另一实例变量origin:
@interface Rectangle:NSObject
{
int width;
int height;
XYPoint*origin;
}
……
应该再添加一个方法,设置矩形的原点并对其进行检索。为突出重点,我们这里不综合原点的存取器方法,我们将自行编写。