面向对象设计,顾名思义,是以对象为核心。分析出现实世界中对象,这些对象含有状态和行为,其中,状态对应着属性,行为对应着方法。除了静态分析除了对象之外,还要研究这些对象之间的动态关系。
在程序设计中,为了实现上诉的分析,面向对象语言常通过封装,继承,多态等特性来实现面向对象设计的。其中,封装理解起来很简单,有两层意思,一个是把现实世界对象的状态和特性封装起来;另外,对象只允许外部类调用一部分的属性和方法,而保留一些私有的特性供内部使用,向调用客户代码屏蔽对象模型中的一些私有属性;继承,这是面向对象语言最常提到的概念,目的是实现代码的复用;多态,这个特性是用同样的一个接口,来调用不同的对象的方法。多态主要是抽象的表现,向调用客户代码屏蔽复杂的对象模型,使程序更具有扩展性。
那么,我们在学习一种新的面向对象语言的时候,通常,首先包括语言的基本变量,逻辑控制分支,函数定义,其次主要的就为揭示语言是如何支持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的修饰下,访问权限是绝不能被修改的;如果没有override进行修饰,那么,可以认定子类是在隐藏父类的函数,此时函数没有多态特性,这里要注意一下,如果没有override的修饰,最好要用new来显式来进行隐藏,否则,编译器会给出警告。此特性和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];