拾贰——面向对象程序的三大特性
一、面向对象的三大特点
面向对象有三大特点:封装性、继承性和多态性,它们是面向对象程序设计的灵魂所在。
1.封装的含义
封装( Encapsulation )是将描述某类事物的数据与处理这些数据的函数封装在一起,形成一个有机整体,称为类。类所具有的封装性可使程序模块具有良好的独立性与可维护性,这对大型程序的开发是特别重要的。类中的私有数据在类的外部不能直接使用,外部只能通过类的公有接口方法( 函数 )来处理类中的数据,从而使数据的安全性得到保证。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而仅需要通过外部接口,特定的访问权限来使用类的成员。
一旦设计好类,就可以实例化该类的对象。我们在形成一个对象的同时也界定了对象与外界的内外界限。至于对象的属性、行为等实现的细节则被封装在对象的内部。外部的使用者和其他的对象只能经由原先规划好的接口和对象交互。
我们可用一个鸡蛋的三重构造来比拟一个对象:
属性( Attributes )类似蛋黄,它隐藏于中心,不能直接接触,它代表的对象的状态( State )。
行为( Behaviors )类似蛋白,它可以经由接口与外界交互而改变内部的属性值,并把这种改变通过接口呈现出来。
接口( Interface )类似蛋壳,它可以与外界直接接触。外部也只能通过公开的接口方法来改变对象内部的属性( 数据 )值,从而使类中数据的安全性得到保证。
2.继承的含义
对象( Object )是类( Class )的一个实例( Instance )。如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。继承性是面向对象的第二大特征。继承( Inheritance )是面向对象程序设计中软件复用的关键技术,通过继承,可以进一步扩充新的特性,适应新的需求。这种可复用、可扩充技术在很大程度上降低了大型软件的开发难度,从而提高软件的开发效率。
当我们说某一个新类 A 继承某一既有类 B 时,表示这个新类 A 具有既有类 B 的所有成员,同时对既有类的成员作出修改,或是增加了新的成员。保持已有类的特性而构造新类的过程称为继承。在已有类的基础上新增自己的特性而产生新类的过程称为派生。我们把既有类称为基类( base class )、超类( super class )或者父类( parent class ),而派生出的新类,称为派生类( derived class )或子类( subclass )。
继承可以使得子类自动具有父类的各种属性和方法,而不需要再次编写相同的代码,从而达到类的复用目的。这样,子类 A 可以对父类 B 的定义加以扩充,从而制定出一个不同于父类的定义,让子类具备新的特性。故此,父类也不等于它的子类。
继承的目的在于实现代码重用,对已有的成熟的功能,子类从父类执行 “ 拿来主义 ”。而派生的目的则在于,当新的问题出现时,原有代码无法解决( 或不能完全解决 )时,需要对原有代码进行全部( 或部分 )改造。
3.多态的含义
多态( Polymorphisn ),从字面上理解,多态就是一种类型表现出多种状态。这也是人类思维方式的一种直接模拟,可以利用多态的特征,用统一的标识来完成这些功能。在 Java 中,多态性分为两类。
(1)方法多态性,体现在方法的重载与覆写上
方法的重载是指同一个方法名称,根据其传入的参数类型、个数和顺序的不同,所调用的方法体也不同,即同一个方法名称在一个类中有不同的功能实现。
方法的覆写是指父类之中的一个方法名称,在不同的子类有不同的功能实现,而后依据实例化子类的不同,同一个方法,可以完成不同的功能。
(2)对象多态性,体现在父、子对象之间的转型上
在这个层面上,多态性是允许将父对象设置成为一个与一个或更多的子对象相等的技术,通过赋值之后,父对象就可以根据当前赋值给的不同子对象,以子对象的特性加以运作。多态意味着相同的( 父类 )信息发送给不同的( 子 )对象,每个子对象表现出不同的形态。
多态中的一个核心概念就是,子类( 派生类 )对象可以视为父类( 基类 )对象。如鱼( Fish )类、鸟( Bird )类和马( Horse )类都继承与父类 Animal(动物)。
在 Java 编程里,我们可以用如下来描述:
Animal a;
Fish f = new Fish();
Bird b = new Bird();
House h = new Horse();
//统“一”的外部接口,表现出“多”种不同形态
a = f; a.move(); //鱼儿游
a = b; a.move(); //鸟儿飞
a = h; a.move(); //马儿跑
在上述代码中,分别实例化父类对象 a 以及子类对象 f、b 和 h。由于 Fish 类、Bird 类和 Horse 类均继承与父类 Animal,所以子类均继承了父类的 move() 方法。由于父类 Animal 的 move() 过于抽象,不能反映 Fish、Bird 和 Horse 等子类中 “ 个性化 ” 的 move() 方法。这样,我们在完成定义后,自然可以做到如下所示:
f.move(); //完成鱼类对象f的移动:鱼儿游
b.move(); //完成鱼类对象b的移动:鸟儿飞
h.move(); //完成鱼类对象h的移动:马儿跑
这并不是多态的表现,我们希望统一用父类对象 a 来接收子类对象 f、b、h,然后用统一的接口 “ a.move() ”,展现出不同的形态:当 “ a=f ” 时,“ a.move() ” 表现出的是子类 Fish 的 move() 方法——鱼儿游,而非父类的 move() 方法,以此类推。这样,就达到了 “ 一对多 ” 的效果——多态就在这里。
父、子对象之间的转型包括以下两种形式。
(1)向上转型( Upcast )( 自动转型 ):父类 父类对象 = 子类实例
将子类对象赋值给父类对象,这样将子类对象自动转换为父类对象。这种转换方式是安全的。例如,我们可以说鱼是动物,鸟是动物,马是动物。这种向上转型在多态中应用的很广泛。
(2)向下转型( Downcast )( 强制转型 ):子类 子类对象 = ( 子类 )父类对象
将父类对象赋值给子类对象。这种转换方式是非安全的。例如,我们说动物是鱼,动物是鸟,动物是马,这类描述是不全面的。因此,在特定背景下如果需要父类对象转换为子类对象,就必须使用强制类型转换。这种向下转型用的比较少。
二、封装的实现
下面具体讨论面向独享的第一大特性——封装性。
1.Java 访问权限修饰符
在Java 中有四种访问权限:公有( public )、私有( private )、保护( protected )、默认( default )。但访问权限修饰符只有三种,因为默认访问权限没有访问权限修饰符。默认访问权限是包访问权限,即在没有任何修饰符的情况下定义的类,属性和方法在一个包内都是客访问的。具体访问权限的规定如下表所示:
私有( private) | 默认( default ) | 保护( protected ) | 公有( public ) | |
类 | 只有内部类允许私有,只能在当前类中被访问 | 可以被当前包中的所有类访问 | 只有内部类可以设为保护权限,相同包中的类和其子类可以访问 | 可以被所有类访问 |
属性 | 只能被当前类访问 | 可以被相同包中的类访问 | 可以被相同包中的类和当前类的子类访问 | 可以被所有的类访问 |
方法 | 只能被当前类访问 | 可以被相同包中的类访问 | 可以被相同包中的类和当前类的子类访问 | 可以被所有的类访问 |
<