Delphi高手突破第二章,3,封装,继承,多态

==============================封装======================================

封装目的:把可变部分与稳定部分分离开来。将稳定部分暴露给其它块,而将可变部分隐藏起来,以便随时让它改变。

Object Pascal中,实现了两个级别的封装:类级和单元级

类级别的封装:

published与public差不多,区别在于published的成员可以被Delphi开发环境的Object Inspector所显示。

所以对于外部类,public和published是对外界的接口;对于派生类,接口是public和published和protected的集合,只有private是内部细节。

单元级的封装:

1. 在一个Unit中声明的多个类,互为友元类(可相互访问private数据)

2. 在一个Unit的interface部分声明的变量为全局变量,其它Unit可见

3. 在一个Unit的implementation部分声明的变量为该Unit的局部变量,只在该Unit可见

4. 每个Unit可有单独的初始化段(initialization)和反初始化段(finalization),可在编译器支持下自动进行Unit级别的初始化和反初始化。

Object Pascal的单元文件被分为两个部分:interface和implementation

如同类的封装一样,Unit的这两部分分别为接口和实现细节。因此interface部分对外是可见的,声明中interface段中的所有函数、过程、变量的集合,都是单元文件作为一个模块的对外接口。而Implementation部分是对外隐藏的。

而为单元文件提供初始化和反初始化机制,即保证了单元的独立性,由此可以脱离对其它模块的依赖。其作用类似于类的构造函数和析构函数。

定义接口的规则:

1. 保证接口是功能的全集,即接口能够覆盖所有的需求

2. 尽量让接口是最小冗余的,有利于客户的学习和使用。

3. 接口是是稳定的。能保护客户的代码在细节改变的情况下,不随之改变。

绝大多数失败的设计,都来自于失败的封装。

==============================继承======================================

定义继承关系:

TB = class(TA)

Object Pascal只支持C++中所谓的public继承:

public还是public,protected还是protected,private依然被继承为private!!!!

public在语义上严格奉行“是一种”关系,也就是说类B若派生自类A的话,那么在任何时候,都可以称“B是一种A”。

如果B不是在任何时候都可以当作A,那么不可以将B从A中派生。

这句好像是错的(明明可以访问):即使派生类对象无法访问基类子对象中的private数据,它们依然存在并占用内存空间的,无法访问它只是因为编译器为它做了额外的保护。

举例:

type

TForm1 = class(TForm)

Button1: TButton;

Memo1: TMemo;

Label1: TLabel;

procedure Button1Click(Sender: TObject);

end;

TMyClass0 = class

private

str:String;

end;

TMyClass = class(TMyClass0)

Public

FMember1 : Integer;

FMember2 : Integer;

FMember3 : WORD;

FMember4 : Integer;

Procedure Method();

End;

var

Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

var

Obj : TMyClass;

begin

Obj := TMyClass.Create();

Obj.str:='dddddddd';

with memo1.Lines do

begin

Add('对象大小:' + IntToStr(Obj.InstanceSize));

Add('对象所在地址 :' + IntToStr(Integer(Obj)));

Add('str 所在地址:' + IntToStr(Integer(@Obj.str)));

Add('FMember1所在地址:' + IntToStr(Integer(@Obj.FMember1)));

Add('FMember2所在地址:' + IntToStr(Integer(@Obj.FMember2)));

Add('FMember3所在地址:' + IntToStr(Integer(@Obj.FMember3)));

Add('FMember4所在地址:' + IntToStr(Integer(@Obj.FMember4)));

end;

ShowMessage(obj.str);

Obj.Free();

end;

Object Pascal只支持单继承,保证每个派生类都只有唯一一份基类子对象。

多态置换原则:A如果不能无条件出现在B的位置上取代B,那么不要把A设计成B的派生类。

举例:请给我一个水果(可以替换成给他一个苹果,没有问题)

悖论(容器问题):当A是B的一种时,那么A的容器绝对不是一种B的容器!

举例:水果袋可以放任何水果(不能替换成给他一个苹果袋,这个结论是错误的)

数学上,圆是椭圆的一种;OOP却说,圆不是一种椭圆,原因在于椭圆拥有的能力已经无法完全包含在圆所拥有的能力中(x和y必不相等),这时就无法满足“多态置换原则”。失败原因在于,让基类拥有了太多的额外能力,哪怕仅仅是一个函数方法(个人解决方案:可以把椭圆的SetSize设置为私有方法)。“是一种”关系,是“普遍”和“特殊”的关系,记住总是要弱化基类,强化派生类。

==============================多态======================================

OOP的三大特征:封装可以隐藏细节(封装到类和函数里),继承可以扩展已有的代码模块,它们的目的都是代码重用。而多态则是为了另一个目的:接口重用。

一个抽象的指令,可以让每个个体完成具有同一性质但不同内容的动作,多神奇啊!

多态性允许用户将派生类的指针赋给基类类型的指针。多态性是通过虚方法(Virtual Method)实现的。

虚方法就是允许派生类重新定义的方法。派生类重新定义基类虚方法的做法,称为“覆盖”(override)。

多态的实质:

1. 在运行期间动态地调用属于派生类的虚方法

2. 允许父对象设置成为与一个或更多的它的子对象相等的技术;赋值之后,父对象就可以根据当前子对象的特征以不同方式运作。

procedure fly(); virtual; abstract; // 纯虚方法

凡是含有abstract方法的类被称为“抽象类”,永远无法创建抽象类的实例对象。抽象类是被用来作为接口的。

VCL类库中,TObject有一个虚拟的Destroy析构函数和一个非虚拟的Free方法。

对于析构函数必须加上override关键字(跟其它虚函数的使用方法一样):destructor Destroy(); override;

否则的话(不加override关键字),父类指针调用的是父类自己的析构函数,这就出问题了:父类的析构函数压根不认识子类的具体元素,怎么可能正确销毁呢。

重载(overload)是指定义多个同名函数,但参数不同:

function func(p:integer): integer; overload

function func(p: string): integer; overload

注意:它们的调用入口地址在编译期间已经静态确定了!重载只是一种语言特征,与面向对象无关。