3.5.1 情境描述
Tom 在该公司进一步调查发现,同样是 B 类员工,有的员工工资计算的时候,计算了岗 位工资,没有时间工资,而另外一部分员工有时间工资,没有岗位工资,为了解决这个问题,
Tom 需要完成以下任务:
(1)利用基类派生出子类。
(2)重写基类中的工资计算方法。
3.5.2 问题分析
B 类员工的工资计算不是简单的拆分问题,故针对 B 类员工而言,其工资的计算应该派 生出两个子类, 分别处理两种不同业务逻辑的工资计算, 在子类中对不带参数的方法 calWages 进行重写可以达到目的。
3.5.3 解决方案
(1)打开 Eclipse,选择项目 Task3_4,复制粘贴形成新的项目 Task3_5。
(2)修改 SalaryB 的类,去掉非公共成员。
/**
*
*/
package com.esms;
/**
* @author 李法平
*
*/
public class SalaryB extends Salary {
private double baseWage;
private double timeWage;
/**
* 构造方法
*
* @param obj
* @param wage
*/
public SalaryB(EmployeeB obj) { super(obj);
this.baseWage = 0;
}
/**
* 构造方法
*
* @param obj
* @param wage
* @param baseWage
* @param posWage
* @param ageWage
* @param timeWage
*/
public SalaryB(EmployeeB obj, double baseWage) { super(obj);
this.baseWage = baseWage;
}
/**
* @return the baseWage
*/
public double getBaseWage() { return baseWage;
}
/**
* @param baseWage
* the baseWage to set
*/
public void setBaseWage(double baseWage) { this.baseWage = baseWage;
} /*
* 工资计算
*
* @see com.esms.Salary#calWages()
*/
@Override
public void calWages() {
this.wage = this.baseWage ; }
/**
* 具备加班工资的工资计算
*
* @param addWage
*/
public void calWages(double addWage) { calWages();
this.wage = this.wage + addWage;
} }
(3)新建 SalaryB 的派生类 SalaryPostB 及 SalaryTimeB 两个子类,具体如图 38 所示。
图 38 新建 SalaryPostB
新建成功之后,分别修改两个类的构造方法,适应新的构造。修改 SalaryPostB 类的构造 方法。
/**
*
*/
package com.esms;
/**
* @author 李法平
*
*/
public class SalaryPostB extends SalaryB { private double posWage;
/**
* @return the posWage
*/
public double getPosWage() { return posWage;
}
/**
* @param posWage the posWage to set
*/
public void setPosWage(double posWage) { this.posWage = posWage;
}
/**
* @param obj
*/
public SalaryPostB(EmployeeB obj) { super(obj);
//TODO Autogenerated constructor stub }
/**
* @param obj
* @param baseWage
* @param posWage
* @param timeWage
*/
public SalaryPostB(EmployeeB obj, double baseWage, double posWage ) {
super(obj, baseWage);
this.posWage=posWage;
//TODO Autogenerated constructor stub }
}
修改 SalaryTimeB 类的构造方法。
/**
*
*/
package com.esms;
/**
* @author 李法平
*
*/
public class SalaryTimeB extends SalaryB { double timeWage;
/**
* @param obj
*/
public SalaryTimeB(EmployeeB obj) { super(obj);
//TODO Autogenerated constructor stub }
/**
* @param obj
* @param baseWage
* @param posWage
* @param timeWage
*/
public SalaryTimeB(EmployeeB obj, double baseWage, double timeWage) {
super(obj, baseWage );
this.timeWage=timeWage;
//TODO Autogenerated constructor stub }
/**
* @return the timeWage
*/
public double getTimeWage() { return timeWage;
}
/**
* @param timeWage the timeWage to set
*/
public void setTimeWage(double timeWage) { this.timeWage = timeWage;
}
/* (nonJavadoc)
* @see com.esms.SalaryB#calWages()
*/
@Override
public void calWages() {
EmployeeB obj = (EmployeeB) this.empObj;
this.wage=this.getBaseWage()+this.getTimeWage()*obj.getEmployeeWorkTimes();
} }
(4)针对 SalaryPostB 类,重写 calWages 方法。
/* (nonJavadoc)
* @see com.esms.SalaryB#calWages()
*/
@Override
public void calWages() {
this.wage=this.getBaseWage()+this.getPosWage();
}
(5)根据 SalaryTimeB 类的特性,在 SalaryTimeB 中重写 calWages 方法。
/* (nonJavadoc)
* @see com.esms.SalaryB#calWages()
*/
@Override
public void calWages() {
EmployeeB obj = (EmployeeB) this.empObj;
this.wage=this.getBaseWage()+
this.getTimeWage()*obj.getEmployeeWorkTimes();
}
(6)在 SalaryB 基类中编写主函数验证。
/**
* 主方法
*
* @param args
*/
public static void main(String[] args) {
EmployeeB objTom = new EmployeeB(); //创建员工对象 objTom.setEmployeeNo("001"); //设置工号 objTom.setEmployeeName("汤姆"); //设置姓名 SalaryB salaryTom = new SalaryPostB(objTom,1500,2000);
//工资计算
salaryTom.calWages();
System.out.println("以下是 B 类员工按岗位计算工资情况:");
//输出当前员工的工资 salaryTom.print();
EmployeeB objJack=new EmployeeB();
objJack.setEmployeeNo("002");
objJack.setEmployeeName("杰克");
objJack.setEmployeeWorkTimes(64);
//第二个构造方法构造对象
SalaryB salaryJack = new SalaryTimeB(objJack,1000,30);
salaryJack.calWages(400);//带加班工资的计算
System.out.println("以下是 B 类员工按小时计算工资情况:");
salaryJack.print();
}
(7)运行结果如图 39 所示。
图 39 程序运行结果
3.5.4 知识总结 1.方法的覆盖
方法覆盖是 Java 实现多态性机制的另一种方式。在类的继承的部分已经作了介绍,由于 它在面向对象的程序设计中的重要性,这里再次对它进行详细的讨论。
在继承关系中,基类(也包括接口)和子类存在同名的方法,如果同时满足以下条件:
l 相同的参数(包括参数个数、类型、顺序)。
l 相同的返回值类型。
那么,子类的方法覆盖基类的方法。使用覆盖时需注意以下几点:
l 不允许出现参数相同,但返回值类型不同的同名方法。
l 子类方法不能比基类同名方法有更严格的访问范围。
l 子类方法不能比基类同名方法抛出更多的异常。
覆盖是一种动态的多态,它是通过基类引用来体现的。JVM 根据当前被引用对象的类型 来动态地决定执行的是覆盖方法的哪个版本。
2.多态的优点
覆盖这种形式的多态具有以下优点:
(1)可替换性:多态对已存在的代码具有可替换性。
(2)可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继 承性,以及其他特性的运行和操作。
(3)接口性:多态是接口通过方法签名,向子类提供一个共同接口,由子类来覆盖它而 实现的。
3.5.5 应用实践
为了更充分地理解多态,体现面向对象程序设计的核心思想,完成本实践内容。
修改实践 3.3.5,通过接口实现对圆、矩形求面积。
interface ShapeOver { double area();
}
class RectangleOver implements ShapeOver { public double length;
public double width;
public double area() { //覆盖接口的方法 return length * width;
} }
class CircleOver implements ShapeOver { public double radius;
public double area() { //覆盖接口的方法 return Math.PI * radius * radius;
} }
public class Practise3_5_5 {
public static void main(String[] args) {
RectangleOver r = new RectangleOver(); //声明一个矩形 CircleOver c = new CircleOver(); //声明一个圆 r.length = 10;
r.width = 8;
c.radius = 5;
ShapeOver shape;
shape = r;
printArea(shape); //实参是 shape,但它指向的是 RectangleOver()的实例 shape = c;
printArea(shape); //实参是 shape,但它指向的是 CircleOver()的实例 }
static void printArea(ShapeOver shape) {
System.out.println("面积是:" + shape.area());
//动态多态,根据传入对象的实际类型,调用正确的覆盖方法的版本 //是矩形时,调用矩形的面积计算方法;是圆时,调用圆的面积计算方法 }
}
在此基础上,读者可根据 Shape 的接口要求,增加实现三角形类等功能。