| Friday, October 19 2001 7:15 PM
在"OOP入门:深入研究继承和多态性"这篇文章中,我们继续讨论了继承和多态性的好处.我们还学习了其它的东西:
- 虽然Java只支持从一个父类继承,但它使用接口的方式支持多重继承.
- 接口实现了多态,使得我们能够给与对象不同特性以满足不同的需要.
- 你可以使用多态机制让完成相似功能的不同的方法拥有相同的名字但是拥有不同的参数列表.
- 动态/运行时的绑定机制允许一个对象在运行时被强制转化成你所需要的对象类型,前提是这个对象实现了必需的接口或者括展了特定的父类.
下面我们将讨论通过限制对对象属性和方法的访问来强制实现对多重接口实现和父类拓展的正确使用的目的和实用性.
黑箱方法:封装 一个基本的面向对象的概念就是封装--将表示一个对象状态的数据与其它对象隔离开来.这一点是通过一个通常叫做作用域的概念来实现的.作用域指的是编程语言的一种能力,这种能力被用来实现一些限制对类或者结构体成员变量的访问的规则.大多数面向对象的语言支持作用域机制,这些机制通常是通过诸如public, protected, 和 private之类的特殊关键字来实现的.
Java提供了四种不同的作用范围:public, package, protected, 和 private.任何类,方法或者成员变量都能通过使用public, protected, 和 private关键字来显式的加以保护.任何类,方法,或者成员变量如果没有使用上面的关键字都将被隐式的给与package的作用范围.所有这些就构成了Java中命名空间的概念.
命名空间和软件包 一个命名空间可以被看成是在一个给定的上下文中一组相关的名字或是标识符.命名空间避免了拥有相同名字或标识符的实体存在于同一个上下文里.这里隐含的意思是只要实体是存在于不同的命名空间中,那么拥有相同名字或者标识符的实体就能够呆在一块儿.Java使用软件包的概念来实现命名空间和作用范围控制.
软件包是一个在统一的名字下的类和接口的集合.每一个类或者接口都必须存在于用package关键字构成的软件包申明语句定义的命名空间中.例如,下面的申明语句:
package com.mycompany.apps.HelloWorld;
它申明了一个存在于com.mycompany.apps软件包中的名叫HelloWorld的类或者接口.软件包申明总是放在包含了类或者接口定义的文件的顶部.
在java开发界,目前对软件包的命名有一个建议,就是使用公司或组织的域名(以相反的顺序),作为你的软件包的第一部分.因为域名是全球唯一的,所以使用你的域名来命名你的软件包也能使你软件包的名字全球唯一.
如果一个Java类或者接口没有包含一个软件包申明,那么它就属于"unamed package,"也就是没有名字的软件包.无名的软件包应该只用来测试程序或是代码原型等等.
| 请尽量使用封装机制 在任何程序风格中,尤其是在面向对象的编程中,将暴露的编程界面背后的实现细节隐藏起来是非常关键的.这使得低层的实现方法能够在不影响编程界面现有的客户端的前提下改变,而且能使对象完全自主的管理它们自己的状态.
分离界面和实现方法的第一步就是隐藏类的内部数据.要使一个成员变量或是方法对Java中所有潜在的客户不可见,可以将用private关键字将它声明为私有成员变量,如下所示:
private int customerCount;
要使一个成员变量或是方法除了其本身所属类的子类以外对Java中所有潜在的客户不可见可以使用protected关键字将它声明成保护类型的,如下所示:
protected int customerCount;
要使一个成员变量或是方法除了其本身所属的类以外对Java中所有潜在的客户不可见不使用任何关键字来声明它,如下所示:
int customerCount;
要将一个成员变量或是方法暴露给其所属类的所有客户,可以用public关键字将它声明为公共的成员变量,如下所示:
public int customerCount;
访问成员变量 不论一个对象的数据隐藏得多么好,客户仍然需要访问一些隐藏的数据.这是通过调用函数或方法来实现的.在Java中,使用特殊的被称做属性访问器的方法来访问隐藏的数据是可能的.在Java中属性访问器和通常的函数之间并没有本质的区别.将一个通常的方法转变成一个属性访问器唯一要做的事情就是参照一个命名规则来添加方法.
读数据的访问器的命名规则就是将方法命名为和数据域一样的名字,将首字母大写,然后在方法名字的前面添加get或是is."写"数据访问器的命名规则就是将方法命名为和数据域一样的名字,将首字母大写,然后在方法名字的前面添加set.下面的例子演示了写和读数据的数据访问器方法.
这是一个"读"数据访问器方法:
public int getCustomerCount() { return(customerCount); }
这是另一个"读"数据访问器方法
public int isCustomerActive() { return(customerActive); } 这是一个"写"数据访问器方法:
public void setCustomerCount(int newValue) { customerCount = newValue; } 使用访问器方法允许其它对象访问一个对象的隐藏数据而不直接涉及数据域.这就允许拥有隐含数据的对象在改变成员变量以前做正确性检查并控制成员变量是否应该被设置成新的值.
现在让我们修改例子程序来使用这些概念,如下所示.
public class HelloWorld { public static void main(String[] args) { Dog animal1 = new Dog(); Cat animal2 = new Cat(); Duck animal3 = new Duck(); animal1.setMood(Animal.COMFORTED); System.out.println("A comforted dog says " +animal1.getHello()); animal1.setMood(Animal.SCARED); System.out.println("A scared dog says " +animal1.getHello()); System.out.println("Is a dog carnivorous? " +animal1.isCarnivorous()); System.out.println("Is a dog a mammal? " +animal1.isCarnivorous()); animal2.setMood(Animal.COMFORTED); System.out.println("A comforted cat says " +animal2.getHello()); animal2.setMood(Animal.SCARED); System.out.println("A scared cat says " +animal2.getHello()); System.out.println("Is a cat carnivorous? " +animal2.isCarnivorous()); System.out.println("Is a cat a mammal? " +animal2.isCarnivorous()); animal3.setMood(Animal.COMFORTED); System.out.println("A comforted duck says " +animal3.getHello()); animal3.setMood(Animal.SCARED); System.out.println("A scared duck says " +animal3.getHello()); System.out.println("Is a duck carnivorous? " +animal3.isCarnivorous()); System.out.println("Is a duck a mammal? " +animal3.isCarnivorous()); } }
abstract class Animal { // The two following fields are declared as public because they need to be // accessed by all clients public static final int SCARED = 1; public static final int COMFORTED = 2; // The following fields are declared as protected because they need to be // accessed only by descendant classes protected boolean mammal = false; protected boolean carnivorous = false; protected int mood = COMFORTED ; public boolean isMammal() { return(mammal); }
public boolean isCarnivorous() { return(carnivorous); }
abstract public String getHello();
public void setMood(int newValue) { mood = newValue; }
public int getMood() { return(mood); } }
interface LandAnimal { public int getNumberOfLegs(); public boolean getTailFlag(); }
interface WaterAnimal { public boolean getGillFlag(); public boolean getLaysEggs(); }
class Dog extends Animal implements LandAnimal { // The following fields are declared private because they do not need to be // access by any other classes besides this one. private int numberOfLegs = 4; private boolean tailFlag = true; // Default constructor to make sure our properties are set correctly public Dog() { mammal = true; carnivorous = true; } // methods that override superclass's implementation public String getHello() { switch (mood) { case SCARED: return("Growl"); case COMFORTED: return("Bark"); } return("Bark"); }
// Implementation of LandAnimal interface
public int getNumberOfLegs() { return(numberOfLegs); }
public boolean getTailFlag() { return(tailFlag); } }
class Cat extends Animal implements LandAnimal { // The following fields are declared private because they do not need to be // access by any other classes besides this one. private int numberOfLegs = 4; private boolean tailFlag = true; // Default constructor to make sure our properties are set correctly public Cat() { mammal = true; carnivorous = true; } // methods that override superclass's implementation public String getHello() { switch (mood) { case SCARED: return("Hiss"); case COMFORTED: return("Purr"); } return("Meow"); }
// Implementation of LandAnimal interface public int getNumberOfLegs() { return(numberOfLegs); }
public boolean getTailFlag() { return(tailFlag); } }
class Duck extends Animal implements LandAnimal, WaterAnimal { // The following fields are declared private because they do not need to be // access by any other classes besides this one. private boolean gillFlag = false; private boolean laysEggs = true; private int numberOfLegs = 2; private boolean tailFlag = false; // Default constructor to make sure our properties are set correctly public Duck() { mammal = false; carnivorous = false; } // methods that override superclass's implementation public String getHello() { switch (mood) { case SCARED: return("Quack, Quack, Quack"); case COMFORTED: return("Quack"); } return("Quack"); }
// Implementation of WaterAnimal interface
public boolean getGillFlag() { return(gillFlag); }
public boolean getLaysEggs() { return(laysEggs); }
// Implementation of LandAnimal interface
public int getNumberOfLegs() { return(numberOfLegs); }
public boolean getTailFlag() { return(tailFlag); } }
这个程序的输出结果应该如下: A comforted dog says Bark A scared dog says Growl Is a dog carnivorous? true Is a dog a mammal? true A comforted cat says Purr A scared cat says Hiss Is a cat carnivorous? true Is a cat a mammal? true A comforted duck says Quack A scared duck says Quack, Quack, Quack Is a duck carnivorous? false Is a duck a mammal? false
总结 使用数据隐藏/封装机制是一个控制对象数据和状态的强有力的方法.它允许一个对象决定是否要改变一个成员变量和如何改变一个成员变量.这使得一个对象的实现细节能够改变而暴露的对象界面得以维持.在我们的下一篇文章中,我们将进一步探讨Java提供的变量作用域规则并开始学习Java对象是如何构造和初始化的. |