-- 作者:admin
-- 发布时间:2006/2/26 21:56:48
-- 课件11下载——类的高级内容
关于Java类的面向对象的设计内容还包含抽象、多态、抽象类、接口和类的复用等。
1、类的抽象 观察下面这个程序。该程序的主要功能是模拟银行业务中经常进行的货币信息处理,它可以有效的表达诸如人民币和美元等货币金额的设置和格式化显示,如: public class exec { public static void main(String args[]) { double amount=1234; System.out.println("Y"+amount); amount=2345; System.out.println("$"+amount); } }
可以看的出来,这里没有采用面向对象的设计方式,程序从而缺乏良好的可扩展性和可维护性。 通过一定的数据抽象,可以发现具有“1234”金额同时能够输出“Y”符号的货币代表了人民币的客观事物,具有“2345”金额同时能够输出“$”符号的货币代表了美元的客观事物,所以按照类的抽象原则,应当识别出这些类,并且根据这些类进行应用程序的设计。
新的程序代码如下: 人民币类: public class Yuan { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } public String toString() { return "Y"+amount; } }
美元类: public class Dollar { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } public String toString() { return "$"+amount; } }
应用程序为: public class exec { public static void main(String args[]) { Yuan y=new Yuan(); y.setAmount(1234); System.out.println(y); Dollar d=new Dollar(); d.setAmount(2345); System.out.println(d); } }
和原先的设计相比,这种设计具有更好的可阅读性和可修改性,即良好的可扩展性和可维护性。
但是此时的类抽象层次是比较低的,因为在Yuan类和Dollar类中存在大量的重复代码,如设置和读取金额属性的操作方法。根据类的继承原则,应当将这些所有货币都具有的属性和方法封装到一个更高层次的类中,此时的Yuan类和Dollar类可以通过继承这个类有效的实现类的复用,如: 货币的父类为: public class Money { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } //输出为空,等待子类重写去实现自己的方法 public String toString() { return ""; } }
人民币类为: public class Yuan extends Money { public String toString() { return "Y"+amount; } }
美元类: public class Dollar extends Money { public String toString() { return "$"+amount; } }
应用程序为: public class exec { public static void main(String args[]) { Yuan y=new Yuan(); y.setAmount(1234); System.out.println(y); Dollar d=new Dollar(); d.setAmount(2345); System.out.println(d); } }
2、类的多态 在面向对象的语言中,由于子类通过继承可以获得父类的全部成员,所以完全可以把子类对象看成是一个父类对象,当然这个对象其实除了具有那些继承过来的父类成员外,还有自己的新成员,包含被子类重写的同名父类成员。
具体到程序设计中,就是意味着可以将父类的引用指向子类的对象变量,如: 父类为: public class CLS { public int i=1; public void f() { System.out.println("CLS"); } }
子类为: public class A extends CLS { public int ia=2; public void f() { System.out.println("A"); } }
应用程序为: public class exec { public static void main(String args[]) { CLS c=new A(); System.out.println(c.i); } } 输出为:1
程序中的父类引用c指向的对象就是子类对象,显然此时的子类对象已经被看成是一个父类对象了,而且通过这个父类引用,甚至连子类自己的成员都无法直接访问,所以这个代码是错误的: public class exec { public static void main(String args[]) { CLS c=new A(); System.out.println(c.ia); } }
要想实现预期功能,得到子类自己的成员,需要使用强制类型转换,如: public class exec { public static void main(String args[]) { CLS c=new A(); System.out.println(((A)c).ia); } } 输出为:2
但是这样的写法有个特点,那就是对于通过父类引用调用的子类方法而言,可以在程序运行期间动态的判断子类的类型,并根据子类对象的真实类型去调用子类自己的同名方法,如: public class exec { public static void main(String args[]) { CLS c=new A(); c.f(); } } 输出为:A
这种特征称为类的多态,即同一个父类引用调用的方法,会随着父类引用在程序运行期间真实指向的子类类型而去调用这些子类自己的同名方法,产生了虽然只有一个语句却具有多种运行状态的效果。
再次看一下上述银行处理货币信息的程序。虽然该程序可以有效的根据不同的货币类来得到不同的输出,然而有时也会存在应用程序的代码重复。下面的应用程序中调用了printMoney方法,而由于printMoney方法要实现不同货币的打印输出,所以需要不同参数的printMoney重载方法,如: public class exec { public static void main(String args[]) { Yuan y=new Yuan(); y.setAmount(1234); System.out.println(y); printMoney(y); Dollar d=new Dollar(); d.setAmount(2345); System.out.println(d); printMoney(d); } public static void printMoney(Yuan m) { System.out.println(m); } public static void printMoney(Dollar m) { System.out.println(m); } }
显然,这种代码的重复会随着printMoney功能的复杂而趋甚。通过类的多态特性,使用父类的引用来操作子类对象,利用多态功能将相同的程序代码产生不同的程序行为,就可以有效的改善这一情况。如: public class exec { public static void main(String args[]) { Money m=new Yuan(); m.setAmount(1234); System.out.println(m); printMoney(m); m=new Dollar(); m.setAmount(2345); System.out.println(m); printMoney(m); } public static void printMoney(Money m) { System.out.println(m); } }
3、抽象类 上述程序中Money类的主要作用是提供子类所需的共享属性和方法,然而创建它的对象显然意义不是很大,可以可以进行,如: public class exec { public static void main(String args[]) { Money m=new Money(); m.setAmount(1234); System.out.println(m); printMoney(m); } public static void printMoney(Money m) { System.out.println(m); } } 输出为空
抽象类的主要功能就在于它本身是不能创建对象的,必须通过继承的方式生成子类,通过子类才能创建对象。 实现抽象类的方法是在类的声明时加入abstract关键字,如将上述Money类改为Money抽象类: public abstract class Money { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } public String toString() { return ""; } } 此时如果再创建Money类的对象,编译时报错,“Money是抽象的,无法对其进行实例化”
4、接口 接口是另外一种特殊的类,它和抽象类很相似,也不能创建它的对象,然而它比抽象类具有更高的抽象级别,如不能定义一般的对象属性,不能有方法的代码实现部分等等,如将上述Money类改为Money接口: public interface Money//注意interface { public void setAmount(double a); public double getAmount(); public String toString(); }
人民币类为: public class Yuan implements Money//注意implements { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } public String toString() { return "Y"+amount; } }
美元类为: public class Dollar implements Money//注意implements { protected double amount=0; public void setAmount(double a) { amount=a; } public double getAmount() { return amount; } public String toString() { return "$"+amount; } }
应用程序没有改变,为: public class exec { public static void main(String args[]) { Money m=new Yuan(); m.setAmount(1234); System.out.println(m); printMoney(m); m=new Dollar(); m.setAmount(2345); System.out.println(m); printMoney(m); } public static void printMoney(Money m) { System.out.println(m); } }
接口的设计原则是比较多的,使用它的好处也是显而易见的,它可以使得人们利用接口来表现与代码实现完全无关的类的说明,而对于整个程序的架构也可以完全不涉及代码实现的只是针对接口来进行编程。具体程序运行时,需要使用该接口的类必须通过继承它并且实现真实的方法代码才能创建对象,而整个使用接口编写的程序代码利用接口的父类引用指向真正实现代码的子类对象,就可以通过类的多态来产生真正的程序功能。
考虑下面这样的一个方法,它的主要功能是在某种图形设备上输出一个红色的圆,定义如下: public void drawCircle(Graphics g) { g.setColor(Color.red); g.drawOval(0,0,10,10); } 应该说不同的图形设备(如屏幕和打印机)有不同的图形输出方法,所以即使是一个设置颜色的方法,也会有不同的实现。然而对于这个方法,其实完全不必考虑这种细节,相反,它要关心的问题是输出什么。如果将Graphics接口设置为所有的图形设备类的父类接口,那么这个方法显然是完全抽象于实现细节的,只是说明了所需功能。具体在执行方法时,通过将特定的接口子类传递给函数参数,就可以依*类的多态特性实现特定图形设备的图形输出。
5、类的复用 5、1 两种常用的类复用方法 复用类的方法除了常见的继承以外,还有其他的方式,如使用引用类型的属性成员。 考虑以前利用继承点类实现的圆类,如: 点类: public class Point { private int x; private int y; public void setX(int i) { x=i; } public int getX() { return x; } public void setY(int i) { y=i; } public int getY() { return y; } public String toString() { return "("+x+","+y+")"; } }
圆类: public class Circle extends Point { private int r; public void setR(int rr) { r=rr; } public int getR() { return r; } }
虽然这种利用继承的设计较为简便的实现了类的复用,但是存在很多问题,最为主要的问题在于难以进一步表达其他的几何类,如矩形类,因为一个矩形类应当具有表达矩形四个角的点对象,显然此时利用继承是无法实现的,因为从点类继承而来的x和y值只有一对,如: public class Rectangle extends Point { }
换种思路,矩形类应当写成这种方式,如: public class Rectangle { private Point[] ps=new Point[4]; public void setPoints(Point[] p) { for(int i=0;i<p.length;i++) { ps=new Point(); ps.setX(p.getX()); ps.setY(p.getY()); } } public Point[] getPoints() { Point[] tempPoint=new Point[4]; for(int i=0;i<tempPoint.length;i++) { tempPoint=new Point(); tempPoint.setX(ps.getX()); tempPoint.setY(ps.getY()); } return tempPoint; } public String toString() { String tempStr=""; for(int i=0;i<ps.length;i++) tempStr=tempStr+"["+ps.getX()+","+ps.getY()+"]"; return tempStr; } }
测试程序为: public class exec { public static void main(String args[]) { Rectangle rec=new Rectangle(); Point[] p=new Point[4]; for(int i=0;i<p.length;i++) p=new Point(); p[0].setX(0); p[0].setY(0); p[1].setX(100); p[1].setY(0); p[2].setX(100); p[2].setY(100); p[3].setX(0); p[3].setY(100); rec.setPoints(p); System.out.println(rec); } }
同理,圆类也可以设计为这种方式,如: public class Circle { private Point p=new Point(); private int r; public void setPoint(Point p) { this.p.setX(p.getX()); this.p.setY(p.getY()); } public Point getPoint() { Point tempPoint=new Point(); tempPoint.setX(p.getX()); tempPoint.setY(p.getY()); return tempPoint; } public void setR(int rr) { r=rr; } public int getR() { return r; } public String toString() { return "("+p.getX()+","+p.getY()+")"+"--"+r; } }
测试程序为: public class exec { public static void main(String args[]) { Circle c=new Circle(); Point p=new Point(); p.setX(1); p.setY(2); c.setPoint(p); c.setR(3); System.out.println(c); } }
归根到底,究竟使用哪一种类的复用方式关键在于类之间的逻辑关系:如果是“is”关系,即“类A是类B”,那么可以使用继承来做,如货币类和美元类,由于美元类是货币类,所以可以考虑利用继承货币类得到美元类;如果是“has”关系,即“类A包含类B”,那么可以使用引用类型的属性成员来做,如圆类和点类,由于是圆类包含点类,所以可以将点类对象作为圆类一个引用类型的成员属性值来处理。 不是继承的关系,非要用继承来做,会产生很多逻辑上的错误,如在利用继承点类得到圆类的方法中,按照类的多态特性,可以将每个圆类对象看成基类的对象,即为点类对象,这并不合适。而且,一个矩形类如果由四个点组成,那么甚至都可以说一个矩形类对象是有四个圆类对象组成的,这显然更加无理。
5、2 关于类对象的拷贝 一般而言,由于类对象采用的引用变量和对象变量分离的内存表示形式,使得我们一般是无法直接使用拷贝引用变量的方式来实现复制类对象,如: public class exec { public static void main(String args[]) { Point p1=new Point(); p1.setX(1); p1.setY(2); Point p2=p1; p2.setX(3); System.out.println(p1.getX()); } } 输出为:3
显然p2引用指向的对象变量和p1引用指向的对象变量是同一个,对象的复制没有产生。
那么如何实现对象的拷贝呢? 对于所有类的基类——Object类,它就提供了一个能够实现对象拷贝的方法,即clone()方法,显然Point类也具有这个继承而来的方法,但是这个方法默认的访问权限是受保护的,所以,只有在Point类重写此方法,并将其声明成公共的方法,同时要注意两点:第一是该方法要显式的声明抛出CloneNotSupportedException异常,这是基类方法原型的规定;而是必须实现一个接口,即Cloneable,其实这个接口什么都没有,只是起到一个标记性的作用。
如支持对象拷贝的点类 public class Point implements Cloneable { private int x; private int y; public void setX(int i) { x=i; } public int getX() { return x; } public void setY(int i) { y=i; } public int getY() { return y; } public String toString() { return "("+x+","+y+")"; } public Object clone()throws CloneNotSupportedException { return super.clone(); } }
测试程序为: public class exec { public static void main(String args[])throws CloneNotSupportedException { Point p1=new Point(); p1.setX(1); p1.setY(2); Point p2=(Point)p1.clone(); p2.setX(3); System.out.println(p1.getX()); } } 输出为:1
上述程序的运行结果表明对象的复制是成功的!
但是这种方法的使用存在很多问题,最为主要的问题在于Object类中的clone()方法只是简单的将一个对象的属性值分别拷贝给另一个对象的属性值,如果对象中存在引用类型的属性成员时,会发生问题。
如支持对象拷贝的圆类: public class Circle implements Cloneable { private Point p=new Point(); private int r; public void setPoint(Point p) { this.p.setX(p.getX()); this.p.setY(p.getY()); } public Point getPoint() { Point tempPoint=new Point(); tempPoint.setX(p.getX()); tempPoint.setY(p.getY()); return tempPoint; } public void setR(int rr) { r=rr; } public int getR() { return r; } public String toString() { return "("+p.getX()+","+p.getY()+")"+"--"+r; } public Object clone()throws CloneNotSupportedException { return super.clone(); } }
测试程序为: public class exec { public static void main(String args[])throws CloneNotSupportedException { Circle c1=new Circle(); Point p1=new Point(); p1.setX(1); p1.setY(2); c1.setPoint(p1); c1.setR(3); Circle c2=(Circle)c1.clone(); Point p2=new Point(); p2.setX(4); p2.setY(5); c2.setPoint(p2); c2.setR(6); System.out.println(c1); } } 输出为: (4,5)--3
显然,引用变量c1指向的圆对象被引用变量c2改变了!更为奇怪的是它仅仅改变了点,半径却仍然保留旧值!
这里的主要原因在于Object类中的clone()方法只是简单的将一个对象的属性值分别拷贝给另一个对象的属性值,对于引用类型的属性而言,这种简单的复制就会使得两个圆对象中的p引用变量都指向同一个Point类的对象,所以会发现引用变量c1指向的圆对象能够被引用变量c2改变,当然半径变量是整型的,因此不会产生这种问题。
所以,对于含有引用类型属性成员的类,更为合理的复制方法是不采用Object类中的clone()方法,而是自己来实现。如:
改进过的支持对象拷贝的圆类: public class Circle implements Cloneable { private Point p=new Point(); private int r; public void setPoint(Point p) { this.p.setX(p.getX()); this.p.setY(p.getY()); } public Point getPoint() { Point tempPoint=new Point(); tempPoint.setX(p.getX()); tempPoint.setY(p.getY()); return tempPoint; } public void setR(int rr) { r=rr; } public int getR() { return r; } public String toString() { return "("+p.getX()+","+p.getY()+")"+"--"+r; } public Object clone()throws CloneNotSupportedException { Circle tempCircle=new Circle(); tempCircle.p=new Point();//important tempCircle.p.setX(this.p.getX()); tempCircle.p.setY(this.p.getY()); tempCircle.setR(this.r); return tempCircle; } }
测试程序为: public class exec { public static void main(String args[])throws CloneNotSupportedException { Circle c1=new Circle(); Point p1=new Point(); p1.setX(1); p1.setY(2); c1.setPoint(p1); c1.setR(3); Circle c2=(Circle)c1.clone(); Point p2=new Point(); p2.setX(4); p2.setY(5); c2.setPoint(p2); c2.setR(6); System.out.println(c1); } } 输出为: (1,2)--3
[此贴子已经被作者于2010-12-12 07:55:51编辑过]
|