面向对象三大特性:继承、封装、多态
— 什么是继承?
继承,就是面向对象中类与类之间的一种关系。继承的类称为子类、派生类,而被继承类称为父类、基类或超类。通过继承,使得子类具有父类的属性和方法,同时子类也可以通过加入新的属性和方法或者修改父类的属性和方法建立新的类层次。
继承机制体现了面向对象技术中的复用性、扩展性和安全性。为面向对象软件开发与模块化软件架构提供了最基本的技术基础。
— 继承的实现本质
— 继承是可传递的,子类是对父类的扩展,必须继承父类方法,同时可以添加新方法。
— 子类可以调用父类方法和字段,而父类不能调用子类方法和字段。
— 虚方法如何实现覆写操作,使得父类指针可以指向子类对象成员。
— 子类不光继承父类的公有成员,同时继承了父类的私有成员,只是在子类中不被访问。
— new 关键字在虚方法继承中的阻断作用。
— 继承的分类与规则
在.NET 中,继承按照其实现方式的不同,一般分类如下。
— 实现继承:派生类继承了基类的所有属性和方法,并且只能有一个基类,在.NET 中 System.Object 是所有类型的最终基类,这种继承方式称为实现继承。
— 接口继承:派生类继承了接口的方法签名。不同于实现继承的是,接口继承允许多继承,同时派生类只继承了方法签名而没有方法实现,具体的实现必须在派生类中完成。因此,确切地说,这种继承方式应该称为接口实现。
实现继承通常情况下表现为对抽象类的继承,而其与接口继承在规则上有以下几点归纳:
— 抽象类适合于有族层概念的类间关系,而接口最适合为不同的类提供通用功能。
— 接口着重于 CAN-DO 关系类型,而抽象类则偏重于 IS-A 式的关系。
— 接口多定义对象的行为;抽象类多定义对象的属性。
— 如果预计会出现版本问题,可以创建“抽象类”。例如,创建了狗(Dog)、鸡(Chicken)和鸭(Duck),那么应该考虑抽象出动物(Animal)来应对以后可能出现马和牛的事情。而向接口中添加新成员则会强制要求修改所有派生类,并重新编译,所以版本式的问题最好以抽象类来实现。
— 因为值类型是密封的,所以只能实现接口,而不能继承类。
— 继承与聚合
面向对象的基本原则
多聚合,少继承。
低耦合,高内聚。
— 继承的局限
继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。
继承的缺点有以下几点:
①:父类的内部细节对子类是可见的。
②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。
什么是封装?
封装就是一个包装,将包装的内外分为两个空间,对内实现数据私有,对外实现方法调用,保证了数据的完整性和安全性。
封装特性为程序设计提供了系统与系统、模块与模块、类与类之间交互的实现手段。具体来说,封装隐藏了类内部的具体实现细节,对外则提供统一访问接口,来操作内部数据成员。这样实现的好处是实现了 UI 分离,程序员不需要知道类内部的具体实现,只需按照接口协议进行控制即可。同时对类内部来说,封装保证了类内部成员的安全性和可靠性。
(1)字段通常定义为 private,属性通常实现为 public,而方法在内部实现为 private,对外部实现为 public,从而保证对内部数据的可靠性读写控制,保护了数据的安全和可靠,同时又提供了与外部接口的有效交互。这是类得以有效封装的基础机制。
(2)通常情况下的理解正如我们上面提到的规则,但是具体的操作还要根据实际的设计需求而定,例如有些时候将属性实现为 private,也将方法实现为 private 是更好的选择。例如在 ATM类中,可能需要提供计数器来记录更新或者选择的次数,而该次数对用户而言是不必要的状态信息,因此只需在 ATM 类内部实现为 private 即可;同理,类型中的某些方法是对内部数据的操作,因此也以 private 方式来提供,从而达到数据安全的目的。
(3)从内存和数据持久性角度上来看,有一个很重要但常常被忽视的事实是,封装属性提供了数据持久化的有效手段。因为,对象的属性和对象一样在内存期间是常驻的,只要对象不被垃圾回收,其属性值也将一直存在,并且记录最近一次对其更改的数据。
(4)在面向对象中,封装的意义还远不止类设计层面对字段、属性和方法的控制,更重要的是其广义层面。我们理解的封装,应该是以实现 UI 分离为目的的软件设计方法,一个系统或者软件开发之后,从维护和升级的目的考虑,一定要保证对外接口部分的绝对稳定。不管系统内部的功能性实现如何多变,保证接口稳定是保证软件兼容、稳定、健壮的根本。所以 OO 智慧中的封装性旨在保证:
— 隐藏系统实现的细节,保证系统的安全性和可靠性。
— 提供稳定不变的对外接口。因此,系统中相对稳定部分常被抽象为接口。
— 封装保证了代码模块化,提高了软件的复用和功能分离