1.3 封装的秘密
面向独享的封装特性
字段赏析
属性赏析
在面向对象三要素中,封装特性为程序员提供了系统与系统,模块与模块,类鱼类之间交互的实现手段,封装为软件设计与开发带来前所未有的革命,成为构成面向对象技术最为重要的基础之一。在.net中,一切看起来已经被包装在.net Framework这一复杂网络中,提供给最终开发人员的是成千上万的类型,方法,和接口,而Framework内部一切已经做好了封装,列如,如果你想对文件进行必要的操作,那么使用System.IO.file基本就能够满足多变的需求,因为.net Framwork 已经1把对文件的重要操作都封装在System.IO.File等一些基本类中,用户不需要关心具体的实现。
那么封装究竟是什么?
首先,我么考察一个常见的生活实例来进行说明,列如每当发工资的日子小王都来到ATM机前,用工资卡取走一笔钱为女朋友买礼物,从这个很帅的动作,可以的出以下结论;
小王和ATM机之间,以银行卡进行交互,要取钱,请交卡
小王并不知道atm机将钱放在什么地方,取款机如何计算钱款,又如何通过银行卡返回小王所要数目的钱,对小王来说,atm就是一个黑匣子,只能等着取钱,而对银行来说,atm机就像银行自己的一份子是安全可靠精壮的额员工。
小王要想渠道自己的钱,必须遵守atm机的对外约定,他的任何违反约定的行为都被视为不轨,列如欲以砖头砸开取钱,用公交卡冒名取钱,盗卡取钱都将面临法律危险,所以小王只能安分守纪的过月光族的日子。
那么小王和atm机的故事,能给我们什么样的启示,对应上面的3条结论,我们的分析如下
小王以工资卡和Atn机交互信息,atm机的如卡口就是atm机提供的对外接口,砖头是塞不进去的,公交卡放进去也没有用。
atm机在内部完成身份验证余额查询,计算取款等各项服务,具体的操作对用户小王是不可见的,对银行来说这种封闭操作带来了安全性和可靠性。
小王和atm机之间遵守了银行规定 ,国家法律这样的协约,这些协约和法律,就挂在atm机旁边的墙上
结合前面的示例再来分析封装吧,具体来说封装隐藏了类内部的具体实现细节,对则提供统一访问接口,来操作内部数据成员,这样实现的好处是实现了ui分离,程序员不需要知道类内部的具体实现细节,对外测提供统一访问接口,来操作内部数据成员,这样实现的好处是实现了ui分离,程序员不需要知道类内部的具体实现,只需要按照接口协议进行控制即可。同时对类内部来说,封装保证了类内部成员的安全性和可靠性。在上例中,atm机可以看做分装了各种取款操作的类,取款,验证的操作对类atm来说,都在内部完成,而atm类还提供了与小王交互的统一接口,并以文档形式--法律法规,规定了接口的规范与协定来保证服务的正常运行。以面向对象的语言来表达哦类似于下面的样子;
在.net应用中,Framework分装了你能想到的各种常见的操作,就像微软提供给我们一个又一个功能不同的atm机一样,而程序员手中筹码就是根据.net规范进行开发,是否能取出自己的钱,要看你的卡是否合法。
那么如果你是银行的主管,又该如何设计自己的atM呢?该以什么样的技术来保证自己的atm在内部隐藏实现,对外提供接口呢?
1.3.3 秘密何处,字段,属性和方法
字段,属性和方法,是面向对象的基本概念之一,其基本的概念介绍不是本书的范畴,任何一本关于语言和面向对象的著作中都有相关的详细解释,本书关注的是在类设计之初应该基于什么样的思路,来实现类的功能要求与交互要求?每个设计这,是以什么角度来完成对类型架构的设计与规划呢?在我看来,下面的问题应该首先被列入讨论的选项:
类的功能是什么?
那些是字段,哪些是属性,哪些是方法,
对外提供的公有方法有哪些,对内隐藏的私有变量有哪些
类与类之间的关系是继承还是聚合?
这些看似简单的问题,却往往是困扰我们进行有效设计的关键因素,通常系统需求描述的核心名称,可以抽象伪类,而对这些名词驱动的动作,可以对应的抽象为方法,当然,具体的设计思路要根据具体的需求情况,在整体架构目标的基础上进行有效的筛选,玻璃和抽象,取舍之间,彰显oo智慧与设计模式的魅力。
那么了解这些选项与原则我们就不难理解关于字段,属性和方法的实现思路了,
这些规则可以从对字段,属性和方法的探索中找到痕迹,然后从反方向来完善我们对于如何设计思考与理解。
1.字段
字段field通常定义为private,表示类的状态信息。clr支持只读和读写字段,值得注意的是,大部分情况下字段都是可读可写的只读字段只能在构造函数中被赋值,其他方法不能改变只读字段,常见的字段定义为
如果以public表示类的状态信息,则我们就可以以类实例访问和改变这些字段内容,列如;
这样看起来并没有带来什么问题,client实例通过操作公有字段很容易达到状态信息的目的。然而封装原则告诉我们,类的字段信息最好以私有方式提供给类的外部,而不是以公有方式来实现,否则不适当的操作将造成不必要的错误方式,破坏对象的状态信息,数据安全性和可靠性无法保证列如:
显然,小王的年龄不可能是1000岁,它使人不是怪物,小王的莫玛也不可能是这些特许符号,应为atm机上根本没有这样的按键,而且密码必须是6位,所以对字段公有化的操作,会引起对数据安全性与可靠性的破坏,封装的第一个原则就是:将字段定义为private
那么如上文所言,将字段设置为private后对对象状态信息的控制又该如何实现呢,小王的状态信息必须以另外的方式提供给类外部访问或者改变,同时我们也期望除了实现对数据的访问,最好能加入一定的操作,达到数据控制的摸底,因此,面向对象引入了另一个重量级的概念,属性。
属性通常定义为public 表示类的对外成员,属性具有可读,可写属性 通过get和set访问器来实现其读写控制,列如上文中client类的字段,我们可以相应的分装其为属性;
当我们再次以
这样的方式来实现对小王的年龄进行控制时,自然会弹出异常提示,从而达到了保护数据完整性的目的,那么属性的get和set访问器怎么实现对对象属性的读写控制呢我们打开ILDASM工具查看client类反编译后的情况时,会发现如图1-10所示的情形
il中不存在get和set方法 而是分别出现了get_Age,Set_Age这样的方法,打开其中的任意方法分析会发现,编译器的执行逻辑是,如果发现一个属性,并且查看该属性中实现了get还是set,就对应的生成get_属性名,set_属性名两个方法,因此我们可以说属性的实质其实就是一个相应方法的调用,它以一种间的形式实现了方法。
所以我们亦可以定义自己的get和set访问器,列如
事实上,这种实现方法征税java语言所采用的机制,而这样的方式显然没有实现get和set访问器来得轻便,而且对属性的操作也带来多余的麻烦,所以我们推荐的还是下面的方式:
您外get和set对属性的读写控制,是通过实现get和set的组合来实现的,如果属性为只读则只实现get访问器即可,如果属性为可写,则实现set访问器即可
通过对共共属性的访问来实现对类状态信息的读写控制,主要有两点好处,一是避免了对数据安全的访问限制,包含内部数据的可靠性,二是避免了类扩展或者修改带来的变量连锁反应
至于修改变量带来的连锁反应,表现在对类的状态信息的需求信息发生变化时,如何来减少代码重构基础上,实现最小的损失和最大的补救例如,如果对client的用户姓名由原来的简单name来标志,换成以firstname和secondname来实现,如果不是属性封装了字段而带来的隐藏内部细节的特点,那么我们在代码中就要拼命的替换原来xiaowang。name这样的实现了。列如
这样带来的好处是,我们只需要更改属性定义中的实现细节,而原来的程序xiaowang。name这样的实现就不需要做任何修改即可适应新的需求,你看这就是封装的强大力量使然。
还有一种含参属性中加入了对参数的处理过程巴拉。
方法
方法封装了勒的行为,提供了类的对外表现,用于将封装的内部细节以公有方法提供对外接口,从而实现与外部的交互与响应,列如从上面属性的分析我们可知,实际上对属性的读写就是通过方法来实现的,因此,对外交互的方法,通常实现为public
当然不是所有的方法都被实现为public 否则类内部的实现岂不是全部暴露在外,必须对对外的行为与内部操作加以区分,因此,通常将在内部的操作全部yiprivate方式来实现,而将需要与外部交互的方法实现为public,这样既保证了对内部数据的隐藏与保护,又实现了类的对外交互,列如在atm类中,对钱的计算,用户验证这些方法涉及银行的=关键数据与安全数据的保护问题,必须以private方法类实现,已隐藏对用户不同命测操作,而提供·返回钱款这一public方法接口即可,在封装原则中,有效的保护内部数据和有效的暴露外部行为一样关键
那么这个过程应该如何来实现呢,还是回到atm类的实例中,我们首先关注两个方法;isvalidUser()和cashprocess其中isvaliduser()用于验证用户的合法性,而cashprocess()用于提供用户操作接口,显然验证用户是银行本省的事情,外部用户无权访问,它主要用于在内部进行验证处理操作,列如cashprocess()中就以isvaliduser()作为方法的进入条件,因此很容易知道isvaliduser()被实现为private,而cashprocess()用于和外部客户进行交互操作,这正是我们反复强调的外部接口方法,显然应该实现为public其他的方法getuser(),getcash也是从这一主线出发来确定其对外封装权限的,自然就能找到合理的定位,从这个过程中我梦发现吗,谁为公有,谁为私有,取决于需求和设计双重因素,在职责单一原则下为类型设计方法,应该广泛考虑的是类本省的功能性,从开发这与设计者的角度出发,分清访问权限就会水到渠成。
通过对字段,属性与方法在封装性这一点上的分析,我们可以更加明确的了解到分装特性作为面向对象的三大特性之一,表现出来的无与伦比的重要性与必要性,对于升入的理解系统设计与类设计提供了绝好的切入点
下面我们针对上文的分析进行小结,以便更好的理解我梦对于封装所提出的思考,主要包括:
1.字段通常定义为private 属性通常实现为public 而方法在内部实现为private。对外部实现为public,从而保证对内部数据的可靠性读写控制,保护了数据的安全和可靠,同时又提供了与外部接口的有效交互。这是类得以有效的基础机制。
通常情况下的理解正如我们上面提到的规则,但没是具体操作还是根据实际的设需求而定,列如有些时候将属性实现weiprivate,也将方法实现为private是更好的选择,列如zaiatm类中,可能需要提供计数器来记录或者选择次数,而该次数对用户而言是不必要的状态信息,因此也以private方式来提供从而达到数据安全的目的
从存和数据持久性角度来看,有一个很重要但常常被忽视的事实是,封装属性提供了数据持久化的有效手段,应为,对象的属性和对象一样在内存期间是常驻的,只要对象不被垃圾回收,其属性也将一直存在,并且记录醉经一次对其更改的数据。
在面向对象中,分装的意义还远不止类设计层面对字段属性和方法的控制,更重要的是其广义层面。我们理解的封装,应该是以实现ui分离为目的的软件设计方法,一个系统或者软件开发之后,从维护和升级的目的考虑,一定要保证对外接口部分的绝对稳定,不管系统内部功能性实现如何多变,保证接口稳定是保证软件兼容,稳定,健壮的根本,所以oo智慧中的封装性旨在保证:
隐藏系统实现的细节,保证系统的安全性和可靠性。
提供稳定不变的对外接口,因此系统中相对稳定部分常被抽象为借口。
封装保证了对吗模块化,提高了软件的复用和功能分离.
封装规则
现在我们对封装特性的规则做一个总结,这些规则就是在平常的实践中提炼与完善出良药,我们在进行实际开发和设计工作谁因尽量遵守规则么人不是盲目的寻求方法。
尽可能的调用类的访问器而不是成员,即使在类的内部其目的在我们的示例中已有说明,列如clinet类中dename属性就可以避免由于需求变化带来的代码更改问题。
内部私优化部分可以任意更该,但是一定要在保证外不接口稳定的前提下。
将对字段的读写控制实现为属性,而不是方法,否则舍近而求远,非明智之举。
类的分装是由访问权限来保证的对内实现为private对外实现为public再结合继承特性们还要对protected,internal有较深的理解,详细的情况参见1.1节“对象的旅行”。
封装的精华是分装的变化张扬在,分装变化是面向对象思想的核心,他提到开发者因从设计角度和实用角度两方面来分析封装,因此我们将系统中变化怕频繁的部分封装为独立的部分,这种隔离选择有利于充分的软件服用和系统柔性。
封装是什么,很骚全文我梦的结论是封装是一个和包装,将包装的内外分为两个控件,对内实现数据私有,对外实现方法调用,保证了数据的完整性和安全性,
我们从封装的意义谈起,然后逐层深入到对字段,属性和方法在定义和实现上的规则,这是一次自上而下的探究方式,也是一次反其道而行的揭秘旅程,关于封装,远不是本届所能全面给展现的话题,关于封装的技巧和更多深入的糖球,来自于面向对象,来自于设计模式,也来自于软件工程,因此要想全面而准确的认识封装除了本节打下的基础之外,不断的在实际学习中完善和总结是不可缺少的这在,net学习中也是至关重要的。