面向对象设计,顾名思义,是以对象为核心。分析出现实世界中对象,这些对象含有状态和行为,其中,状态对应着属性,行为对应着方法。除了静态分析除了对象之外,还要研究这些对象之间的动态关系。
在程序设计中,为了实现上诉的分析,面向对象语言常通过封装,继承,多态等特性来实现面向对象设计的。其中,封装理解起来很简单,有两层意思,一个是把现实世界对象的状态和特性封装起来;另外,对象只允许外部类调用一部分的属性和方法,而保留一些私有的特性供内部使用,向调用客户代码屏蔽对象模型中的一些私有属性;继承,这是面向对象语言最常提到的概念,目的是实现代码的复用;多态,这个特性是用同样的一个接口,来调用不同的对象的方法。多态主要是抽象的表现,向调用客户代码屏蔽复杂的对象模型,使程序更具有扩展性。
那么,我们在学习一种新的面向对象语言的时候,通常,首先包括语言的基本变量,逻辑控制分支,函数定义,其次主要的就为揭示语言是如何支持OOP特性的语法,也就是如何支持封装,继承,多态的语法。
1.封装:访问权限问题
C++体系中,字段和方法都用private,protected, public三个不同访问权限级别的关键字来修饰,缺省是private类型;其中,属性和字段没有用关键字加以区分,只能,程序员自己定义方法,来对字段进行灵活权限的访问。
C#中,字段,属性和方法,同样可以通过private,protected,public修饰来控制封装权限,private只能在本类中调用,protected可以在子类中调用,public在外部和内部都可以进行访问。除此之外,还增加了internal的关键字来控制成员在模块间的访问级别,但要注意internal只能和其他三个访问关键词联合使用。
字段还可以由readonly关键字来修饰,表示字段只能由构造函数或者初始化赋值语句进行赋值。
属性可以用set,get来控制外部的访问,他们也同样可以用private,protected,public来修饰,但受控于属性的整体访问权限。
ObjC中,同样有private protected(可以由其子类进行访问) public的区别,对于字段,缺省属性和C++不同,是protected属性。同样,字段可以申明为@private,@protected,@public的形式,外部访问时@public字段时,可以用(对象->字段)的形式来进行访问。
通常我们都会保留ObjC中字段的缺省访问权限,用属性来对字段进行包装来控制访问权限,objC中有定义属性的关字@property,@synthesize,属性可以用[readwrite(缺省)/ readonly]; [assign/ retain(缺省)/ copy]; [atomicity/ nonatomic]来进行修饰。在ObjC中,属性的实现方式是由编译器自动添加[set<字段>]和[<字段>]的方法来对字段进行访问的,可以用<.>来对属性进行访问,这里一定要注意,属性和字段的区别,属性和对象的retainCount有很大的关系,也就是和内存管理有关系,而字段完全没有此特点。
对象方法的访问权限都是public的,如果需要私有方法,就不要在.h文件中申明,只许在 .m文件中直接进行定义就可以。但ObjC的方法调用,都是通过发送消息来实现的,如果外部客户知道私有方法的函数签名,那么,就可以对此方法进行调用,这一点没有C++和C#语法严谨。再多说一点,申明私有方法的做法在ObjC中有一个很响亮的名字——类别(categary)。是一种可以扩展原有类功能的语法。用此语法可以在编写一个类时,分组(人员,文件)实现,也可以实现非正式委托 (informal protocol)。在C#中,也有同样的用法,C++中没有此特点。
2.继承:复用问题;多态:动态绑定
继承是面向对象编程语言中最重要的应用。这篇文章中,我只谈一下,其中的接口和抽象在三中语言中的用法。关于子类如何继承父类中的字段和方法,读者可以搜索其他文件进行了解。
在C++中,接口和抽象类在形式上没有明显的区别。可以直观的认为包含有纯虚函数(virtual <函数签名>=0;)的类都是接口类或者抽象类,实际编程中,在意义上是有区别的。纯虚函数在子类中,必须提供实现,才能实例化,纯虚函数可以提供缺省的实现,见下文例子。纯虚函数可以是private, protected, public三种访问类型,子类中,可以修改这些访问类型。
在C++中,可以多重继承,这是其他两种语言中所没有的特性,同时,也引入了钻石结构的缺点,引入了virtual虚继承。这也是C++没有很好的区分接口类和抽象类的后果。
- class IDicomFunction//当作接口
- {
- public:
- virtual void Exp2File() = 0;
- virtual void Exp2Disk() = 0;
- virtual void Exp2Film() = 0;
- };
- void IDicomFunction::Exp2Disk()//表示接口是必须被重写的(有协议意思),而且还提供了缺省实现(有复用的意思):接口和缺省实现实现分离
- {
- cout<<"IDicomFunction Exp2File is called"<<endl;
- }
- class CPerson
- {
- protected://可以修改访问说明符
- virtual void Say()
- {
- cout<<"CPerson Say is called"<<endl;
- }
- };
- class CPatient: public CPerson, public IDicomFunction
- {
- public :
- void Say()
- {
- cout<<"patient say CPatient called"<<endl;
- CPerson::Say();
- }
- void Exp2File()//无论是否写virtual关键字,此函数是虚函数的属性都不会更改
- {
- IDicomFunction::Exp2Disk();//
- cout<<"exp 2 file CPatient is called"<<endl;
- }
- virtual void Exp2Disk()
- {
- cout<<"exp 2 disk CPatient is called"<<endl;
- }
- virtual void Exp2Film()
- {
- cout<<"exp 2 film CPatient is called"<<endl;
- }
- };
- class CChinaPatient:public CPatient
- {
- private:
- void Exp2File()//改变CChinaPatient的访问权限
- {
- cout<<"chinaPatient is called"<<endl;
- CPatient::Exp2File();
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- CPatient* pPatient = new CChinaPatient;
- pPatient->Exp2File();//可以调用CChinaPatient中的私有函数
- system("pause");
- return 0;
- }
C#中,对接口和抽象在形式上区分了开来,接口类是通过关键字interface来修饰,抽象类是通过abstract关键字修饰(包含有抽象函数的类,即为抽象类)。C#中规定,子类只能继承于一个父类,也就是一个子类只能is a一个父类,但可以遵循多个接口(协议)。类中函数用virtual修饰就是虚函数,必须提供函数主体,供子类进行覆盖或继承。
用abstract来修饰即为抽象函数(不能提供函数体实现,假设可以提供函数体实现,也是没有意义的。因为,抽象类不能实例化,且抽象函数必须被覆盖(override),那么,当调用抽象函数时,就必定是子类的覆盖后的函数体实现)#1,子类必须进行(override)覆盖#2。
其中,在C#中,abstract或者virtual函数不能是private访问类型#3,而且在覆盖父类虚函数或者抽象函数时,不能修改覆盖函数的访问修饰符#4。
接口类的含义是一组协议,需要实体类进行遵守。其所有的函数都是共有类型,在函数申明前不能加任何访问修饰符#5。
在C#继承语法中,虚函数和函数其实是一类函数,是可以相互转化的。即,虚函数可以被子类隐藏为一个一般函数,使函数失去多态特性;同时,一般函数也可以被子类隐藏为虚函数,而具有了多态特性;同时,隐藏函数时,访问权限是可以被修改的#6。也就是说,要想实现多态,必须要添加override关键字,在override的修饰下,访问权限是绝不能被修改的。此特性和C++相悖,C++中,一朝被定义为virtual函数,一直都是虚函数,无论在子类中,是否加入virtual关键字,都是具有多态特性的。
- interface ExpDcm
- {
- //public void ExpDcmFuction ();//#5错误
- void ExpDcmFuction ();
- void ExpDcm2Pacs ();
- void ExpDcm2Film ();
- void ExpDcm2MediaDisk ();
- }
- public abstract class Person
- {
- private string name ;
- public string Name
- {
- get
- {
- return name ;
- }
- set
- {
- name = value ;
- }
- }
- protected abstract void Bing ();
- public abstract void Call();//#1不能提供函数体
- //private abstract void Bing ();//#3编译错误,抽象函数不能被私有访问符修饰
- }
- public class Patient : Person, ExpDcm
- {
- //protected override void Call ()//#4不能修改继承而来函数体的访问级别
- public override void Call()//#2实现抽象函数
- {
- Console.WriteLine (@"call is called");
- }
- public void ExpDcmFuction()//实现接口
- {
- Console.WriteLine(@"patient exp dcm function is call" );
- }
- public virtual void ExpDcm2Pacs() //实现接口,并且增加虚函数属性
- {
- Console.WriteLine(@"patient exports dcm file and send to pacs" );
- }
- public virtual void ExpDcm2Film() //实现接口,并且增加虚函数属性
- {
- Console.WriteLine(@"patient exports dcm file and send to film" );
- }
- void ExpDcm .ExpDcm2MediaDisk()//只能通过接口来访问,不能添加public 属性
- {
- Console.WriteLine(@"patient exports dcm file and send to disk" );
- }
- public void GetId()
- {
- Console.WriteLine (@"the patient GetId is call");
- }
- }
- public class ChinaPatient :Patient
- {
- new protected virtual void GetId ()//#6虚函数覆盖一般函数,使函数具有多态特性;并且修改访问类型
- {
- Console .WriteLine ( @"china patient GetId called" );
- }
- public override void Call()//实现抽象函数
- {
- Console.WriteLine (@"china patient is called");
- }
- new protected void ExpDcmFuction()//隐藏一般函数,可修改隐藏函数的访问类型
- {
- Console.WriteLine(@"ch patient exp dcm is call");
- }
- new public virtual void ExpDcm2Pacs() //隐藏父类虚函数
- {
- Console.WriteLine(@"china patient exports dcm file and send to pacs");
- }
- public override void ExpDcm2Film()//重写父类虚函数
- {
- Console.WriteLine(@"china exports dcm file and send to film");
- }
- }
还有partical的用法,也可以复用代码;
ObjC中,没有虚函数之说,因为在ObjC的世界里,所有的函数都是动态调用的,可参考我之前写的一篇关于ObjC函数调用的文章;而C++的函数有的动态调用,有的是静态调用的,动态调用是通过虚指针和虚表来实现的,算不上优美。
同C#一样,ObjC中,只能继承于一个类,但同时可以遵循实现多个protocol协议(接口),在ObjC中,成员函数都是public的,所以,也不同区分访问权限的问题,简单实用。相关的一些代码可以参考以下
- #import <Foundation/Foundation.h>
- /**实现关于Dicom的相关接口协议
- */
- @protocol DcmFunction
- @required
- -(void) ExpDcm2Pacs;
- -(void) ExpDcm2Film;
- @optional
- -(void) ExpDcm2Disk;
- @end
- /**person接口
- */
- @interface Person: NSObject
- {
- NSString* strName;
- int nAge;
- int nSex;//缺省是protected ; 子类中可以进行访问
- }
- @property(nonatomic, assign) int nAge;//定义属性的一种方式,和字段是相同的名字
- @end
- /**Patient
- */
- @interface Patient:Person<DcmFunction>
- {
- NSString* strAccessNumber;
- }
- @property(nonatomic, copy) NSString* access_number;//定义属性的方法,在synthesize中,把字段赋值给属性
- -(void) Say;
- @end
- /**.m文件
- */
- @implementation Person
- @synthesize nAge;
- -(void) Say
- {
- NSLog(@"Person's Say called");
- }
- @end
- @interface Patient(PrivateMethod)
- -(void) GetInstanceUid;
- @end
- @implementation Patient//给出警告:Method <> in protocol not implemented
- @synthesize access_number = strAccessNumber;//属性的定义方式
- -(void) GetInstanceUid//定义私有方法,利用的是类别(categary),在外部可以向此类发送此消息,那么,此类的实例将进行响应
- {
- NSLog(@"patient get instance uid");
- }
- -(void) Say
- {
- NSLog(@"Patient's Say called");
- }
- //实现接口
- -(void) ExpDcm2Pacs
- {
- NSLog(@"expose to pacs");
- }
- -(void) ExpDcm2Film
- {
- NSLog(@"expose to film");
- }
- @end
- /**main
- */
- Person* pPatient = [[Patient alloc]init];
- if([pPatient respondsToSelector:@selector(GetInstanceUid)] == YES)
- {
- [pPatient performSelector:@selector(GetInstanceUid)];
- }
- [pPatient release];