NCHU-PTA航空货运管理系统blog

前言

关于题目的总结:

1.题目集8和9分别是三道题,分别考察继承与多态、容器类和类设计等方面的问题。

2.每个题目集的前两道题分别是对以前做过的基础题目的类设计进行重构,以实现继承与多态的技术性需求,或是使得程序可以对功能进行扩展。

3.两次题目集的最后一题,第一题是考察类设计,重点考核面向对象设计原则中的单一职责原则、里氏代换原则、开闭原则以及合成复用原则,第二次的题目则是针对不同货物费率方面的细化,使得不同的用户类型的每个订单的运费可以享受相应的折扣,跟贴近现实使用。

关于课程学习:

期中考试中考察了继承与多态问题方面,很遗憾第二道题我没能拿到满分,说明第八次题目我并没有完全吃透,没有抓住对一类问题的设计思路,没法活学活用,在考试后我开始做第九次题目,沉下心来学习继承与多态问题的设计思路,现在再回头看原先的那道题目,已经可以应对。

题目分析与心得改进

第八次题目集

7-1点线面问题重构(继承与多态)问题

在“点与线(类设计)”题目基础上,对题目的类设计进行重构,以实现继承与多态的技术性需求。

对题目中的点Point类和线Line类进行进一步抽象,定义一个两个类的共同父类Element(抽象类),将display()方法在该方法中进行声明(抽象方法),将Point类和Line类作为该类的子类。

再定义一个Element类的子类面Plane,该类只有一个私有属性颜色color,除了构造方法和属性的getter、setter方法外,display()方法用于输出面的颜色,输出格式如下:The Plane's color is:颜色

在主方法内,定义两个Point(线段的起点和终点)对象、一个Line对象和一个Plane对象,依次从键盘输入两个Point对象的起点、终点坐标和颜色值(Line对象和Plane对象颜色相同),然后定义一个Element类的引用,分别使用该引用调用以上四个对象的display()方法,从而实现多态特性。

题目重点分析

重构抽象类Element,在该父类中将display()抽象方法进行声明,通过Element类型的变量引用不同子类对象,实现统一调用display()方法,然后设计三个子类Point、Line、Plane分别对该抽象方法进行复写,各子类负责自身信息的格式化输出,从而实现多态特性。

以下为重要部分的代码

点击查看代码
abstract class Element {//重构抽象类
    public abstract void display();
}
class Point extends Element {//存储坐标并实现坐标格式化输出
    private double x;
    private double y;

    public Point (double x,double y){
        this.x=x;
        this.y=y;
    }
    public double getX (){return x;}
    public double getY (){return y;}
    @Override
    public void display(){
        System.out.printf("(%.2f,%.2f)\n",x,y);
    }
}
class Line extends Element{//计算线段长度并展示详细信息
    private Point point1;
    private Point point2;
    private String color;

    public Line(){ }
    public Line(Point p1,Point p2,String color){
        this.point1 = p1;
        this.point2 = p2;
        this.color = color;
    }

    public double getDistance(){
        double disx=point2.getX()-point1.getX();
        double disy=point2.getY()-point1.getY();
        return Math.sqrt(disx*disx + disy*disy);
    }
    @Override
    public void display(){
        System.out.println("The line's color is:"+color);
        System.out.println("The line's begin point's Coordinate is:");
        point1.display();
        System.out.println("The line's end point's Coordinate is:");
        point2.display();
        System.out.printf("The line's length is:%.2f\n", getDistance());
    }
}
class Plane extends Element {//仅存储颜色属性,展示平面颜色
    private String color;
    public Plane(String color) {this.color = color;}

    @Override
    public void display() {
        System.out.println("The Plane's color is:" + color);
    }
}

7-2雨刷程序功能扩展设计

该问题是在给定的汽车手动风挡玻璃雨刷程序基础上进行重构,目标是使程序能够扩展功能以适应不同类型的雨刷系统。

业务背景中介绍了基础手动雨刷系统的组成、控制杆档位、刻度盘刻度与雨刷速度的对应关系等,而 “高级” 雨刷系统在控制杆和刻度盘上有新增档位和刻度,原程序无法满足需求,故需重构。

重构要求程序能自动匹配不同雨刷系统类型,实现 “插拔式” 增减系统种类,需符合单一职责、开 - 闭等多项面向对象设计原则。作业内容包括在第一种雨刷系统程序基础上重构以实现扩展,提交可编译运行的 Java 程序。输入规范为两行数据,分别指定雨刷系统类型和操作序列,输出需按规定格式显示每次操作后的相关信息,同时要处理非法输入和越界操作等情况,设计需运用类设计、封装、继承、多态、接口与抽象类等知识。

题目重点分析

解题需重构雨刷程序,用抽象类来支持不同系统扩展,管理控制杆和刻度盘状态,以及处理输入输出及非法情况,这是我一开始失分的重要地区,没有注意到文档里对于不同的异常需要进行不同的处理,如第二行不合法输入则直接跳过,以及如果没有有效操作,则直接返回,整个过程中需遵循单一职责、开 - 闭等原则,确保代码可扩展而且层次清晰。
以下为完整代码

点击查看代码
import java.util.ArrayList;
import java.util.Scanner;
import java.util.List;

interface WiperSystem {
    int getMaxLever();
    int getMaxDial();
    int getSpeed(int leverPos, int dialPos);
    String getLeverName(int pos);
}

class Basic implements WiperSystem {
    public int getMaxLever() { return 4; }
    public int getMaxDial() { return 3; }

    public int getSpeed(int leverPos, int dialPos) {
        if (leverPos == 1) {
            return 0;
        }
        if (leverPos == 2) {
            int result;
            switch (dialPos) {
                case 1: result = 4; break;
                case 2: result = 6; break;
                case 3: result = 12; break;
                default: result = 0; break;
            }
            return result;
        }
        if (leverPos == 3) return 30;
        if (leverPos == 4) return 60;
        return 0;
    }

    @Override
    public String getLeverName(int pos) {
        String leverName;
        switch (pos) {
            case 1: leverName = "停止"; break;
            case 2: leverName = "间歇"; break;
            case 3: leverName = "低速"; break;
            case 4: leverName = "高速"; break;
            default: leverName = ""; break;
        }
        return leverName;
    }
}

class Advanced implements WiperSystem {
    public int getMaxLever() { return 5; }
    public int getMaxDial() { return 5; }

    public int getSpeed(int leverPos, int dialPos) {
        if (leverPos == 1) {
            return 0;
        }
        if (leverPos == 2) {
            int result;
            switch (dialPos) {
                case 1: result = 4; break;
                case 2: result = 6; break;
                case 3: result = 12; break;
                case 4: result = 15; break;
                case 5: result = 20; break;
                default: result = 0; break;
            }
            return result;
        }
        if (leverPos == 3) return 30;
        if (leverPos == 4) return 60;
        if (leverPos == 5) return 90;
        return 0;
    }

    @Override
    public String getLeverName(int pos) {
        String leverName;
        switch (pos) {
            case 1: leverName = "停止"; break;
            case 2: leverName = "间歇"; break;
            case 3: leverName = "低速"; break;
            case 4: leverName = "高速"; break;
            case 5: leverName = "超高速"; break;
            default: leverName = ""; break;
        }
        return leverName;
    }
}

class Brush {
    private int speed;
    public Brush() {
        speed = 0;
    }
    public int getSpeed() { return speed; }
    public void setSpeed(int speed) { this.speed = speed; }
}

class Lever {
    private int pos;
    private Lever lever;
    private Dial dial;
    private Brush brush;

    public Lever() { pos = 1; }

    public Lever getLever() { return lever; }
    public void setLever(Lever lever) { this.lever = lever; }
    public Brush getBrush() { return brush; }
    public Dial getDial() { return dial; }
    public void setBrush(Brush brush) { this.brush = brush; }
    public void setDial(Dial dial) { this.dial = dial; }

    public int getPos() { return pos; }

    // 限制控制杆位置不超过最大值
    public void levelUp(int max) {
        if (pos < max) pos++;
    }

    public void leverDown() {
        if (pos > 1) pos--;
    }
}

class Dial {
    private int pos;
    public Dial() { pos = 1; }
    public int getPos() { return pos; }

    // 限制拨盘位置不超过最大值
    public void dialUp(int max) {
        if (pos < max) pos++;
    }

    public void dialDown() {
        if (pos > 1) pos--;
    }
}

class Agent {
    private final Lever lever = new Lever();
    private final Dial dial = new Dial();
    private final Brush brush = new Brush();
    private final WiperSystem system;

    public Agent(WiperSystem system) {
        this.system = system;
    }

    public String handleOperation(int operation) {
        String type = "";
        switch (operation) {
            case 1:
                lever.levelUp(system.getMaxLever());
                type = "Lever up";
                break;
            case 2:
                lever.leverDown();
                type = "Lever down";
                break;
            case 3:
                dial.dialUp(system.getMaxDial());
                type = "Dial up";
                break;
            case 4:
                dial.dialDown();
                type = "Dial down";
                break;
        }

        brush.setSpeed(system.getSpeed(lever.getPos(), dial.getPos()));
        String result = type + "/" + system.getLeverName(lever.getPos()) + "/" + dial.getPos() + "/" + brush.getSpeed();
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 输入处理
        if (!scanner.hasNextInt()) {
            System.out.println("Wrong Format");
            return;
        }
        int type = scanner.nextInt();
        scanner.nextLine(); // 消耗换行符

        // 多态创建系统对象
        WiperSystem system;
        if (type == 1) {
            system = new Basic();
        } else if (type == 2) {
            system = new Advanced();
        } else {
            System.out.println("Wrong Format");
            return;
        }

        // 处理第二行输入
        List<Integer> ops = new ArrayList<>();
        if (scanner.hasNextLine()) {
            String secondLine = scanner.nextLine().trim();
            //第二行不合法输入直接跳过
            if (secondLine.isEmpty()) {
                return;
            }
            String[] inputs = secondLine.split("\\s+");
            for (String input : inputs) {
                if (input.equals("0")) break;
                try {
                    int op = Integer.parseInt(input);
                    if (op >= 1 && op <= 4) ops.add(op);
                } catch (NumberFormatException ignored) {}
            }
        }

        // 如果没有有效操作,则直接返回
        if (ops.isEmpty()) {
            return;
        }

        // 执行操作并输出
        Agent agent = new Agent(system);
        for (int op : ops) {
            System.out.println(agent.handleOperation(op));
        }
    }
}

7-3航空货运管理系统(类设计)问题

某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包
括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。
本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:
一、计费重量的确定
空运以实际重量(Gross Weight)和体积重量(Volume Weight)中的较
高者作为计费重量。
计算公式:
体积重量(kg) = 货物体积(长×宽×高,单位:厘米)÷ 6000
示例:
若货物实际重量为 80kg,体积为 120cm×80cm×60cm,则:
体积重量 = (120×80×60) ÷ 6000 = 96kg
计费重量取 96kg(因 96kg > 80kg)。
二、基础运费计算
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如
CNY 30/kg)。本次作业费率采用分段计算方式:

公式:基础运费 = 计费重量 × 费率
三、题目说明
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场所在城市,航班降落机场所在城市,航班
日期,航班最大载重量)
客户填写货运订单并进行支付,需要提供如下信息:
 客户信息(姓名,电话号码等)
 货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
 运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
 支付方式(支付宝支付、微信支付)

题目分析

本次题目较为复杂,我选择现对其进行模块化处理,将系统拆分为输入处理(InputUtils)、业务逻辑(实体类)、控制输出(OrderController)、错误处理(Report)等模块,类设计方面:

1.主类(Main)
协调各模块,按顺序调用输入、业务逻辑和输出功能。
处理用户输入的客户信息、货物详情、航班信息等数据。
验证航班载重是否超限,生成订单详情并打印报告。

通过工具类InputUtils集中处理输入,避免主类逻辑臃肿。
使用OrderController整合数据并输出结果,符合单一职责原则。

2.工具类(InputUtils)
封装所有输入逻辑,提供统一的输入处理接口(如inputClientInfo、loadGoodsDetails)。
解析用户输入的原始数据,转换为对应的对象(如Client、Flight)。

3.基础类(Person)
作为Client、Shipper、Receiver的父类,封装人员的公共属性(姓名、电话、地址)。
提供属性的访问方法,实现代码复用。

Client继承Person并新增客户编号(clientId)。
Shipper和Receiver直接继承Person。

4.业务实体类
-Flight类:
存储航班信息,提供载重查询接口。
-Order类:
记录订单基础信息(订单号、日期)。
-GoodsItem类:
计算货物的计费重量(体积重量与实际重量取较大值)、运费费率和运费金额。
包含多个业务方法(computeVolumeWeight、calculateFreightCost),封装物流计费逻辑。
-OrderSummary类:
汇总订单中的货物信息,计算总重量和总费用,提供数据集合的统一访问接口。

5.控制与报告类
-OrderController类:
作为核心控制器,整合所有业务对象(客户、航班、订单等)。
负责打印订单摘要和货物明细,分离数据处理与输出逻辑。
-Report类:
单独处理错误报告,体现单一职责原则。

6.支付模块
定义PaymentMethod接口规范支付方式

源码静态分析

类型 数据
行数 314
语句数 175
分支语句占比 4.6%
方法调用语句 56
注释行占比 6.7%
类和接口数量 6
每个类的平均方法数 13
每个方法平均语句 0.63
最大块深度 3
平均块深度 0.95
平均复杂度 1.33

反思与心得

1.支付方面虽然我定义PaymentMethod接口,但是钻了空子,只可实现WeChatPayment也就是微信支付。
没能将支付宝支付加入其中,但是仍有预留扩展能力,未来可修改新增其他支付方式(如支付宝)而不修改现有代码,符合开闭原则。

// 支付方式接口
interface PaymentMethod {
    String getPaymentType();
}

// 微信支付实现
class WeChatPayment implements PaymentMethod {
    @Override
    public String getPaymentType() {
        return "微信支付";
    }
}//未继续实现支付宝支付

2.在得到满分之前,第三个异常测试测试点一直不过,经过不断仔细阅读题目文件发现未对订单总重量(orderSummary.getTotCWeight())与航班最大载重(flightInfo.getMaxLoad())进行比较。

若货物总重量超过航班载重,程序会直接跳过错误提示,继续执行订单打印流程,导致逻辑错误。

所以我在最后添加了一个report类,当总重量超过航班载重时,会调用Report类打印错误信息,并提前终止程序,确保逻辑正确性。

public class Main {
    public static void main(String[] args) {
        Scanner inputScanner = new Scanner(System.in);

        Client client = InputUtils.inputClientInfo(inputScanner);
        int goodsCount = Integer.parseInt(inputScanner.nextLine().trim());
        List<GoodsItem> goodsList = InputUtils.loadGoodsDetails(inputScanner, goodsCount);
        Flight flightInfo = InputUtils.readFlightDetails(inputScanner);
        Order orderInfo = InputUtils.getOrderInfo(inputScanner);
        Shipper senderInfo = (Shipper) InputUtils.createPerson(inputScanner, "sender");
        Receiver receiverInfo = (Receiver) InputUtils.createPerson(inputScanner, "receiver");
        PaymentMethod payment = new WeChatPayment();
        OrderSummary orderSummary = new OrderSummary(goodsList);

        // 新增的部分,检查航班载重
        double totalWeight = orderSummary.getTotCWeight();
        if (totalWeight > flightInfo.getMaxLoad()) {
            Report report = new Report();
            report.printError(flightInfo.getFlightNumber());
            return;// 终止程序,避免生成无效订单
        }

        OrderController controller = new OrderController();
        controller.setClient(client);
        controller.setFlight(flightInfo);
        controller.setOrder(orderInfo);
        controller.setOrderSummary(orderSummary);
        controller.setSender(senderInfo);
        controller.setReceiver(receiverInfo);
        controller.setPaymentMethod(payment);

        // 打印报告
        controller.printOrderSummary();
        controller.printGoodsDetails();
        inputScanner.close();
    }
}
// 报告类
class Report {
    public void printError(String flightNumber) {
        System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n", flightNumber);
    }
}

3.静态分析显示6.7% 的行包含注释,但是关键业务逻辑缺乏注释,可能影响后续维护。

改进心得:
在 GoodsItem 的计费方法中添加注释,解释备注体积重量计算公式和费率规则。
在 OrderSummary 的汇总方法中注释计算逻辑。

第九次题目集

7-1 魔方问题

问题描述:本问题中的魔方有两种,一种是正方体魔方,一种是正三棱锥魔方,其中,正方体或正三棱锥魔方是由单元正方体或正三棱锥组成,单元正方体或正三棱锥的个数由阶数(即层数)决定,即魔方边长=阶数*单元边长。

利用“立体图形”问题源码,实现如下功能:

魔方有三个属性:颜色,阶数,类型(正方体魔方、正三棱锥魔方),程序要求输出魔方的颜色、表面积和体积。参考设计类图如下所示:

题目重点分析

这是一道很常规的对于继承与多态的考察的题目,相对于之前“立体图形”问题的源码,通过抽象方法 getArea() 和 getVolume(),使得不同类型的魔方和实体可以根据自身特性提供不同的实现。

设计RubikCube 的子类:SquareCube(方形魔方)、RegularPyramidCube(正四面体魔方)
Solid 的子类:Cube(立方体)、RegularPyramid(正四面体)

下面是详细代码

点击查看代码
import java.util.Scanner;

abstract class RubikCube{
    protected String color;
    protected int layer;
    protected Solid solid;

    public RubikCube(){}
    public RubikCube(String color,int layer,Solid solid){
        this.color=color;
        this.layer=layer;
        this.solid=solid;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getLayer() {
        return layer;
    }

    public void setLayer(int layer) {
        this.layer = layer;
    }

    public Solid getSolid() {
        return solid;
    }

    public void setSolid(Solid solid) {
        this.solid = solid;
    }
    public abstract double getArea();
    public abstract double getVolume();
}

class SquareCube extends RubikCube{
    public SquareCube(){}
    public SquareCube(String color,int layer,Solid solid){
        super(color,layer,solid);
    }
    @Override
    public double getArea(){
        double actSide=layer*solid.getSide();
        return solid.getArea()*layer*layer;
    }
    @Override
    public double getVolume(){
        double actSide=layer * solid.getSide();
        return  Math.pow(layer * solid.getSide(), 3);
    }
}

class RegularPyramidCube extends RubikCube{
    public RegularPyramidCube(){}
    public RegularPyramidCube(String color,int layer,Solid solid){
        super(color,layer,solid);
    }

    @Override
    public double getArea() {
        double actSide = layer * solid.getSide();
        return solid.getArea() * layer * layer;
    }

    @Override
    public double getVolume() {
        double actSide = layer * solid.getSide();
        return solid.getVolume() * layer * layer*layer;
    }
}
abstract class Solid{
    protected double side;
    public Solid(){}
    public Solid(double side){
        this.side=side;
    }

    public double getSide() {
        return side;
    }

    public void setSide(double side) {
        this.side = side;
    }
    public abstract double getArea();
    public abstract double getVolume();
}
class Cube extends Solid{
    public Cube(){}
    public Cube(double side){
        super(side);
    }

    @Override
    public double getArea() {
        return 6*side*side;
    }

    @Override
    public double getVolume() {
        return side*side*side;
    }
}
class RegularPyramid extends Solid{
    public RegularPyramid(){}
    public RegularPyramid(double side){
        super(side);
    }

    @Override
    public double getArea() {
        return Math.sqrt(3) * side * side;
    }

    @Override
    public double getVolume() {
        return Math.sqrt(2) / 12 * side * side * side;
    }
}
public class Main {
    public static void main(String[]args){
        Scanner input=new Scanner(System.in);
        String color = input.next();
        int layer = input.nextInt();
        double side = input.nextDouble();

        RubikCube cube1 = new SquareCube(color, layer,new Cube(side));

        color = input.next();
        layer = input.nextInt();
        side = input.nextDouble();

        RubikCube cube2 = new RegularPyramidCube(color, layer,new RegularPyramid(side));
        display(cube1);
        display(cube2);
    }
    public static void display(RubikCube cube) {
        System.out.println(cube.getColor());
        System.out.printf("%.2f\n", cube.getArea());
        System.out.printf("%.2f\n", cube.getVolume());
    }
}

7-2 点线面问题再重构(容器类)问题

在“点与线(继承与多态)”题目基础上,对题目的类设计进行重构,增加容器类保存点、线、面对象,并对该容器进行相应增、删、遍历操作。

在原有类设计的基础上,增加一个GeometryObject容器类,其属性为ArrayList类型的对象(若不了解泛型,可以不使用
增加该类的add()方法及remove(int index)方法,其功能分别为向容器中增加对象及删除第index - 1(ArrayList中index>=0)个对象
在主方法中,用户循环输入要进行的操作(choice∈[0,4]),其含义如下:
1:向容器中增加Point对象
2:向容器中增加Line对象
3:向容器中增加Plane对象
4:删除容器中第index - 1个数据,若index数据非法,则无视此操作
0:输入结束

输入结束后,按容器中的对象顺序分别调用每个对象的display()方法进行输出。
类图如下所示:

问题重点分析

这道题还是考察面向对象的抽象类、继承和多态特性,设计Element 抽象类来作为所有几何元素的基类,并且定义了 display() 抽象方法,强制子类实现该方法。

设计Element 的子类:
Point:表示二维平面上的点,包含 x 和 y 坐标。
Line:表示线段,由两个点和颜色属性组成。
Plane:表示平面,仅包含颜色属性。

组合类设计:
GeometryObject 类:管理多个几何元素,提供添加和删除元素的方法。

通过抽象方法 display(),不同类型的几何元素可以根据自身特性提供不同的显示实现,主程序中通过 element.display() 调用来实现程序的多态性。

下面为完整的代码

点击查看代码
import java.util.Scanner;
import java.util.ArrayList;

abstract class Element{
    //将display方法定义为抽象方法,强制子类实现
    public abstract void display();
}

class Line extends Element{
    private Point point1;
    private Point point2;
    private String color;

    public Line(){}
    public Line(Point p1,Point p2,String color){
        this.point1=p1;
        this.point2=p2;
        this.color=color;
    }

    public Point getPoint2() {
        return point2;
    }

    public void setPoint2(Point point2) {
        this.point2 = point2;
    }

    public Point getPoint1() {
        return point1;
    }

    public void setPoint1(Point point1) {
        this.point1 = point1;
    }

    public double getDistance(){
        double dx = point2.getX() - point1.getX();
        double dy = point2.getY() - point1.getY();
        double d= Math.sqrt(dx * dx + dy * dy);
        return d;
    }

    @Override
    public void display() {
        System.out.println("The line's color is:" + color);
        System.out.println("The line's begin point's Coordinate is:");
        point1.display();
        System.out.println("The line's end point's Coordinate is:");
        point2.display();
        System.out.printf("The line's length is:%.2f\n", getDistance());
    }
}

class Plane extends Element{
    private String color;

    public Plane(){}
    public Plane(String color){
        this.color=color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public void display(){
        System.out.println("The Plane's color is:" + color);
    }
}

class Point extends Element{
    private double x;
    private double y;

    public Point(){}
    public Point(double x,double y){
        this.x=x;
        this.y=y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    @Override
    public void display() {
        System.out.printf("(%.2f,%.2f)\n",x,y);
    }
}

class GeometryObject{
    private ArrayList<Element> list =new ArrayList<>();

    public GeometryObject(){}
    public void add(Element element){
        list.add(element);
    }

    public void remove(int index){
        if(index>=0&&index<list.size()){
            list.remove(index);
        }
    }

    public ArrayList<Element> getList(){
        return list;
    }
}
public class Main {
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        GeometryObject geometryObject = new GeometryObject();

        int choice;
        choice=scanner.nextInt();
        while(choice!=0){
            switch(choice){
                case 1:
                    double x=scanner.nextDouble();
                    double y=scanner.nextDouble();
                    Point point=new Point(x,y);
                    geometryObject.add(point);
                    break;
                case 2:
                    double x1 = scanner.nextDouble();
                    double y1 = scanner.nextDouble();
                    double x2 = scanner.nextDouble();
                    double y2 = scanner.nextDouble();
                    String color1 = scanner.next();
                    Point p1 = new Point(x1, y1);
                    Point p2 = new Point(x2, y2);
                    Line line = new Line(p1, p2, color1);
                    geometryObject.add(line);
                    break;
                case 3:
                    String color2 = scanner.next();
                    Plane plane = new Plane(color2);
                    geometryObject.add(plane);
                    break;
                case 4:
                    int index = scanner.nextInt();
                    geometryObject.remove(index - 1);
                    break;
            }
            choice=scanner.nextInt();
        }
        for(Element element:geometryObject.getList()){
            element.display();
        }
    }
}

7-3 航空货运管理系统(继承与多态)问题

在上一次题目集的基础上添加

并添加现金支付方法,要求可扩展的类:用户、支付方式、货物

问题分析

第二次的题目则是针对不同货物费率方面的细化,使得不同的用户类型的每个订单的运费可以享受相应的折扣,采用统一的分段阶梯费率,新增 “现金支付” 选项,新增依赖倒转原则,需确保代码依赖抽象而非具体实现,例如通过接口定义用户类型、货物类型等,总体相比于第一道题变化不大。

关于类设计:

1.核心业务类
Client(客户类)
属性:客户编号、姓名、电话、地址、客户类型(企业 / 个人)。
方法:根据客户类型计算折扣率(企业 8 折,个人 9 折)。
作用:区分客户类型以应用不同折扣,体现业务规则。
GoodsItem(货物类)
属性:货物编号、名称、尺寸、实际重量、货物类型。
方法:计算体积重量、计费重量、根据货物类型和重量计算运费费率、总运费。
作用:封装货物的计费逻辑,不同货物类型对应不同费率规则,体现多态性。
Flight(航班类)
属性:航班号、起降机场、日期、最大载重。
方法:获取航班号和最大载重。
作用:用于校验订单总重量是否超过航班载重限制。
Order(订单类)
属性:订单号、日期。
作用:存储订单基础信息,结构简单。
OrderSummary(订单详情类)
属性:货物列表。
方法:计算总计费重量、总费用。
作用:聚合货物信息,提供订单统计功能。

2 角色类
Person(人员基类)
属性:姓名、电话、地址。
子类:Shipper、Receiver,通过复用基类减少代码冗余。

3.支付模块
PaymentMethod(支付接口)
方法:getPaymentType()获取支付类型。
实现类:WeChatPayment、AliPayPayment、CashPayment,通过接口解耦支付方式,方便扩展。

4.工具类与控制类
InputUtils(输入工具类)
作用:集中处理用户输入逻辑,避免主程序臃肿,符合单一职责原则。
OrderController(控制类)
作用:协调各业务类,填充数据并生成报告,是程序的核心调度层。

5.辅助类
Report(报告类)
作用:处理异常情况(如航班载重超限),输出错误信息。

以下是我设计的类图

源码静态分析

类型 数据
行数 382
语句数 86
分支语句占比 14%
方法调用语句 45
注释行占比 5.8%
类和接口数量 4
每个类的平均方法数 5
每个方法平均语句 0.63
最大块深度 4
平均块深度 1.83
平均复杂度 1.86

反思与心得

1.由静态分析数据可看出方法调用语句较多,有 45 条方法调用,可能导致代码的调用关系复杂,不利于理清程序执行流程。可对一些功能相近的方法进行整合,减少不必要的方法调用。

如可以优化InputUtils类包含的多个相似的输入方法:

// 工具类,集中处理输入逻辑 
class InputUtils {
    // 通用输入处理方法,减少重复代码
    private static String getInput(Scanner scanner, String prompt) {
        System.out.print(prompt);
        return scanner.nextLine().trim();
    }
    
    // 优化后的客户信息输入方法
    public static Client inputClientInfo(Scanner scanner) {
        String clientType = getInput(scanner, "请输入客户类型: ");
        String clientId = getInput(scanner, "请输入客户ID: ");
        String name = getInput(scanner, "请输入客户姓名: ");
        String telephone = getInput(scanner, "请输入客户电话: ");
        String address = getInput(scanner, "请输入客户地址: ");
        return new Client(clientId, name, telephone, address, clientType);
    }
    
    // 优化后的货物详情加载方法
    public static List<GoodsItem> loadGoodsDetails(Scanner scanner, int count, String goodsType) {
        List<GoodsItem> items = new ArrayList<>();
        System.out.printf("开始录入%d件%s类型货物详情:\n", count, goodsType);
        
        for (int i = 0; i < count; i++) {
            System.out.printf("录入第%d件货物信息:\n", i + 1);
            String goodsId = getInput(scanner, "  货物ID: ");
            String name = getInput(scanner, "  货物名称: ");
            int width = Integer.parseInt(getInput(scanner, "  宽度(cm): "));
            int length = Integer.parseInt(getInput(scanner, "  长度(cm): "));
            int height = Integer.parseInt(getInput(scanner, "  高度(cm): "));
            double realWeight = Double.parseDouble(getInput(scanner, "  实际重量(kg): "));
            items.add(new GoodsItem(goodsId, name, width, length, height, realWeight, goodsType));
        }
        return items;
    }
}

2.图中可知注释行占比低,只有 5.8% ,代码中很多关键逻辑部分缺乏注释。

比如InputUtils类中各个方法对输入的具体要求、GoodsItem类中计费重量和运费计算逻辑等,应适当增加注释说明关键功能和算法。

总结

收获

在这次作业的细化改进过程中,相比于上一次电梯问题,我对问题的设计思路更加清晰,对于类设计已经更加得心应手,随着对继承与多态方面的学习,对于增强代码的可复用性和可维护性也有了一些心得,不断学习中也获得了很多宝贵的经验。

在多次报错的经验中,我也懂得了一定一定,要仔细看清要求(没错,就是7-3,,,在第三个测试点卡了好久,,,)不要钻没用的牛角尖,减少犯错成本。

建议

对于我自己的建议还是多加练习,缩减对问题思考和解决的时间,就比如应对限时编程考试时,可以减少在简单问题上用的时间,为更复杂的问题争取时间,提高思维灵活度。

posted @ 2025-05-22 21:34  miky默  阅读(14)  评论(0)    收藏  举报