一、目的
通过编程和上机实验理解 Java中的继承和多态,掌握抽象类,以及如何实现抽象方法,
并创建对象,掌握Java接口及其应用
二、内容
(一)抽象类的应用
1、定义一个形状的抽象类(Shape),设计“矩形”、“三角形(等腰)”、“正方形”,“圆”等
类,并实现Shape的抽象方法,分别计算相应形状的面积和周长
Shape.java代码
package com.xianyu.abs;
public abstract class Shape {
private double length;
private double width;
private final double PI = 3.14;
public Shape(double length,double width){
this.length =length;
this.width =width;
}
public abstract double area();//求面积
public abstract double perimeter();//求周长
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public double getWidth() {
return width;
}
public double getPI() {
return PI;
}
}
Triangle.java代码
package com.xianyu.abs;
import com.xianyu.abs.Shape;
public class Triangle extends Shape {
public Triangle(double length, double width) {
super(length, width);
}
@Override
public double area() {
double area = 0.5*(super.getLength()*getWidth());
return area;
}
@Override
public double perimeter() {
double pm =getWidth()+2* Math.hypot(getLength(),getWidth()/2);
return pm;
}
}
Circle.java代码
package com.xianyu.abs;
public class Circle extends Shape{
public Circle(double length) {
super(length, length);
}
@Override
public double area() {
return getPI()*Math.pow(getLength()/2,2);
}
@Override
public double perimeter() {
return getPI()*getLength();
}
}
Rectangle.java代码
package com.xianyu.abs;
public class Rectangle extends Shape {
public Rectangle(double length, double width) {
super(length, width);
}
@Override
public double area() {
return getLength() * getWidth();
}
@Override
public double perimeter() {
return 2 * (getLength() + getWidth());
}
}
Square.java代码
package com.xianyu.abs;
public class Square extends Rectangle {
public Square(double side) {
super(side, side);
}
@Override
public double area() {
// Since both sides are equal, area is side^2
return Math.pow(getLength(), 2);
}
@Override
public double perimeter() {
// Perimeter of a square is 4 times one of the sides
return 4 * getLength();
}
}
TestShape.java代码
package com.xianyu.abs;
public class TestShape {
public static void main(String[] args) {
Triangle triangle = new Triangle(3,4);
System.out.println("三角形的面积是:"+triangle.area());
System.out.println("三角形的周长是:"+triangle.perimeter());
Circle circle = new Circle(3);
System.out.println("圆的面积是:"+circle.area());
System.out.println("圆的周长是:"+circle.perimeter());
Rectangle rectangle = new Rectangle(3, 4);
System.out.println("长方形的面积是: " + rectangle.area());
System.out.println("长方形的周长是: " + rectangle.perimeter());
Square square = new Square(4);
System.out.println("正方形的面积是: " + square.area());
System.out.println("正方形的周长是: " + square.perimeter());
}
}
代码运行结果
解析
这段代码是一个简单的面向对象的示例,用于演示抽象类和继承的概念。它定义了一个抽象类Shape
,并实现了几个具体的形状类,如Triangle
、Circle
、Rectangle
和Square
。
-
Shape
类是一个抽象类,它有两个私有成员变量length
和width
,以及一个常量PI
。它还定义了一个构造函数和两个抽象方法area()
和perimeter()
。这些抽象方法将在具体的形状类中被实现,用于计算相应形状的面积和周长。 -
Triangle
、Circle
、Rectangle
和Square
都是Shape
类的具体子类。它们分别实现了父类的抽象方法,计算了各自形状的面积和周长。 -
TestShape
类是用来测试以上定义的形状类的主类。它创建了几个形状对象,并调用其方法来计算并输出面积和周长。
通过这个示例,我们可以看到抽象类的作用和继承的使用方式,以及多态的体现。每个具体的形状类都实现了自己的计算逻辑,但它们都可以作为Shape
类型来使用,体现了多态性。
2、编写某销售公司雇员工资支付系统,这个公司有各种类型的雇员(Employee),不同的雇员按不同的方式支付工资:
经理(Manager):按年薪制领取工资,每年固定得到一份收入;
普通员工(Worker):按每月获得一份固定的工资;
销售员(Salesman):在基本工资基础上每月按销售件数获得提成;
(1)要求定义一个 abstract 类,类名为 Employee。Employee 抽象类有两个abstract方法:
public abstract void print();//打印个人信息如姓名、职位、工资信息等;
public abstract double earnings();//年收入计算
Manager、Worker 和 salesman 都是 Employee 的子类。并各自实现其print()和earnings()方法,给出各自信息和领取报酬的具体方式。
(2)有一个 Company 类,该类用 Employee 数组作为成员,假如公司里有1个经理(Manager)、5 个普通员工(Worker)、5 个 salesman。计算公司全年需要支付薪水总额。
Employee.java代码
package com.xianyu.abs;
public abstract class Employee {
protected String name;
public Employee(String name) {
this.name = name;
}
public abstract void print();
public abstract double earning();
}
Manager.java代码
package com.xianyu.abs;
public class Manager extends Employee{
private double annualSalary;
public Manager(String name,double annualSalary) {
super(name);
this.annualSalary =annualSalary;
}
@Override
public void print() {
System.out.println("My name is:"+name+",I am manager,"+
"my annualSalary is"+annualSalary);
}
@Override
public double earning() {
return annualSalary;
}
}
Worker.java代码
package com.xianyu.abs;
public class Worker extends Employee{
private double salary;
public Worker(String name,double salary) {
super(name);
this.salary=salary;
}
@Override
public void print() {
System.out.println("My name is "+name+",I am worker,"+
"my salary is:"+salary);
}
@Override
public double earning() {
return salary*12;
}
}
Employee.java代码
package com.xianyu.abs;
public class Saleman extends Employee{
private double baseSalary;
private int saleNum;
public Saleman(String name,double baseSalary,int saleNum) {
super(name);
this.baseSalary = baseSalary;
this.saleNum =saleNum;
}
@Override
public void print() {
System.out.println("My name is:"+name+
",I am saleman,my salary is:"+earning());
}
@Override
public double earning() {
return baseSalary*12+saleNum*1;
}
}
Company.java代码
package com.xianyu.abs;
public class Company {
public void print(Employee[] employees){
for (int i=0;i<employees.length; i++){
employees[i].print();
}
}
public double getTotalPay(Employee[] employees){
double totalPay = 0;
for (int i=0;i<employees.length; i++){
totalPay+=employees[i].earning();
}
return totalPay;
}
public static void main(String[] args){
//抽象类
Employee[] employees =new Employee[8];
employees[0] = new Manager("jack",100000000);
employees[1] =new Worker("w1",10000);
employees[2] =new Worker("w2",12000);
employees[3] =new Worker("w3",13000);
employees[4] =new Worker("w4",14000);
employees[5] =new Worker("w5",15000);
employees[6] = new Saleman("s6",5000,1000000);
employees[7] = new Saleman("s7",5000,1200000);
Company company =new Company();
//打印所有人信息
company.print(employees);
// 计算公司薪水总支出并输出
double totalPay = company.getTotalPay(employees);
System.out.println("公司总支出: " + totalPay);
}
}
代码运行结果
这段代码是一个简单的雇员工资支付系统的示例,其中定义了一个抽象类Employee
来表示不同类型的雇员,包括经理Manager
、工人Worker
和销售员Saleman
。每种类型的雇员都继承自Employee
类,并实现了抽象方法print()
和earning()
。
-
Employee
类是一个抽象类,包含了雇员的基本信息和抽象方法。其中的print()
方法用于打印雇员信息,earning()
方法用于计算雇员的收入。 -
Manager
、Worker
和Saleman
分别是不同类型的具体雇员类,它们继承自Employee
类并实现了父类的抽象方法。每个具体类包括特定的属性和方法来处理各自类型的雇员。 -
Company
类包含了两个方法,print(Employee[] employees)
用于打印所有雇员的信息,getTotalPay(Employee[] employees)
用于计算公司的总支出。它们遍历传入的雇员数组,分别调用每个雇员的print()
和earning()
方法来输出信息和计算总支出。
在main
方法中,示例创建了不同类型的雇员对象,并调用Company
类的方法来展示雇员信息和计算公司的总支出。通过这个示例,展示了抽象类、继承和多态的应用,以及如何使用类和方法来组织实现一个雇员工资系统。
(二)接口类的应用
1、编写两接口类 Music 和 Art,两类学生,一类 MusicStudent(音乐学院学生)具有Music 能力,并实现 Music 接口的方法,另一类 ArtStudent(艺术学院学生)具有Art 能力,并实现 Art 方法;Teacher 同时拥有 Music 和 Art 能力,并实现两接口的所有方法;如图所示
实例化一个音乐学院学生类,让她唱歌跳舞;
实例化一个艺术学院学生,让他画画写字;
实例化一个教师类,让他唱歌、跳舞、画画、写字;
Art.java代码
package com.xianyu.abs;
public interface Art {
void paint();
void write();
}
Music.java代码
package com.xianyu.abs;
public interface Music {
void sing();
void dance();
}
Student.java代码
package com.xianyu.abs;
public abstract class Student {
protected String name;
public Student(String name) {
this.name = name;
}
public void print() {
System.out.println("学生姓名: " + name);
}
}
MusicStudent.java代码
package com.xianyu.abs;
public class MusicStudent extends Student implements Music {
public MusicStudent(String name) {
super(name);
}
@Override
public void sing() {
System.out.println(name + " 唱歌");
}
@Override
public void dance() {
System.out.println(name + " 跳舞");
}
}
ArtStudent.java代码
package com.xianyu.abs;
public class ArtStudent extends Student implements Art {
public ArtStudent(String name) {
super(name);
}
@Override
public void paint() {
System.out.println(name + " 画画");
}
@Override
public void write() {
System.out.println(name + " 写作");
}
public void print() {
System.out.println("教师姓名: " + name);
}
}
Teacher.java代码
package com.xianyu.abs;
public class Teacher implements Music, Art {
private String name;
public Teacher(String name) {
this.name = name;
}
@Override
public void sing() {
System.out.println(name + " 唱歌");
}
@Override
public void dance() {
System.out.println(name + " 跳舞");
}
@Override
public void paint() {
System.out.println(name + " 画画");
}
@Override
public void write() {
System.out.println(name + " 写作");
}
public void print() {
System.out.println("教师姓名: " + name);
}
}
Main.java代码
package com.xianyu.abs;
public class Main {
public static void main(String[] args) {
// 实例化一个音乐学院学生类,让她唱歌跳舞
MusicStudent musicStudent = new MusicStudent("甲");
musicStudent.print();
musicStudent.sing();
musicStudent.dance();
// 实例化一个艺术学院学生,让他画画写字
ArtStudent artStudent = new ArtStudent("乙");
artStudent.print();
artStudent.paint();
artStudent.write();
// 实例化一个教师类,让他唱歌、跳舞、画画、写字
Teacher teacher = new Teacher("丙");
teacher.print();
teacher.sing();
teacher.dance();
teacher.paint();
teacher.write();
}
}
运行结果
代码解析
这段代码是一个简单的接口类的应用示例,其中定义了两个接口类Music
和Art
,以及两个具体的学生类MusicStudent
和ArtStudent
,还有一个教师类Teacher
。
-
Music
接口和Art
接口是两个接口类,分别定义了一些方法来表示音乐和艺术的能力。 -
Student
类是一个抽象类,包含了学生的基本信息和一个打印信息的方法print()
。 -
MusicStudent
类和ArtStudent
类分别是音乐学院学生和艺术学院学生的具体实现类,它们分别继承自Student
类,并实现了相应接口的方法。 -
Teacher
类是教师的具体实现类,它实现了Music
接口和Art
接口的方法,并定义了一个打印信息的方法print()
。 -
Main
类是主类,用于测试以上定义的接口类和实现类。它创建了一个音乐学院学生对象、一个艺术学院学生对象和一个教师对象,并调用它们的方法来展示其相应的能力和打印信息。
通过这个示例,展示了接口的定义和实现,以及多态的应用。学生类和教师类分别实现了相应接口的方法,可以灵活地扩展和组合不同的能力。
(三)面向接口的编程
接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这样原则,很多软件架构设计理论都提倡“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程降低程序的耦合。
(1)编写一款游戏,该游戏中出现的角色、物品都需显示在屏幕上。游戏采用面向接口方式编程:
定义一个GameObject接口,接口里面有一个抽象的draw方法;定义一个屏幕类Screen,有一display(GameObject go)方法调用接口里的draw()方法将游戏里的角色或物品显示在屏幕上;
定义多个角色和物品类,相应的实现自己的draw方法。接口定义好规范,将程序的使用者(Screen)和实现者(例如:Tank、Plane)分离,由此降低程序的耦合性。
GameObject.java代码
package com.xianyu.intf;
public interface GameObject {
public void draw();
}
Tank.java代码
package com.xianyu.intf;
public class Tank implements GameObject{
@Override
public void draw() {
System.out.println("draw an tank T32");
}
}
Game.java代码
package com.xianyu.intf;
public class Game {
public static void main(String[] args) {
Tank tank = new Tank();
Plane plane = new Plane();
Soldier soldier = new Soldier();
Screen screen = new Screen();
screen.draw(tank);
screen.draw(plane);
screen.draw(soldier);
}
}
Soldier.java代码
package com.xianyu.intf;
public class Soldier implements GameObject{
@Override
public void draw() {
System.out.println("draw a Soldier");
}
}
Screen.java代码
package com.xianyu.intf;
public class Screen {
//public void drawBullt();
//public void drawPlane();
//public void drawTank();
//使用接口
public void draw(GameObject go){
go.draw();
}
}
Plane.java代码
package com.xianyu.intf;
public class Plane implements GameObject{
@Override
public void draw() {
System.out.println("draw an plane 0");
}
}
运行结果
代码解析
这段代码是一个简单的游戏示例,采用面向接口编程的方式,其中定义了一个接口类GameObject
,以及几个实现了该接口的具体类Tank
、Soldier
和Plane
。
-
GameObject
接口定义了一个draw()
方法,用于在屏幕上绘制游戏中的角色和物品。 -
Tank
、Soldier
和Plane
都是实现了GameObject
接口的具体类,它们分别实现了draw()
方法来绘制不同的角色或物品。 -
Screen
类是用来显示游戏中的角色和物品的类。它定义了一个draw()
方法,接收一个GameObject
类型的参数,并调用其draw()
方法来显示。 -
Game
类是游戏的主类,它创建了一个Tank
对象、一个Plane
对象和一个Soldier
对象,并将它们传递给Screen
对象的draw()
方法来显示在屏幕上。
通过这个示例,展示了面向接口编程的特点和优势。通过定义一个公共的接口类GameObject
,不同的角色和物品都实现了该接口,并通过传递给Screen
类的draw()
方法来显示在屏幕上。这样,游戏中的角色和物品可以灵活地添加和扩展,而不需要修改Screen
类的代码。同时,通过接口的使用,实现了多态性,可以将不同的对象作为参数传递给相同的方法,实现统一的操作。
2.简单工厂模式
场景描述:假设程序中有个Computer类需要组合一个输出设备,现在有两个方案:(1)第一种方案:直接让Computer该类组合一个Printer属性;假设让Computer组合一个Printer属性,如果有一天系统需要重构,使用BetterPrinter来代替Printer,于是我们打开Computer类源代码进行修改。如果系统只有一个Computer类组合了Printer属性还好,如果系统中有个100个类组合了Printer属性,甚至1000个,10000个……将意味着要打开100个、1000个、100000……类进行修改,这是多么大的工程。(2)为了避免第一种方案中的问题,我们采用第二种方案:面向接口编程,首先创建一个接口Output.java,代码如下:
public interface Output {
int MAX_CACHE_LINE = 50;
void out();
void getData(String msg);
}
我们让Computer组合一个OutPut属性,将Computer类与Printer类完全分离。Computer对象实际组合的是Printer对象,还是BetterPrinter对象,对Computer而言是完全透明的,当Printer对象切换到BetterPrinter对象时,系统完全不受影响。创建Computer.java,代码如下:
public class Computer {
private Output out; // 组合一个Output属性
public Computer(Output out) {
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg) {
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print() {
out.out();
}
}
上面的Computer类已经完全与Printer类分离,只是与Output接口耦合。Computer不再负
6责创建Outer对象,系统提供一个Output工厂来负责生成Out对象。这个OutputFactory工厂代码OutputFactory.java如下:
public Output getOutput() {
// 下面两行代码用于控制系统到底使用Output的哪个实现类。
return new BetterPrinter();
//return new BetterPrinter();
}
public static void main(String[] args) {
OutputFactory of = new OutputFactory();
Computer c = new Computer(of.getOutput());
c.keyIn("家里蹲大学");
c.keyIn("姓名:jack");
c.keyIn("学号:2021");
c.keyIn("面向接口编程实验");
c.print();
}
}
该OutputFactory类中包含了一个getOutput方法,该方法负责创建并返回一个Output实例,具体创建哪个实现类的对象由该方法决定;如果系统需将Printer改为BetterPrinter实现类,只需要让BetterPrinter实现Output接口,并改变OutputFactory类中的getOut方法即可。
下面是BetterPrinter实现类的代码,BetterPrinter只是对原有的Printer进行简单修改,以模拟系统重构后的改进。代码BetterPrinter.java如下:
public class BetterPrinter implements Output {
private String[] printData = new String[MAX_CACHE_LINE * 2];// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out() {
// 只要还有作业,继续打印
while (dataNum > 0) {
System.out.println("高速打印机正在打印:" + printData[0]);// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData, 1, printData, 0, --dataNum);}
}
public void getData(String msg) {
if (dataNum >= MAX_CACHE_LINE * 2) {
System.out.println("输出队列已满,添加失败");
} else {
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
}
通过这中方式,我们把所有生产Output对象的逻辑集中到OutputFactory工厂类中管理,而所有需要使用Output对象的类只需要与Output接口耦合,而不是与具体的实现类耦合。即使系统中有很多类使用了Printer对象,只要OutputFactory类的getOutput方法来生成Output对象是BetterPrinter对象,则它们全部都会改为使用BetterPrinter对象,而所有程序无需修改,只需要修改OutputFactory工厂的getOutput的方法实现即可。将getOutput方法中的return new Printer()改成return new BetterPrinter();就可以改变系统的功能,而不需要更改太多,达到程序可重用性。
停留在世界边缘,与之惜别