第一篇:COM接口与对象学习心得150901
COM对象与接口学习心得 引言
COM是Microsoft提出的一种二进制兼容构件的规范,只要遵守这种规范,不管用什么编程语言和工具开发的COM构件,也不管是否运行在同一台机器上,还是运行在不同的机器上,都可以被使用。COM是建立在二进制层次上的调用标准,与编程语言和开发工具无关;COM定义了大量的标准规范接口(如IUnknown、IClassFactory、IDispatch等等)用于各种不同的用途。如今,COM成了微软跟上因特网快速发展的重要基础技术,在windows平台上,COM随处可见。
COM对象是指符合面向对象设计中对象的基本概念,通过COM接口提供服务的COM组件的实例。COM接口是客户与对象之间的通信协议,对象实现COM接口,客户调用COM接口。COM对象由GUID唯一标识,通常可以实现多个接口。COM接口由CLSID标识,它不能被独立使用,要求必须存在于某个COM对象上,COM接口提供的功能是与语言、平台和编译器无关的。静态链接与动态链接
C++程序的链接按照时间可以分为静态链接和动态链接。动态链接是在运行时与库函数进行链接,它使得不同的程序开发者能够相对独立地开发和测试自己的程序模块,从某种意义上来讲大大促进了程序的开发效率,原先限制程序的规模也随之扩大;静态链接是在编译的时候与库函数进行连接,会将静态库中的所有方法都编译到应用程序中。C++的动态链接是符合COM接口设计需要的。
静态链接的优点:代码装载速度快,执行速度略比动态链接库快; 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。缺点:使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。
动态链接优点:更加节省内存并减少页面交换;DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。缺点:使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。3 客户与对象之间的通信
C++的封装是语法上的封装,而不是二进制封装,其对象创建与释放的运算符new/delete是编译器相关的,编译器不仅要知道类的public信息,也要知道private信息。C++对象的二进制结构是编译器相关的,即使客户看到的C++类公开接口没有变化,但是C++类的实现改变了,仍然会打破客户与对象之间的连接。
在对COM接口进行设计时,应使客户与C++对象之间的连接点越小越好,尽可能地只有接口部分必要的信息才放入接口,把C++类的实现细节与接口分开,提取出针对所有编译器都不变的因素作为客户与对象共享的接口信息。
C++纯虚基类的设计可以很好地解决COM接口的设计问题。纯虚基类只包含了虚函数,限定每个虚函数的调用规则,在给定的平台上所有的编译器都会产生同样的二进制结构。对于跨平台的情形,需要通过中间层,这里暂不考虑。纯虚基类的使用解决了方法实现的命名冲突,也解决了C++类中二进制布局不兼容的问题,客户只能看到vtable,没有看到其他的实现细节,保证了不同语言编写的程序可以互操作,也可以在不改变接口的情况下,可以单独升级客户或者对象。
例如定义一个接口IString,它包含了两个纯虚方法。class IString {
virtual const char*Find(const char *psz)=0;
virtual int Length()=0;};
类CMyString实现了IString定义的接口(具体实现略)。class CMyString : public IString { private:
char *m_psz;public:
CMyString(const char * psz);
~CMyString();
const char*Find(const char *psz);
int Length();};
如果不使用C++的运算符,客户如何得到IString的虚表接口呢?可以提供一个引出函数供客户使用,隐藏创建对象的内部细节。
extern “C” _declspec(dllexport)IString *CreateString(const char *psz);extern “C” IString *CreateString(const char *psz){
return new CMyString(psz);}
客户访问vtable接口举例: #include “istring.h” typedef IString *(*PfnCreateString)(const char *psz);void main(){
IString *p;
HANDLE h = LoadLibrary(“c: empmystring.dll”);
if(NULL!=h){
PfnCreateString pfn =
(PfnCreateString)GetProcAddress(h,“CreateString”);
if(pfn){
p = pfn(“Hello”);
if(p){
const char*psz = p->Find(“llo”);
int n = p->Length();
}
}
} };
至此,客户避开了C++运算符的编译器相关性,访问了IString接口,这样就建立了对象与客户之间的通信方式。IUnknown接口
COM定义的每一个接口都由IUnknown接口继承而来,由于IUnknown接口提供了两个重要的特性:生存期控制和接口查询。客户程序只能通过COM接口与COM对象通信,虽然客户不用关心对象内部对方法的实现细节,但是它必须控制对象的存在与否。class IUnknown { public: virtual HRESULT__stdcall QueryInterface(const IID& iid, void **ppv)= 0;virtual ULONG __stdcall AddRef()= 0;
virtual ULONG __stdcall Release()= 0;};
每个COM对象要管理一个被称为引用计数(reference count)的整数值。每个引用从被有效赋值开始,一直到生命周期结束,这期间被称为:outstanding reference。为了有效地管理对象的生命周期,COM提供一些规则和操作,供客户遵守和使用:保持引用计数的确切含义,也就是记录当前outstanding reference的数目;引用计数从0开始,首次把接口递交给客户时为1,以后由客户管理,当引用计数回到0时,删除自己。
当客户通过复制获得新的接口指针时,引用计数加一,当某个接口不用时,减一。
3.1 COM接口的内存模型
COM接口的内存模型如下图所示,客户首先通过创建对象来获得接口指针,然后利用接口指针来访问vtable中的方法,vtable方法由COM对象内部实现。
3.2 查询接口
一个COM对象可以实现多个接口。从一个接口到另一个接口的访问途径可以通过函数QueryInterface(iid, ppv)来实现。初始得到了一个接口指针之后,调用它的QueryInterface函数,获得另一个接口指针。这里要注意:IUnknown必须是个静态接口指针,其他接口指针可以是动态的;QueryInterface在返回接口之前,必须调用新接口的AddRef函数,以示该接口的引用计数加1。
3.3 对象生存期与引用计数
引用计数是为了控制对象的生命周期,多个客户可以独立地控制对象的生存周期。引用计数反映了被客户引用的个数Outstanding references。引用计数在对象被创建时赋值为0,在接口被复制时加1,接口释放时减一,当引用计数为0时,表示没有客户在使用对象或者接口,则释删除对象或接口。
引用计数的实现由三个层次,组件级、对象级、接口级。组件级引用计数分辨率太粗糙,不容易控制,而接口级引用计数分辨率太细,效率较低。
引用计数规则:在顺序执行过程中,如果要对一个接口指针变量赋值,则对赋值后的接口指针变量调用AddRef,并且,如果赋值前的接口指针变量还没有结束,则赋值前必须对它调用Release以便先结束它的使用;如果要结束使用一个接口指针变量,以后不再用到它了,则调用Release函数。
引用计数注意事项:在整个生存期内,AddRef与Release一定要配对,否则漏掉AddRef,程序出错,漏掉Release,对象永不释放,造成内存泄露;对象的释放由Release内部实现,AddRef和Release的返回值并不可靠,不能准确的反映Outstanding references。
第二篇:COM多线程和DCOM学习心得160217
COM多线程和DCOM 1 COM多线程模型
COM提供的线程模型共有三种:Single-Threaded Apartment(STA 单线程套间)、Multithreaded Apartment(MTA 多线程套间)和Neutral Apartment/Thread Neutral Apartment/Neutral Threaded Apartment(NA/TNA/NTA 中立线程套间,由COM+提供)。虽然它们的名字都含有套间这个词,这只是COM运行时期库(注意,不是COM规范,以下简称COM)使用套间技术来实现前面的三种线程模型,应注意套间和线程模型不是同一个概念。COM提供的套间共有三种,分别一一对应。而线程模型的存在就是线程规则的不同导致的,而所谓的线程规则就只有两个:代码是线程安全的或不安全的,即代码访问公共数据时会或不会发生访问冲突。
1.1 COM套间
套间(Apartment)是线程模型的一个实现者,就像在操作系统课程中讲到的线程只是一个数学模型,而Windows的线程、进程是它(数学模型的线程、进程)的实现者。套间只是逻辑上的一个概念,实现时只是一个结构(由COM管理)而已,记录着相关信息,如它的种类(只能是上面那三个,至少现在是),并由COM根据那个结构进行相应的处理。下面说明这三种套间的实现方式:
STA套间:一个套间如果是STA,那么那个套间有且只有一个线程和其关联,有多个对象或没有对象和其关联,就像有多个线程和一个进程关联一样,也就是说套间那个结构和某个线程及多个对象之间有关系,关系具体是什么由COM说得算,幸运的是COM正是按照上面的线程模型来定义互相之间关系的。根据上面的算法,很容易就知道只有这个线程可以访问这个套间里的对象。COM是通过在STA套间里的线程中创建一个隐藏窗口,然后外界(这个套间外的线程)对这个对象的调用都转变成对那个隐藏窗口发送消息,然后由这个隐藏窗口的消息处理函数来实际调用组件对象的方法来实现STA的规则的。之所以使用一个隐藏窗口是为了方便组件代码的编写——只需调用DispatchMessage即可将方法调用的消息和普通的消息区分开来(通过隐藏窗口的消息处理函数)。外界对这个对象的调用都将转变成对这个隐藏窗口的消息发送来实现同步。至于COM如何截获外界对对象的调用,则是利于代理对象,后面再说明。
MTA套间:这种类型的套间可以和多个线程及多个或没有对象相关联。根据上面的MTA模型,可知只有这个套间里的线程才能访问这个套间里的对象,和STA不同的只是可以多个线程同时访问对象。外界(不属于这个套间的线程)对这个套间里的对象的调用将会导致调用线程(外界线程,也就是STA线程,因为NA没有线程)挂起,然后向RPC管理的一个线程池请求一个线程(RPC线程,并已经进入了这个MTA套间)以调用那个对象的方法。对象返回后,调用线程被唤醒,继续运行。虽然可以让STA线程直接调用对象(而不用像前述的挂起等待另一个线程来调用对象),但这是必须的,因为可能会有回调问题,比如这个MTA线程又反过来回调外界线程中的组件对象(假设客户本身也是一个组件对象,这正是连接点技术),如果异步回调将可能发生错误。反过来,MTA的线程访问STA里的对象时,COM将把调用转换成对STA线程里那个隐藏窗口的一个消息发送,返回后再由COM转成结果返回给MTA的线程(如果使用标准汇集法生成标准代理对象,则发生的具体情况就如上面STA套间所述)。因此STA和MTA都是只能由它们关联的线程调用它们关联的对象。而根据上面所说,当MTA调STA或STA调MTA,都会发生线程切换,也就是说一个线程挂起而换成执行另一个线程。这是相当大的消耗(需要从内核模式向用户模式转换,再倒转好几回),而NA就是针对这个设计的。
NA套间:这种套间只和对象相关联,没有关联的线程,因此任何线程都可以直接访问里面的对象,不存在STA的还是MTA的。外界(其实就是任何线程)对这个套间里面的调用都不需要挂起等待,而是进入NA套间,直接调用对象的方法。NA套间是由COM+提供的,COM+中的每个对象都有一个环境和其相绑定,环境记录了必要的信息,并监听对对象的每一次调用,以保证当将对象的接口指针成员变量进行传递或回调时其操作的正确性(保证执行线程在正确的套间内,MTA线程就是通过将自己挂起以等待STA线程的消息处理完毕来保证的),从而避免了调用线程的挂起,因此这个代理(其实也就是环境的一部分)被称作轻量级代理(相对于STA套间和MTA套间的重量级代理——需要挂起调用线程,发生线程切换)。
1.2 套间实现规则
COM的套间机制要成功实现,必须服务器(组件)、客户和COM运行时期库三方面合力实现,其中有任何一方不按着规矩来,将不能实现套间机制的功能,不过这并不代表什么错误,套间机制不能运作并不代表程序会崩溃,只是不能和其他COM应用兼容而已。比如:对象中的属性1在设计的算法中肯定不会被两个以上的线程写入,只是会被多个线程同时读出而已,因此不用同步,可以用MTA,但对象的属性2却可能被多个线程写入,因此决定使用STA。从而在客户端,通过前面说的CoMarshalInterface和CoUnmarshalInterface将对象指针传到那个只会写入对象的属性1的线程,其实这时就可以直接将对象指针传到这个线程,而不用想上面那样麻烦(而且增加了效率),但是就破坏了COM的套间规矩了——两个线程可以访问对象,但对象在STA套间中。所以?!什么事都不会发生,因为已经准确知道这个算法不会捅娄子(线程访问冲突),即使破坏COM的规矩又怎样?!而且组件仍可以和其他客户兼容,因为不按规矩来的是客户,与组件无关。不过如果组件破坏规矩,那么它将不能和每一个客户兼容,但并不代表它和任何客户都不兼容。这里其实就是客户和组件联合起来欺骗了COM运行时期库。
STA 当一个组件是STA时,它必须同步保护全局变量和静态变量,即对全局变量和静态变量的访问应该用临界段或其他同步手段保护,因为操作全局和静态变量的代码可以被多个STA线程同时执行,所以那些代码的地方要进行保护。比如对象计数(注意,不是引用计数),代表当前组件生成的对象个数,当减为零时,组件被卸载。此变量一般被类厂对象使用,还好ATL和MFC已经帮我们实现了缺省类厂,这里一般不用担心,但自定义的全局或静态变量得自己处理。
主STA 与STA唯一的不同是这是傻瓜型的,连静态和全局变量都可以不用线程保护,因为所有不是安全访问静态和全局变量的对象都通过主线程(第一个调用CoInitialize的线程)的消息派送机制运行,因此不安全的访问都被集中到了一个线程的调用中,因而调用被序列化了,也就实现了对静态和全局变量的线程保护。至于为什么是主线程,因为进程要使用STA,则一定会创建主线程,所以一定可以创建主STA。因此主STA并不是什么第四种套间,只是一个STA套间,不过关联的是主线程而已,由于它可以被用作保护静态和全局变量而被单独提出来说明。因此一个进程内也只有一个主STA套间。2 分布式DCOM DCOM(分布式组件对象模型,分布式组件对象模式)是一系列微软的概念和程序接口,利用这个接口,客户端程序对象能够请求来自网络中另一台计算机上的服务器程序对象。DCOM基于组件对象模型(COM),COM提供了一套允许同一台计算机上的客户端和服务器之间进行通信的接口。
2.1 DCOM对象的创建
DCOM使得组件的位置对你来说完全透明,无论它是位于客户的同一进程中或是在地球的另一端。在任何情况下,客户连接组件和调用组件的方法的方式都是一样的。DCOM不仅无需改变源码,而且无需重新编译程序。一个简单的再配置动作就改变了组件组件之间相互连接的方式。DCOM的位置独立性极大地简化了将应用组件分布化的任务,使其能够达到最合适的执行效果。例如,设想某个组件必需位于某台特定的机器上或某个特定的位置,并且此应用有许多小组件,你可以通过将这些组件配置在同一个LAN上,或者同一台机器上,甚至同一个进程中来减少网络的负载。
创建COM对象可以在C OM库的基础上,通过远程调用SCM来实现。
客户调用创建函数COM库(OLE32.DLL)远程创建SCM(RPCSS.EXE)RPCSCM(RPCSS.EXE)组件创建进程和对象
图创建DCOM组件对象
2.2 DCOM的连接和并发管理
DCOM使用ORPC协议实现远程通信。连接具有可传递性,因为接口的列集数据(OR或者OBJREF)包含机器相关的信息;连接传递与创建传递含义不同,DCOM不支持创建传递, 可用连接传递间接支持创建传递,利用连接传递性可实现动态负载平衡。
服务器1①请求创建对象A客户机②pA->CreateObjectB④返回对象B⑤释放对象A⑥客户直接与对象B连接服务器3名字开放服务器③选择服务器3并创建对象B服务器2 图 DCOM连接管理
客户程序或者组件程序实现过滤器对象,然后用CoRegisterMessageFilter函数指定使用自定义的消息过滤器;否则COM使用缺省的过滤器对象。
2.3 DCOM安全性
DCOM使用了Windows NT提供的扩展的安全框架。Windows NT提供了一套稳固的内建式安全模块,它用来提供从传统的信用领域的安全模式到非集中管理模式的复杂的身份确认和鉴定机制,极大地扩展了公钥式安全机制。安全性框架的中心部分是一个用户目录,它存储着用来确认用户凭据(用户名、密码、公钥)的必要信息。大多数并非基于Windows NT平台的系统提供了相似或相同的扩展机制,我们可以使用这种机制而不用管此平台上用的是哪种安全模块。大多数DCOM的UNIX版本提供了同Windows NT平台相容的安全模块。
第三篇:微机原理与接口技术学习心得
本学期微机原理课程已经结束,关于微机课程的心得体会甚多。微机原理与接口技术作为一门专业课,虽然要求没有专业课那么高,但是却对自己今后的工作总会有一定的帮助。记得老师第一节课说学微机原理是为以后的单片机打基础,这就让我下定决心学好微机原理这门课程。
初学《微机原理与接口技术》时,感觉摸不着头绪。面对着众多的术语、概念及原理性的问题不知道该如何下手。在了解课程的特点后,我发现,应该以微机的整机概念为突破口,在如何建立整体概念上下功夫。可以通过学习一个模型机的组成和指令执行的过程,了解和熟悉计算机的结构、特点和工作过程。《微机原理与接口技术》课程有许多新名词、新专业术语。透彻理解这些名词、术语的意思,为今后深入学习打下基础。一个新的名词从首次接触到理解和应用,需要一个反复的过程。而在众多概念中,真正关键的并不是很多。比如“中断”概念,既是重点又是难点,如果不懂中断技术,就不能算是搞懂了微机原理。在学习中凡是遇到这种情况,绝对不轻易放过,要力求真正弄懂,搞懂一个重点,将使一大串概念迎刃而解。
学习过程中,我发现许多概念很相近,为了更好地掌握,将一些容易混淆的概念集中在一起进行分析,比较它们之间的异同点。比如:微机原理中,引入了计算机由五大部分组成这一概念;从中央处理器引出微处理器的定义;在引出微型计算机定义时,强调输入/输出接口的重要性;在引出微型计算机系统的定义时,强调计算机软件与计算机硬件的相辅相成的关系。微处理器是微型计算机的重要组成部分,它与微型计算机、微型计算机系统是完全不同的概念
在微机中,最基础的语言是汇编语言。汇编语言是一个最基础最古老的计算机语言。语言总是越基础越重要,在重大的编程项目中应用最广泛。就我的个人理解,汇编是对寄存的地址以及数据单元进行最直接的修改。而在某些时候,这种方法是最有效,最可靠的。
然而,事物总有两面性。其中,最重要的一点就是,汇编语言很复杂,对某个数据进行修改时,本来很简单的一个操作会用比较烦琐的语言来解决,而这些语言本身在执行和操作的过程中,占有大量的时间和成本。在一些讲求效率的场合,并不可取。
汇编语言对学习其他计算机起到一个比较、对照、参考的促进作用。学习事物总是从最简单基础的开始。那么学习高级语言也当然应当从汇编开始。学习汇编语言实际上是培养了学习计算机语言的能力和素养。个人认为,学习汇编语言对学习其他语言很有促进作用。
汇编语言在本学期微机学习中有核心地位。本学期微机原理课程内容繁多,还学习了可编程的计数/定时的8253,可编程的外围接口芯片8255A,可编程中断控制器8259A等。学的这些都是芯片逻辑器件,“可编程”说明其核心作用不可低估。
还有就是,在学习中要考虑到“学以致用”,不能过分强调课程的系统性和基本理论的完整性,而应该侧重于基本方法和应用实例。从微机应用系统的应用环境和特点来看,微机系统如何与千变万化的外部设备、外部世界相连,如何与它们交换信息,是微机系统应用中的关键所在,培养一定的微机应用系统的分析能力和初步设计能力才是最终目的!
在此门课程的学习过程中,××老师给我们细心讲解了一个个重要的知识点,并为我们一一解答了我们学习过程中遇到的问题及疑惑。因此在本学期结束之际,再三感谢××老师给予我及同学们在学习上的帮助和支持!
第四篇:学习心得《面向对象》
面向对象课程学习心得
这学期的面向对象课程对我来说是收获匪浅的一门课。通过老师课件的讲解,自己一些相关书籍的阅读和实践作业的完成,逐步对课程有了由浅及深的认识。
面向对象(Object Oriented,OO)是一门以实践为主课程,课程中可以分开两块OOA(面向对象系统分析)和OOD(面向对象系统设计)。OOA(面向对象系统分析)主要内容: 研究问题域和用户需求,运用面向对象的观点和原则发现问题域中与系统责任有关的对象,以及对象的特征和相互关系.OOA不涉及针对具体实现采取的设计决策和有关细节,独立于具体实现的系统模型。是一个完整确切反映问题域和用户需求的系统模型。OOA的优势:复用、可扩展、可维护性、弹性。
OOD(面向对象系统设计):以OOA模型为基础,按照实现的要求进行设计决策,包括全局性的决策和局部细节的设计,与具体的实现条件相关。OOD的步骤:细化重组类→细化和实现类之间的关系,明确其可见性→增加属性,指定属性的类型和可见性→分配职责,定义执行每个职责的方法→对消息驱动的系统,明确消息传递的方式→利用设计模式进行局部设计→画出详细的类图和时序图。
面向对象的分析与设计方法将致力于解决传统软件研发过程中由于软件模块化结构化程度不高带来的软件重用性差、软件可维护性差、开发出的软件不能满足用户需要等方面问题。面向对象的概念包括:对象、对象的状态和行为、类、类的结构、消息和方法。对象概念将包含对象唯一性、抽象性、继承性、多态性的重要特征。面向对象的要素包含:抽象、封装性、共享性三方面。
在设计模式的研究过程中,我们组选择的是迭代器(Iterator)的设计模式研究。完成设计研究后,我对迭代器的设计模式有了更为深刻的理解。迭代器(Iterator)提供一个方法顺序访问一个聚合对象的各个元素,而又不暴露该对象的内部表示。并了解到迭代器设计模式一般在以下三类场合使用较多。
访问一个聚合对象的内容而无需暴露它的内部表示。 支持对聚合对象的多种遍历。因为遍历状态是保存在每一个迭代器对象中的。
为遍历不同的聚合结构提供一个统一的接口。根据实现方式的不同,效果上会有差别。同时还简化了容器的接口。但是在java Collection中为了提高可扩展性,容器还是提供了遍历的接口。在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据。面向对象设计原则中有一条是类的单一职责原则,所以我们要尽可能的去分解这些职责,用不同的类去承担不同的职责。Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。
在Java Collection的应用中,提供的具体迭代器角色是定义在容器角色中的内部类。这样便保护了容器的封装。但是同时容器也提供了遍历算法接口,你可以扩展自己的迭代器。至于迭代器模式的使用。客户程序要先得到具体容器角色,然后再通过具体容器角色得到具体迭代器角色。这样便可以使用具体迭代器角色来遍历容器了。
OOA和OOD之间没有明显的界限。OOA与OOD的不可分割性正好说明了OO思想的强大,即软件过程阶段的无缝连接,在交流与沟通中不会产生鸿沟,这是相对结构化思想的好处,因为从功能模块到某块详细控制逻辑设计两者之间的联系不是十分紧密,需要分析人员与设计人员的再沟通。
通过课程的学习与实践,对面向对象的理念,以及相关方法,设计模式有了更为深刻的理解与掌握。针对面向对象的分析与设计课程的授课内容及方法,我个人觉得对我还是有不少的帮助和 提高。结合自己的工作,虽然与开发接触的比较少,但是在运维过程中,如果能了解开发原理,结合实际的工作,会对一些源代码的分析能力以及工作效率的提高起到明显的帮助作用。
第五篇:C#面向对象学习心得
一、封装
这是一种隐藏信息的特性。拿本节引例来说,类CalculateDate 将数据结构与算法隐藏在类的内部,外界使用者无需知道具体技术实现细节即可使用此类。封装这一特性不仅大大提高了代码的易用性,而且还使得类的开发者可以方便地更换新的算法,这种变化不会影响使用类的外部代码。可以用以下公式展示类的封装特性:封装的类=数据+对此数据所进行的操作(即算法)。通俗地说,封装就是:包起外界不必需要知道的东西,只向外界展露可供展示的东西。在面向对象理论中,封装这个概念拥有更为宽广的含义。小到一个简单的数据结构,大到一个完整的软件子系统,静态的如某软件系统要收集数据信息项,动态的如某个工作处理流程,都可以封装到一个类中。具备这种“封装”的意识,是掌握面向对象分析与设计技巧的关键。
二、继承
继承是面向对象编程中一个非常重要的特性,它也是另一个重要特性——多态的基础。现实生活中的事物都归属于一定的类别。在一些书中,将父类称为超类(super class)。“继承”关系有时又称为“派生”关系,“B 继承自A”,可以说为“B 派生自A”,或反过来说,“A 派生出B”。父类与子类之间拥有以下两个基本特性:
(1)是一种(IS-A)关系:子类是父类的一种特例。
(2)扩充(Extends)关系:子类拥有父类所没有的功能。
1.类成员的访问权限
面向对象编程的一大特点就是可以控制类成员的可访问性。当前主流的面向对象语言都拥有以下三种基本的可访问性:
(1)公有 public 访问不受限制。
(2)私有 private 只有类自身成员可以访问。
(3)保护 protected 子类可以访问,其他类无法访问。
由此可见,可以通过子类对象访问其父类的所有公有成员,事实上,外界根本分不清楚对象的哪些公有成员来自父类,哪些公有成员来自子类自身。小结一下继承条件下的类成员访问权限:
(1)所有不必让外人知道的东西都是私有的。
(2)所有需要向外提供的服务都是公有的。
(3)所有的“祖传绝招”,“秘不外传”的都是保护的。
C#中还有一种可访问性,就是由关键字internal 所确定的“内部”访问性。internal 有点像public,外界类也可以直接访问声明为internal 的类或类的成员,但这只局限于同一个程序集内部。
读者可以简单地将程序集理解为一个独立的DLL 或EXE 文件。一个DLL 或EXE 文件中可以有多个类,如果某个类可被同一程序集中的类访问,但其他程序集中的类不能访问它,则称此类具有internal 访问性。internal 是C#的默认可访问性,这就是说,如果某个类没有任何可访问性关键字在它前面,则它就是internal 的。
2.子类父类变量的相互赋值
子类对象可以被当成基类对象使用。这是因为子类对象本就是一种(IS_A)父类对象,因此,以下代码是合法的:
Parent p;
Son c = new Son();
p = c;
然而,反过来就不可以,父类对象变量不可以直接赋值给子类变量。如果确信父类变量中所引用的对象的确是子类类型,则可以通过类型强制转换进行赋值,其语法格式为: 子类对象变量=(子类名称)基类对象变量;
子类对象变量=基类对象变量 as 子类名称;
3.方法重载、隐藏与虚方法调用
由于子类对象同时汇集了父类和子类的所有公共方法,而C#并未对子类和父类的方法名称进行过多限制,因此,一个问题出现了:如果子类中某个方法与父类方法的签名一样(即方法名和方法参数都一样),那当通过子类对象访问此方法时,访问的是子类还是父类所定义的方法?让我们先从子类方法与父类方法之间的关系说起。总的来说,子类方法与父类方法之间的关系可以概括为以下三种:
(1)扩充(Extend):子类方法,父类没有;
(2)重载(Overload):子类有父类的同名函数,但参数类型或数目不一样;
(3)完全相同:子类方法与父类方法从方法名称到参数类型完全一样。
当子类与父类拥有完全一样的方法时,称“子类隐藏了父类的同名方法,当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。“new”关键字明确告诉C#编译器,子类隐藏父类的同名方法,提供自己的新版本。如果子类隐藏了父类的同名方法,要在子类方法的实现代码中调用父类被隐藏的同名方法时要使用base 关键字。如果子类隐藏了父类的同名方法,不进行强制转换,就无法通过父类变量直接调用子类的同名方法,哪怕父类变量引用的是子类对象。这是不太合理的。我们希望每个对象都只干自己职责之内的事,即如果父类变量引用的是子类对象,则调用的就是子类定义的方法,而如果父类变量引用的就是父类对象,则调用的是父类定义的方法。这就是说,希望每个对象都“各人自扫门前雪,莫管他人瓦上霜”。为达到这个目的,可以在父类同名方法前加关键字virtual,表明这是一个虚方法,子类可以重写此方法:即在子类同名方法前加关键字override,表明对父类同名方法进行了重写。所以,将父类方法定义为虚方法,子类重写同名方法之后,通过父类变量调用此方法,到底是调用父类还是子类的,由父类变量引用的真实对象类型决定,而与父类变量无关!很明显,“虚方法调用”特性可以让我们写出非常灵活的代码,大大减少由于系统功能
扩充和改变所带来的大量代码修改工作量。由此给出结论:面向对象语言拥有的“虚方法调用”特性,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作。
三、抽象
1.抽象类与抽象方法
在一个类前面加上“abstract”关键字,此类就成为了抽象类。对应地,一个方法类前面加上“abstract”关键字,此方法就成为了抽象方法。注意抽象方法不能有实现代码,在函数名后直接跟一个分号。抽象类专用于派生出子类,子类必须实现抽象类所声明的抽象方法,否则,子类仍是抽象类。抽象类一般用于表达一种比较抽象的事物,而抽象方法则说明此抽象类应该具有的某种性质,从同一抽象类中继承的子类拥有相同的方法(即抽象类所定义的抽象方法),但这些方法的具体代码每个类都可以不一样。抽象类不能创建对象,一般用
它来引用子类对象。一个抽象类中可以包含非抽象的方法和字段。因此:包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法。除了方法可以是抽象的之外,属性也可以是抽象的。
2.接口
接口可以看成是一种“纯”的抽象类,它的所有方法都是抽象方法。抽象类定义了对象所属的类别,而接口实际上定义了一种对象应具有的行为特性。某个类可以实现多个接口,当创建一个此类的对象之后,通过引用这个对象的对象变量可以访问其所有的公有方法(包括自身的公有方法以及由接口定义的公有方法以)。在这种情况下,根本分不清哪些方法是由接口定义的,哪些是由类自己定义的。C#提供了一种“显式接口”实现机制,可以区分开这两种情况。由此得到一个结论:如果一个类显式实现某个接口,则只能以此接口类型的变量为媒介调用此接口所定义的方法,而不允许通过类的对象变量直接调用。或者这样说:被显式实现的接口方法只能通过接口实例访问,而不能通过类实例直接访问。
四、多态
方法重载属于多态的一种,两个构成重载关系的函数必须满足几个条件:函数名相同、参数类型不同,或参数个数不同。具体调用哪个方法要看参数,需要注意的是,方法返回值类型的不同不是方法重载的判断条件。多态编程的基本原理是:使用基类或接口变量编程。在多态编程中,基类一般都是抽象基类,其中拥有一个或多个抽象方法,各个子类可以根据需要重写这些方法。或者使用接口,每个接口都规定了一个或多个抽象方法,实现接口的类根据需要实现这些方法。因此,多态的实现分为两大基本类别:继承多态和接口多态。
1.接口多态与继承多态
接口多态与继承多态其编程方式与作用都是类似的。但由于一个类可以实现多个接口,所以,接口多态较继承多态更灵活,因而在编程中也用得更广。多态是面向对象技术中最精华的部分之一。大量的精巧软件设计方案都建立在对多态特性的巧妙应用上。在编程中应用多态,可以将其简化为两句:应用继承实现对象的统一管理;应用接口定义对象的行为特性。对比传统的不使用多态的编程方式,使用多态的好处是:当要修改程序并扩充系统时,需要修改的地方较少,对其他部分代码的影响较小。
五、类与对象
类是面向对象编程的基本单元,与使用C语言等结构化编程语言不一样,使用C#编程,所有的程序代码几乎都放在类中,不存在独立于类之外的函数。一个类可以包含两种成员:静态成员和实例成员,静态成员是供类的所有对象所共享的,而实例成员只供某一个对象所有。实例成员与静态成员的访问规则:位于同一类中的实例方法可直接相互调用;类的字段(包括实例字段和静态字段)可以被同一类中的所有实例方法直接访问;类中的静态方法只能直接访问类静态字段。
类中包括:方法和字段,属性是一种特殊的字段,它可以保证数据的合法性,方法和字段这两个概念是面向对象理论的术语,是通用于各种面向对象语言的。字段(Field)代表了类中的数据,在类的所有方法之外定义一个变量即定义了一个字段。在变量之前可以加上public、private 和protected 表示字段的访问权限。方法(function)功能代码的集合,在程序开发过程中,经常发现多处需要实现或调用某一个公用功能,这些功能的实现都需要书
写若干行代码。如果在调用此功能的地方重复书写这些功能代码,将会使整个程序中代码大量重复,会增大开发工作量,增加代码维护的难度。为了解决代码重复的问题,绝大多数程序设计语言都将完成某一公用功能的多个语句组合在一起,起一个名字用于代表这些语句的全体,这样的代码块被称为“函数(function)”。引入“函数”概念之后,程序中凡需要调用此公用功能的地方都可以只写出函数名,此名字就代表了函数中所包含的所有代码,这样一来,就不再需要在多个地方重复书写这些功能代码。
对象是以类为模板创建出来的。类与对象之间是一对多的关系。在C#中,使用new 关键字创建对象。在程序中“活跃”的是对象而不是类。在面向对象领域,对象有时又被称为是“类的实例”,“对象”与“类的实例”这两个概念是等同的。
六、值类型与引用类型
1.值类型
值类型变量与引用类型变量的内存分配模型也不一样。每个正在运行的程序都对应着一个进程(process),在一个进程内部,可以有一个或多个线程(thread),每个线程都拥有一块“自留地”,称为“线程堆栈”,大小为1M,用于保存自身的一些数据,比如函数中定义的局部变量、函数调用时传送的参数值等,这部分内存区域的分配与回收不需要程序员干涉。所有值类型的变量都是在线程堆栈中分配的。值类型共有三种:简单类型、枚举类型和结构类型。
2.引用类型
另一块内存区域称为“堆(heap)”,在.NET 这种托管环境下,堆由CLR 进行管理,所以又称为“托管堆(managed heap)”。用new 关键字创建的类的对象时,分配给对象的内存单元就位于托管堆中。在程序中我们可以随意地使用new 关键字创建多个对象,因此,托管堆中的内存资源是可以动态申请并使用的,当然用完了必须归还。打个比方更易理解:托管堆相当于一个旅馆,其中的房间相当于托管堆中所拥有的内存单元。当程序员用new 方法创建对象时,相当于游客向旅馆预订房间,旅馆管理员会先看一下有没有合适的空房间,有的话,就可以将此房间提供给游客住宿。当游客旅途结束,要办理退房手续,房间又可以为其他旅客提供服务了。引用类型共有四种:类类型、接口类型、数组类型和委托类型。所有引用类型变量所引用的对象,其内存都是在托管堆中分配的。严格地说,我们常说的“对象变量”其实是类类型的引用变量。但在实际中人们经常将引用类型的变量简称为“对象变量”,用它来指代所有四种类型的引用变量。
七、命名空间与类库
1.命名空间
在使用面向对象技术开发的现代软件系统中,经常拥有数百甚至上千个类,为了方便地管理这些类,面向对象技术引入了“命名空间(namespace)”的概念。命名空间可以看成是类的“容器”,它可以包含多个类。.NET Framework 使用命名空间来管理所有的类。如果把类比喻成书的话,则命名空间类似于放书的书架,书放在书架上,类放在命名空间里。当我们去图书馆查找一本书时,需要指定这本书的编号,编号往往规定了书放在哪个书库的哪个书架上,通过逐渐缩小的范围:图书馆->书库->书架,最终可以在某个书架中找到这本书。类似地,可以采用图书馆保存图书类似的方法来管理类,通过逐渐缩小的范围:最大的命名空间->子命名空间->孙命名空间„„,最终找到一个类。
2.类库
为了提高软件开发的效率,人们在整个软件开发过程中大量应用了软件工程的模块化原则,将可以在多个项目中使用的代码封装为可重用的软件模块,其于这些可复用的软件模块,再开发新项目就成为“重用已有模块,再开发部分新模块,最后将新旧模块组装起来”的过程。整个软件开发过程类似于现代工业的生产流水线,生产线上的每个环节都由特定的人员负责,整个生产线上的工作人员既分工明确又相互合作,大大地提高了生产效率。在组件化开发大行其道的今天,人们通常将可以重用的软件模块称为“软件组件”。在全面向对象的.NET 软件平台之上,软件组件的表现形式为程序集(Assembly),可以通过在Visual Studio 中创建并编译一个类库项目得到一个程序集。在Visual Studio 的项目模板中,可以很方便地创建类库(Class Library)项目,Visual Studio 会自动在项目中添加一个名为Class1.cs 的类文件,程序员可在此类文件中书写代码,或者添加新的类。一个类库项目中可以容纳的类数目没有限制,但只有声明为public 的类可以被外界使用。类库项目编译之后,会生成一个动态链接库(DLL:Dynamic Link Library)文件。这就是可以被重用的.NET 软件组件——程序集。默认情况下,类库文件名就是项目名加上“.dll”后缀。每个类库项目都拥有一个默认的命名空间,可以通过类库项目的属性窗口来指定。需要仔细区分“类库项目”、“程序集”和“命名空间”这三个概念的区别:
(1)每个类库项目编译之后,将会生成一个程序集。
(2)类库项目中可以拥有多个类,这些类可属于不同的命名空间。
(3)不同的类库项目可以定义相同的命名空间。
根据上述三个特性,可以得到以下结论:“命名空间”是一个逻辑上的概念,它的物理载体是“程序集”,具体体现为“DLL”(或EXE)文件。在Visual Studio 中,可通过创建“类库”类型的项目生成程序集。一个程序集可以有多个命名空间,而一个命名空间也可以分布于多个程序集。一旦生成了一个程序集,在其他项目中就可以通过添加对这一程序集的引用而使用此程序集中的类。其方法是在“项目”菜单中选择“添加程序集”命令,激活“浏览”卡片,选择一个现有的程序集文件(DLL 或EXE)。一个项目添加完对特定程序集的引用之后,就可以直接创建此程序集中的类了,当然要注意指明其命名空间。
八、委托
委托是一种新的面向对象语言特性,在历史比较长的面向对象语言比如C++中并未出现过。微软公司在设计运行于.NET Framework平台之上的面向对象语言(如C#和VisualBasic.NET)时引入了这一新特性。委托(delegate)也可以看成是一种数据类型,可以用于定义变量。但它是一种特殊的数据类型,它所定义的变量能接收的数值只能是一个函数,更确切地说,委托类型的变量可以接收一个函数的地址,很类似于C++语言的函数指针。简单地说:委托变量可看成是一种类型安全的函数指针,它只能接收符合其要求的函数地址。委托可以看成是一个函数的“容器”,将某一具体的函数“装入”后,就可以把它当成函数一样使用。定义委托类型时对函数的要求被称为函数的“签名(signature)”。函数的签名规定了函数的参数数目和类型,以及函数的返回值,体现了函数的本质特征。每一个委托都确定了一个函数的签名。拥有不同签名的函数不能赋值给同一类型的委托变量。因此,一个委托类型的变量,可以引用任何一个满足其要求的函数。
1.委托的组合与分解
委托变量可以代表某一函数,使用委托变量就相当于调用一个函数。如果仅是这么简单,那么直接调用函数不就行了吗?为什么还要引入“委托”这一特性?事实上,委托不仅可以代表一个函数,还可以组合“一堆”的函数,然后批量执行它们,这样的委托变量又称为“多路委托变量”。可以用加法运算符来组合单个委托变量为多路委托变量。类似地,也可以使用减法运算符来从一个多路委托变量中移除某个委托变量。
2.事件与多路委托
事件的主要特点是一对多关联,即一个事件源,多个响应者。在具体技术上,.NET Framework 的事件处理机制是基于多路委托实现的。事件与多路委托其实大同小异,只不过多路委托允许在事件源对象之外激发事件罢了。所有的.NET Framework 可视化窗体控件的预定义事件,都是某一对应的“事件名+Handler”委托类型的变量。与此事件相关的信息都封装在“事件名+Args”类型的事件参数中,此事件参数有一个基类EventArgs,它是所有事件参数的基类。明了上述内部机理,对于我们在程序中定义自己的事件非常有好处,尤其是开发一个自定义的可视化控件时,如果需要增加新的事件类型,我们应尽量遵循.NET Framework 的定义事件的框架,给事件取一个名字,定义一个“事件名+Handler”的事件委托类型,再从EventArgs 派生出自定义事件的参数,取名为“事件名+Args”。
面向对象的软件系统有许多都是事件驱动的,ASP.NET 就采用了“事件驱动”的编程方式。所谓“事件驱动”的开发方式,就是指整个系统包含许多的对象,这些对象可以引发多种事件,软件工程师的主要开发工作就是针对特定的事件书写代码响应它们。.NET 事件处理机制建立在委托的基础之上,而这两者都是ASP.NET 技术的基础之一。因此,必须牢固地掌握好委托和事件这两种编程技术,才能为掌握ASP.NET 技术扫清障碍。