代码仅供参考,并提供程序整体思路的基础解析,请勿直接引用。可通过咸鱼AI进行代码重构降重以及获取更详细的代码解析!

一、目的

通过编程和上机实验理解 Java中的继承和多态,掌握抽象类,以及如何实现抽象方法,
并创建对象,掌握Java接口及其应用

二、内容

(一)抽象类的应用

1、定义一个形状的抽象类(Shape),设计“矩形”、“三角形(等腰)”、“正方形”,“圆”等

类,并实现Shape的抽象方法,分别计算相应形状的面积和周长
java 抽象类与接口 案例及解析


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());
    }
}



代码运行结果
java 抽象类与接口 案例及解析



解析
这段代码是一个简单的面向对象的示例,用于演示抽象类和继承的概念。它定义了一个抽象类Shape,并实现了几个具体的形状类,如TriangleCircleRectangleSquare

  1. Shape类是一个抽象类,它有两个私有成员变量lengthwidth,以及一个常量PI。它还定义了一个构造函数和两个抽象方法area()perimeter()。这些抽象方法将在具体的形状类中被实现,用于计算相应形状的面积和周长。

  2. TriangleCircleRectangleSquare都是Shape类的具体子类。它们分别实现了父类的抽象方法,计算了各自形状的面积和周长。

  3. TestShape类是用来测试以上定义的形状类的主类。它创建了几个形状对象,并调用其方法来计算并输出面积和周长。

通过这个示例,我们可以看到抽象类的作用和继承的使用方式,以及多态的体现。每个具体的形状类都实现了自己的计算逻辑,但它们都可以作为Shape类型来使用,体现了多态性。



2、编写某销售公司雇员工资支付系统,这个公司有各种类型的雇员(Employee),不同的雇员按不同的方式支付工资:

经理(Manager):按年薪制领取工资,每年固定得到一份收入;
普通员工(Worker):按每月获得一份固定的工资;
销售员(Salesman):在基本工资基础上每月按销售件数获得提成;
(1)要求定义一个 abstract 类,类名为 Employee。Employee 抽象类有两个abstract方法:
public abstract void print();//打印个人信息如姓名、职位、工资信息等;
public abstract double earnings();//年收入计算
java 抽象类与接口 案例及解析


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);

    }
}



代码运行结果
java 抽象类与接口 案例及解析



这段代码是一个简单的雇员工资支付系统的示例,其中定义了一个抽象类Employee来表示不同类型的雇员,包括经理Manager、工人Worker和销售员Saleman。每种类型的雇员都继承自Employee类,并实现了抽象方法print()earning()

  1. Employee类是一个抽象类,包含了雇员的基本信息和抽象方法。其中的print()方法用于打印雇员信息,earning()方法用于计算雇员的收入。

  2. ManagerWorkerSaleman分别是不同类型的具体雇员类,它们继承自Employee类并实现了父类的抽象方法。每个具体类包括特定的属性和方法来处理各自类型的雇员。

  3. Company类包含了两个方法,print(Employee[] employees)用于打印所有雇员的信息,getTotalPay(Employee[] employees)用于计算公司的总支出。它们遍历传入的雇员数组,分别调用每个雇员的print()earning()方法来输出信息和计算总支出。

main方法中,示例创建了不同类型的雇员对象,并调用Company类的方法来展示雇员信息和计算公司的总支出。通过这个示例,展示了抽象类、继承和多态的应用,以及如何使用类和方法来组织实现一个雇员工资系统。



(二)接口类的应用

1、编写两接口类 Music 和 Art,两类学生,一类 MusicStudent(音乐学院学生)具有Music 能力,并实现 Music 接口的方法,另一类 ArtStudent(艺术学院学生)具有Art 能力,并实现 Art 方法;Teacher 同时拥有 Music 和 Art 能力,并实现两接口的所有方法;如图所示

java 抽象类与接口 案例及解析
java 抽象类与接口 案例及解析


实例化一个音乐学院学生类,让她唱歌跳舞;
实例化一个艺术学院学生,让他画画写字;
实例化一个教师类,让他唱歌、跳舞、画画、写字;


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();
    }
}




运行结果
java 抽象类与接口 案例及解析



代码解析
这段代码是一个简单的接口类的应用示例,其中定义了两个接口类MusicArt,以及两个具体的学生类MusicStudentArtStudent,还有一个教师类Teacher

  1. Music接口和Art接口是两个接口类,分别定义了一些方法来表示音乐和艺术的能力。

  2. Student类是一个抽象类,包含了学生的基本信息和一个打印信息的方法print()

  3. MusicStudent类和ArtStudent类分别是音乐学院学生和艺术学院学生的具体实现类,它们分别继承自Student类,并实现了相应接口的方法。

  4. Teacher类是教师的具体实现类,它实现了Music接口和Art接口的方法,并定义了一个打印信息的方法print()

  5. Main类是主类,用于测试以上定义的接口类和实现类。它创建了一个音乐学院学生对象、一个艺术学院学生对象和一个教师对象,并调用它们的方法来展示其相应的能力和打印信息。

通过这个示例,展示了接口的定义和实现,以及多态的应用。学生类和教师类分别实现了相应接口的方法,可以灵活地扩展和组合不同的能力。



(三)面向接口的编程
接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这样原则,很多软件架构设计理论都提倡“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程降低程序的耦合。

(1)编写一款游戏,该游戏中出现的角色、物品都需显示在屏幕上。游戏采用面向接口方式编程:

定义一个GameObject接口,接口里面有一个抽象的draw方法;定义一个屏幕类Screen,有一display(GameObject go)方法调用接口里的draw()方法将游戏里的角色或物品显示在屏幕上;
定义多个角色和物品类,相应的实现自己的draw方法。接口定义好规范,将程序的使用者(Screen)和实现者(例如:Tank、Plane)分离,由此降低程序的耦合性。
java 抽象类与接口 案例及解析


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");
    }
}





运行结果
java 抽象类与接口 案例及解析



代码解析
这段代码是一个简单的游戏示例,采用面向接口编程的方式,其中定义了一个接口类GameObject,以及几个实现了该接口的具体类TankSoldierPlane

  1. GameObject接口定义了一个draw()方法,用于在屏幕上绘制游戏中的角色和物品。

  2. TankSoldierPlane都是实现了GameObject接口的具体类,它们分别实现了draw()方法来绘制不同的角色或物品。

  3. Screen类是用来显示游戏中的角色和物品的类。它定义了一个draw()方法,接收一个GameObject类型的参数,并调用其draw()方法来显示。

  4. 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();就可以改变系统的功能,而不需要更改太多,达到程序可重用性。