==============================封装====================================== 封装目的:把可变部分与稳定部分分离开来。将稳定部分暴露给其它块,而将可变部分隐藏起来,以便随时让它改变。
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 注意:它们的调用入口地址在编译期间已经静态确定了!重载只是一种语言特征,与面向对象无关。
|
请发表评论