bzzd0704

导航

云散月明谁点缀,天容海色本澄清-第二次blog作业

云散月明谁点缀,天容海色本澄清--第二次blog作业🔭

前言:

又到了blog的总结时间,我想,也许对于我们普通的学生来说,初学Java多态时,总觉得那些父类引用指向子类对象的代码,像一场晦涩的哑谜。编译器沉默地注视着我手忙脚乱地调试,而IDE里鲜红的报错提示像午夜的路灯,照着我这个迷途的初学者。 记得某个调试到凌晨的夜晚,当我终于让重写的方法正确调用时,屏幕上的输出忽然变得清晰起来,仿佛拨开了层层代码的迷雾。那一刻,我想起那句"云散月明谁点缀"——原来晦涩难懂的语法规则背后,藏着的是一轮皎洁的月光,只待我耐心拨开浮云,便能看见它的温柔照耀。 现在想来,编程路上的困惑,不过是成长必经的夜色。而每一次顿悟,都是月光终于穿透云层,洒在键盘上的时刻,那是我才发现,原来--天容海色本澄清🌙

 

 本次blog我们分成三个题目的分类讨论,先来看第一个,点线面问题的两次重构:

初见这道题时,我还是懵懂的,不了解多态,继承的含义,看着题目给的三个element分别使用了三次,不免产生了深深的疑惑,于是便去仔细了解了一下继承/多态:

 

有一位同学给我举了一个例子,我觉得非常生动:

继承:白嫖父类的代码,还能自己加功能或改功能。

class 老爸的支付宝 {

void 扫码付款()

{ System.out.println("付款成功!");

} }

class 我的支付宝 extends 老爸的支付宝

{ // extends 就是继承! void 发红包()

{ System.out.println("红包已塞钱!");

} // 新增功能 @Override void 扫码付款()

{ // 改进老爸的旧功能 System.out.println("请先按指纹~");

super.扫码付款(); // 调老爸原来的方法

} }

(在这里用父类子类就很生动形象了😁)

 

那么多态呢?

 

多态:同一个方法名,干不同的事
多态就像「同样的按钮,按出不同效果」

多态和继承就像"遥控器"和"电器"
万能遥控器(Element类),它可以控制各种电器。所有电器都必须有"开/关"功能(display()方法)。
现在有几种电器:

电灯(Point 类):按开关会亮灯 💡
空调(Line 类):按开关会吹冷风 ❄️
电视(Plane 类):按开关会播放节目 📺

 

多态:父类引用统一调用,但实际执行看子类具体实现;

 

那么在结合第一次代码的基础上

我们进行一个类图的大致分析:

 

在这个程序里,Element就是我的魔法棒:

指向Point时,display()变成坐标打印机
指向Line时,display()化身线段测量仪
指向Plane时,display()又成了颜色播报员

Element接口就是我和三个类之间的秘密暗号,不管它们内部怎么折腾,只要遵守display()这个约定,我就能用统一的方式唤醒它们的不同能力😁。

 

在知道这些核心内容后,我们就能够成功写出这道题的代码:

 

点我折叠哦^-^
 
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        Point p1 = new Point(sc.nextDouble(), sc.nextDouble());
        Point p2 = new Point(sc.nextDouble(), sc.nextDouble());
        sc.nextLine();
        String color = sc.nextLine();
        
        
        if (isValid(p1,p2)) {
            Line l = new Line(p1, p2, color);
            Plane plane = new Plane(color);
            
            Element element;
            
            element = p1;
            element.display();
            
            element = p2;
            element.display();
            
            element = l;
            element.display();
            
            element = plane;
            element.display();
        } else {
            System.out.println("Wrong Format");
        }
    }
    
    public static boolean isValid(Point p1, Point p2) {
        return (p1.getX() > 0 && p1.getX() <= 200) && 
               (p2.getX() > 0 && p2.getX() <= 200) && 
               (p1.getY() > 0 && p1.getY() <= 200) && 
               (p2.getY() > 0 && p2.getY() <= 200);
    }
}

/*学渣的挣扎与理解:多态和继承就像"遥控器"和"电器"
万能遥控器(Element类),它可以控制各种电器。所有电器都必须有"开/关"功能(display()方法)。
现在有几种电器:

电灯(Point类)
空调(Line类)
电视(Plane类)*/

abstract class Element {
 {
    public abstract void display();
}

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 void setX(double x) {
        this.x = x;
    }
    
    public double getY() {
        return y;
    }
    
    public void setY(double y) {
        this.y = 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 point1, Point point2, String color) {
        this.point1 = point1;
        this.point2 = point2;
        this.color = color;
    }
    
    public Point getPoint1() {
        return point1;
    }
    
    public void setPoint1(Point point1) {
        this.point1 = point1;
    }
    
    public Point getPoint2() {
        return point2;
    }
    
    public void setPoint2(Point point2) {
        this.point2 = point2;
    }
    
    public String getColor() {
        return color;
    }
    
    public void setColor(String color) {
        this.color = color;
    }
    
    public double getDistance() {
        double dx = point2.getX() - point1.getX();
        double dy = point2.getY() - point1.getY();
        return Math.sqrt(dx * dx + dy * dy);
    }
    
    @Override
    public void display() {
        System.out.printf("The line's color is:%s\n", 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.printf("The Plane's color is:%s\n", color);
    }
}

 

 

 

那我们来看第二个部分--魔方🗜

 

第一阶段:初读题目(这都是啥跟啥)
第一次看到这个题目时,我的表情是这样的:😳

类太多:Solid、RubikCube、SquareCube、RegularPyramidCube...类名都好长,而且我还看不懂什么意思!!!!
关系复杂:既有继承又有组合,还有抽象类
公式劝退:看到√3、√2这些数学公式直接头大
内心OS:"这到底是编程题还是数学题啊?"

第二阶段:尝试理解类图吧

 

(地铁,老人,手机..)

 

最让我弄不懂的,还是这个solid类到底是干啥的,觉得这个类很多余:"直接写Cube和Pyramid不就行了吗?"
后来发现它的精妙之处:


相当于制定了一套"几何体标准"
任何几何体想要加入这个体系,必须:
要有边长(side属性)
能计算表面积和体积(实现两个抽象方法)

以规范的格式规定我必须去计算每一个小立体的表面积,体积;

欸,那为什么RubikCube会继承于solid类呢?看着旁边另外两个继承,我似乎一下子醍醐灌顶,这道题的思路大抵就是:

先用Solid规范每一个小的立体,然后传给"魔方类"

再根据魔方类为主类,去重新计算正方体和三棱锥的相关信息,就像是一层层拼起来,Solid和RubikCube更像是,监督?

简直是层层递进,妙,实在是妙啊!

继承的优点在这个程序中体现
代码复用:

所有子类共享Solid中定义的side属性和getter/setter方法
不需要在每个子类中重复定义这些基本内容
多态性:

display()方法接收RubilkCube参数,但可以处理任何子类对象
运行时根据实际对象类型调用相应的方法实现
扩展性:

可以轻松添加新的立体形状或魔方类型,只需继承相应基类
不需要修改现有代码,符合开闭原则
抽象与具体分离:

高层代码(如Main)只需要知道RubilkCube的接口
具体实现细节隐藏在子类中

那我们一起来看一下这道题的代码吧!

 

^-^防伪标识
  import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner input = new Scanner(System.in);

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

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

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

        RubilkCube cube2 = new RegularPyramidCube(color, layer,new RegularPyramid(side));
        display(cube1);
        display(cube2);
    }
    static void display(RubilkCube cube) {
        System.out.println(cube.getColor());
        // 输出表面积(通过组合的Solid对象计算)
        System.out.printf("%.2f\n", cube.getArea());

        // 输出体积(通过组合的Solid对象计算)
        System.out.printf("%.2f\n", cube.getVolume());
    }
    
}
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 RegularPyramid extends Solid{
    public RegularPyramid() {
    }

    public RegularPyramid(double size) {
        super(size);
    }

    @Override
    public double getSide() {
        return super.getSide();
    }

    @Override
    public void setSide(double side) {
        super.setSide(side);
    }

    @Override
    public double getVolume() {
        return Math.sqrt(3)*getSide()*getSide()*getSide()/36;//根号三*a三次方/36
    }

    @Override
    public double getArea() {
        return Math.sqrt(3)*getSide()*getSide();
    }
}
class Cube extends Solid{
    public Cube(){}

    public Cube(double side){
        super(side);
    }

    @Override
    public double getSide() {
        return super.getSide();
    }

    @Override
    public void setSide(double side) {
        super.setSide(side);
    }

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

    @Override
    public double getVolume() {
        return getSide()*getSide()*getSide();
    }


}
abstract class RubilkCube extends Solid{
    protected String color;
    protected int layer;
    protected Solid solid;

    public RubilkCube() {}

    public RubilkCube(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;
    }
}
class SquareCube extends RubilkCube {
    public SquareCube() {}
    public SquareCube(String color, int layer,Solid solid) {
        super(color, layer, solid);
    }

    @Override
    public double getArea(){
        // 步骤1:获取阶数(层数)→ 来自父类RubikCube的属性
        int layers = this.getLayer();

        // 步骤2:获取单元正方体的边长 → 来自组合的Cube对象
        double unitSide = this.getSolid().getSide();

        // 步骤3:计算实际魔方边长(放大后的边长)
        double actualSide = layers * unitSide;

        // 步骤4:套用正方体表面积公式
        return 6 * actualSide * actualSide;
    }

    @Override
    public double getVolume() {
        double actualSide = this.getSolid().getSide()*this.getLayer();

        return Math.pow(actualSide, 3);
    }

}
class RegularPyramidCube extends RubilkCube {
    public RegularPyramidCube() {
    }

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

    @Override
    public double getArea() {
        int layers = this.getLayer();

        double unitSide = this.getSolid().getSide();

        double actualSide = layers * unitSide;

        return Math.sqrt(3) * actualSide * actualSide;
    }

    @Override
    public double getVolume() {
        double actualSide = this.getLayer() * this.getSolid().getSide();

        // 正三棱锥体积公式:√2/12 × 边长³
        return (Math.sqrt(2)/12) * Math.pow(actualSide, 3);
    }
}

 

那么最后分享两个踩坑心得吧!

System.out.println(cube.getColor());
        // 输出表面积(通过组合的Solid对象计算)
        System.out.printf("%.2f\n", cube.getArea());

        // 输出体积(通过组合的Solid对象计算)
        System.out.printf("%.2f\n", cube.getVolume());

这里,我第一次写的代码是:cube.getSolid().getVolume(),结果输出的数字刚好是每一个小方块的信息

而这两种方法的核心差异就在于:

cube.getVolume() 调用链: 1. 访问 SquareCube 的 getVolume() 方法 2. 计算实际边长:2.0(side) × 3(layer) = 6.0 3. 返回 6.0³ = 216.0

cube.getSolid().getVolume() 调用链: 1. 获取 solid 引用 → Cube对象 2. 调用 Cube 的 getVolume() 3. 返回 2.0³ = 8.0

因为RubilkCube cube1 = new SquareCube(color, layer,new Cube(side));和RubilkCube cube2 = new RegularPyramidCube(color, layer,new RegularPyramid(side));

已经明确了我的调用路径会去找谁,所以直接一次getVolume就能找到拼装后的结果啦!

 

那么我们来到,字数最多,解释最复杂,但是也能够攻克的最后一个,航空货运题✈️

 

ok,点开这道题,发现题目要求是pdf,两眼一黑😇

 

https://images.ptausercontent.com/499d204b-fcef-4610-a9ca-c8fbf5e346d9.pdf

 

老样子,我们先来看两组图表数据:

Lines:代码行数,Main.java有 293 行 。
Statements:语句数量,共 156 条语句。
% Branches:分支语句占比,为 5.1% 。
Calls:方法调用次数,为 33 次。
% Comments:注释占比,4.4% 。
Classes:类的数量,有 7 个类。
Methods/Class:平均每个类的方法数,为 6.43 。
Avg Stmts/Method:平均每个方法的语句数,2.16 条 。
Max Complexity:方法的最大复杂度,为 5 。
Max Depth:最大嵌套深度,为 3 。
Avg Depth:平均嵌套深度,1.63 。
Avg Complexity:平均复杂度,1.30 。

组合关系的实际应用 在基础练习中,我们学习到SquareCube应该组合Cube而不是继承它。货运系统将这个原则发挥得更加充分:


Order类组合了Cargo、Flight等多个业务对象
每个类职责单一,通过组合完成复杂功能
特别值得注意的是calCargoWeight工具类的设计,将体积计算完全独立封装


多态机制的进阶使用 相比基础练习中简单的display()抽象,货运系统的calculateRate接口展现了更专业的多态应用:
通过策略模式实现费率计算
不同的Rate实现类可以自由替换
业务逻辑与具体实现完全解耦

其实仔细观察发现,逻辑上来说并不复杂,只不过是很多数据堆砌在了一起,我们在仔细拆解后可以得到以下思考:

 

其中,我们的OrderSystem类持有:

多个Cargo对象(List集合)
单个Flight对象
三个Customer对象(客户、发件人、收件人)
一个TieredRate策略对象

一个calCargoWeight计算工具


接口实现关系:
TieredRate实现了calculateRate接口
OrderSystem依赖接口而非具体实现


计费重量计算流程:
OrderSystem遍历所有Cargo
每个Cargo调用calCargoWeight计算体积重量
比较实际重量和体积重量取较大值


运费计算流程:
根据Cargo提供的计费重量
TieredRate按重量区间选择费率
累加所有货物的运费

 

 

 

我们的类图如下:

 

在这道题目中,其实只要结合题目给的提示,梳理清楚逻辑,在前几道题目的铺垫上,会发现其实并没有那么复杂,不过我还是犯了一些小错误💩(在PTA上我也注释了这个错误,投机取巧的后果非常可怕啊!)

我们来看一下:

为什么会有这个反思呢?很简单,我们来看一下题目要求的输入:

 

会发现,其实这里不光只有两个人,其实还有第三个人!

没错,他就是我们的--郭靖!

为什么我要单独把他拎出来呢?

我的心路历程belike:在第一行的System.out中,我们输出的其实是郭靖的相关信息,如果只创建两个"送件人"和"收货人",那我们第一行的郭靖的信息怎么办呢?那我偷个懒,直接在show方法之前输出一行不就行了?但是这样却是拆东墙,补西墙,一直有问题😇

因为,除了我们的送件人和收件人,我们的郭靖应该是本次货运的"客户",他的信息我们需要单独去创建一个类进行打印,这就导致,如果我想在主函数里偷懒,直接打印郭靖的信息的话,会让信息打印顺序有误,那有人会说了,你把打印郭靖信息的代码放到输出信息前面不就好了吗?事实并非如此,因为我们还要有货物超重的判断,这样会导致先打印郭靖的信息,然后再输出货物超重!!!

那没办法了,只能老老实实的建立一个郭靖了!

然后跟其他的输出内容使用一样的格式,一起出来,那我们来看一下最后的代码吧:

 

^-^我又来咯
  import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

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

        //客户基本信息与取件人收件人的输入中间隔了很多,所以不太好放到customer类里面,所以我直接在后面作了单独一行去打印
        
        //没错,我打脸了,这样失败了,因为在输出错误提示的时候,我发现先把郭靖打上去了,所以重新创建了一个customer作为存储郭靖信息的地方
        String custId = scanner.nextLine();
        String custName = scanner.nextLine();
        String custPhone = scanner.nextLine();
        String custAddress = scanner.nextLine();
        Customer customer = new Customer(custName, custPhone, custAddress);

        //货物数量
        int cargoCount = Integer.parseInt(scanner.nextLine());
        List<Cargo> cargos = new ArrayList<>();

        //读取货物数据
        for (int i = 0; i < cargoCount; i++) {
            String id = scanner.nextLine();
            String name = scanner.nextLine();
            double width = Double.parseDouble(scanner.nextLine());
            double length = Double.parseDouble(scanner.nextLine());
            double height = Double.parseDouble(scanner.nextLine());
            double weight = Double.parseDouble(scanner.nextLine());

            cargos.add(new Cargo(id, name, width, length, height, weight));
        }

        //读取航班信息
        String flightNumber = scanner.nextLine();
        String departure = scanner.nextLine();
        String arrival = scanner.nextLine();
        LocalDate flightDate = LocalDate.parse(scanner.nextLine());
        double maxLoad = Double.parseDouble(scanner.nextLine());

        Flight flight = new Flight(flightNumber, departure, arrival,
                flightDate, maxLoad);

        // 读取订单信息(洪七公和黄药师)
        String orderNumber = scanner.nextLine();
        LocalDate orderDate = LocalDate.parse(scanner.nextLine());

        // 读取发件人信息(南昌大学、洪七公、18907912325)
        String senderAddr = scanner.nextLine();
        String senderName = scanner.nextLine();
        String senderPhone = scanner.nextLine();
        Customer sender = new Customer(senderName, senderPhone, senderAddr);

        // 读取收件人信息(北京大学、黄药师、13607912546)
        String receiverAddr = scanner.nextLine();
        String receiverName = scanner.nextLine();
        String receiverPhone = scanner.nextLine();
        Customer receiver = new Customer(receiverName, receiverPhone, receiverAddr);

        // 创建订单系统时传递 Customer 对象
        OrderSystem orderSystem = new OrderSystem(
                cargos,
                flight,
                new TieredRate(),
                orderNumber,
                orderDate,
                customer,
                sender,
                receiver
        );
        //System.out.println("客户:" + custName+ "(" + custPhone + ")订单信息如下:");
        orderSystem.process();
    }
}

class Flight {
    private String number;
    private double maxLoad;
    private double remainingLoad;
    private String departure;
    private String arrival;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public double getMaxLoad() {
        return maxLoad;
    }

    public void setMaxLoad(double maxLoad) {
        this.maxLoad = maxLoad;
    }

    public double getRemainingLoad() {
        return remainingLoad;
    }

    public void setRemainingLoad(double remainingLoad) {
        this.remainingLoad = remainingLoad;
    }

    public String getDeparture() {
        return departure;
    }

    public void setDeparture(String departure) {
        this.departure = departure;
    }

    public String getArrival() {
        return arrival;
    }

    public void setArrival(String arrival) {
        this.arrival = arrival;
    }

    public Flight(String number, String departure, String arrival,LocalDate date, double maxLoad) {
        this.number = number;
        this.maxLoad = maxLoad;
        this.remainingLoad = maxLoad;
        this.departure = departure;
        this.arrival = arrival;
    }

    public boolean canCarry(double weight) {
        return weight <= remainingLoad;
    }

    public void reduceCapacity(double weight) {
        remainingLoad -= weight;
    }
}

class Cargo {
    private String id;
    private String name;
    private double width;
    private double length;
    private double height;
    private double weight;
    private calCargoWeight calculator = new calCargoWeight();

    public Cargo() {}

    public Cargo(String id, String name, double width, double length,double height, double weight) {
        this.id = id;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public double getChargeableWeight() {
        //calCargoWeight calculator = new calCargoWeight();
        double volume = calculator.calculateVolume(length, width, height);
        return calculator.getChargeableWeight(weight, volume);
    }
}

class calCargoWeight {
    private static final int v = 6000;

    public double calculateVolume(double length, double width, double height) {
        return (length * width * height) / v;
    }
    public double getChargeableWeight(double actualWeight, double volume) {
        return Math.max(actualWeight, volume);
    }
}

class TieredRate implements calculateRate {

    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 35;
        else if (weight < 50) return 30;
        else if (weight < 100) return 25;
        else return 15;
    }

}

interface calculateRate {
    public abstract double calculateRate(double weight);
}

class OrderSystem {

    private List<Cargo> cargos;
    private Flight flight;
    private String orderNumber;
    private LocalDate orderDate;
    private Customer sender;
    private Customer receiver;
    private Customer customer;
    private calculateRate calculateRate;

    public OrderSystem(List<Cargo> cargos, Flight flight, calculateRate calculateRate,
                       String orderNumber, LocalDate orderDate,Customer customer,Customer sender, Customer receiver) {
        this.cargos = cargos;
        this.flight = flight;
        this.calculateRate = calculateRate;
        this.orderNumber = orderNumber;
        this.orderDate = orderDate;
        this.sender = sender;
        this.receiver = receiver;
        this.customer = customer;
    }

    public void process() {
        double totalWeight = 0;
        for (Cargo cargo : cargos) {
            totalWeight += cargo.getChargeableWeight();
        }

        if (!flight.canCarry(totalWeight)) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n",
                    flight.getNumber());
            System.exit(0);
        }

        flight.reduceCapacity(totalWeight);
        show(totalWeight);
    }

    private void show(double totalWeight){
        double totalPayment = 0;
        //这里把原来的第一行输出删掉了,因为发现郭靖变成了内谁

        //就是这里,删不掉,还得是在这块打印啊!突然发现自己上面的方法好像是投机取巧,因为只有在这里打印才算是满足那个单一职责
        System.out.println("客户:" +customer.getName() + "(" + customer.getPhone() + ")订单信息如下:");
        System.out.println("-----------------------------------------");
        System.out.println("航班号:" + flight.getNumber());
        System.out.println("订单号:" + orderNumber);
        System.out.println("订单日期:" + orderDate);
        System.out.println("发件人姓名:" + sender.getName());
        System.out.println("发件人电话:" + sender.getPhone());
        System.out.println("发件人地址:" + sender.getAddress());
        System.out.println("收件人姓名:" + receiver.getName());
        System.out.println("收件人电话:" + receiver.getPhone());
        System.out.println("收件人地址:" + receiver.getAddress());
        System.out.println("订单总重量(kg):" + totalWeight);
        for (Cargo cargo : cargos) {
            double weight = cargo.getChargeableWeight();
            double rate = calculateRate.calculateRate(weight);
            double payment = weight * rate;
            totalPayment += payment;
        }
        System.out.printf("微信支付金额:%.1f\n", totalPayment);

        System.out.println("\n货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");

        int index = 1;
        for (Cargo cargo : cargos) {
            double weight = cargo.getChargeableWeight();
            double rate = calculateRate.calculateRate(weight);
            double payment = weight * rate;
            System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n",index++, cargo.getName(), weight, rate, payment);
        }
    }
}

class Customer {
    private String name;
    private String phone;
    private String address;

    public Customer(String name, String phone, String address) {
        this.name = name;
        this.phone = phone;
        this.address = address;
    }

    public String getName() { return name; }
    public String getPhone() { return phone; }
    public String getAddress() { return address; }
}

 

那我们来看迭代后的下一题:https://images.ptausercontent.com/499d204b-fcef-4610-a9ca-c8fbf5e346d9.pdf

(地铁,老人,手机*2...😶)

秉持着以不变应万变的思想,我们先来大致看一下迭代后的要求与第一次哪里有不同:

第一个就是费率与之前的计算方式有改变,分了更多的类,这部分好改,我们可以定义一个接口,分别去重写三种方法,对应三种不同的计算方法:

interface calculateRate {
    public abstract double calculateRate(double weight);
}

//普通类货物
class NormalRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 35;
        else if (weight < 50) return 30;
        else if (weight < 100) return 25;
        else return 15;
    }
}
//加急类货物
class ExpediteRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 60;
        else if (weight < 50) return 50;
        else if (weight < 100) return 40;
        else return 30;
    }
}
// 危险类货物
class DangerousRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 80;
        else if (weight < 50) return 50;
        else if (weight < 100) return 30;
        else return 20;
    }
}

那么第二个变化的地方就是我们的支付方式,这是题目明确要求的添加的类,这里也不难,我们只需要将对应的英文输出对应的中文的支付方式,再给他计算后的货物总费用即可:

class Payment {
    private String method; // Wechat/ALiPay/Cash
    private double amount;

    public Payment(String method, double amount) {
        this.method = method;
        this.amount = amount;
    }

    public String getPaymentInfo() {
        String methodName = switch (method) {
            case "Wechat" -> "微信";
            case "ALiPay" -> "支付宝";
            case "Cash" -> "现金";
            default -> "未知";
        };
        return methodName + "支付金额:" + String.format("%.1f", amount);
    }
}

 

那么最后一个地方,也是让我思考颇多,收益匪浅的地方.段老师在群里给我们这样的提示:

 

那意味着我的第一次的ordersystem类就要拆成两个类了,那么为什么单独一个类来处理订单不好呢?

如果一个类的话,这个类将要处理:

分成两个类来处理:

Order类:

负责订单整体管理
处理订单级别的操作(如总费用计算、折扣应用、航班容量检查)
协调订单与客户、航班等外部实体的关系
OrderItem类:

专注于单个货物项的处理
计算单个货物的计费重量和运费
维护货物与费率策略的关系
这种分离让每个类只关注自己的核心职责,避免了"上帝类"的出现。

这就让代码具有了更好的封装性和内聚性:
Order不需要了解货物计费的细节,只需通过OrderItem接口获取结果
OrderItem封装了所有与单个货物相关的计算逻辑,变化不会影响Order
修改货物计费方式(如增加新的费率策略)只需修改OrderItem,不影响订单主逻辑

在实际业务中:

  • 客服查询订单状态 → 只需要Order信息
  • 仓库处理发货 → 主要关注OrderItem
  • 财务对账 → 需要Order汇总和Item明细

分析到这,我再看平常点外卖的订单,似乎对这个生活中不起眼的细节有了更加深刻的理解..😎

那么到了最后,让我们一起来看一下代码的最终实现吧!

最后的代码啦,辛苦批阅^-^
 import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

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

        String custType = scanner.nextLine();
        String custId = scanner.nextLine();
        String custName = scanner.nextLine();
        String custPhone = scanner.nextLine();
        String custAddress = scanner.nextLine();
        Customer customer = new Customer(custName, custPhone, custAddress, custType);

        String cargoType = scanner.nextLine();
        calculateRate rateStrategy;
        if ("Expedite".equals(cargoType)) {
            rateStrategy = new ExpediteRate();
        } else if ("Dangerous".equals(cargoType)) {
            rateStrategy = new DangerousRate();
        } else {
            rateStrategy = new NormalRate(); //默认普通货物
        }

        //货物数量
        int cargoCount = Integer.parseInt(scanner.nextLine());
        List<Cargo> cargos = new ArrayList<>();

        //读取货物数据
        for (int i = 0; i < cargoCount; i++) {
            String id = scanner.nextLine();
            String name = scanner.nextLine();
            double width = Double.parseDouble(scanner.nextLine());
            double length = Double.parseDouble(scanner.nextLine());
            double height = Double.parseDouble(scanner.nextLine());
            double weight = Double.parseDouble(scanner.nextLine());

            cargos.add(new Cargo(id, name, width, length, height, weight, cargoType));
        }

        //读取航班信息
        String flightNumber = scanner.nextLine();
        String departure = scanner.nextLine();
        String arrival = scanner.nextLine();
        LocalDate flightDate = LocalDate.parse(scanner.nextLine());
        double maxLoad = Double.parseDouble(scanner.nextLine());

        Flight flight = new Flight(flightNumber, departure, arrival,
                flightDate, maxLoad);

        // 读取订单信息(洪七公和黄药师)
        String orderNumber = scanner.nextLine();
        LocalDate orderDate = LocalDate.parse(scanner.nextLine());

        // 读取发件人信息(南昌大学、洪七公、18907912325)
        String senderAddr = scanner.nextLine();
        String senderName = scanner.nextLine();
        String senderPhone = scanner.nextLine();
        Customer sender = new Customer(senderName, senderPhone, senderAddr,null);

        // 读取收件人信息(北京大学、黄药师、13607912546)
        String receiverAddr = scanner.nextLine();
        String receiverName = scanner.nextLine();
        String receiverPhone = scanner.nextLine();
        Customer receiver = new Customer(receiverName, receiverPhone, receiverAddr,null);

        // 创建订单系统时传递 Customer 对象
        Order order = new Order(
                cargos,
                flight,
                rateStrategy,
                orderNumber,
                orderDate,
                customer,
                sender,
                receiver
        );
        String paymentMethod = scanner.nextLine();
        order.process(paymentMethod);
    }
}

class Flight {
    private String number;
    private double maxLoad;
    private double remainingLoad;
    private String departure;
    private String arrival;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public double getMaxLoad() {
        return maxLoad;
    }

    public void setMaxLoad(double maxLoad) {
        this.maxLoad = maxLoad;
    }

    public double getRemainingLoad() {
        return remainingLoad;
    }

    public void setRemainingLoad(double remainingLoad) {
        this.remainingLoad = remainingLoad;
    }

    public String getDeparture() {
        return departure;
    }

    public void setDeparture(String departure) {
        this.departure = departure;
    }

    public String getArrival() {
        return arrival;
    }

    public void setArrival(String arrival) {
        this.arrival = arrival;
    }

    public Flight(String number, String departure, String arrival,LocalDate date, double maxLoad) {
        this.number = number;
        this.maxLoad = maxLoad;
        this.remainingLoad = maxLoad;
        this.departure = departure;
        this.arrival = arrival;
    }

    public boolean canCarry(double weight) {
        return weight <= remainingLoad;
    }

    public void reduceCapacity(double weight) {
        remainingLoad -= weight;
    }
}

class Cargo {
    private String id;
    private String name;
    private double width;
    private double length;
    private double height;
    private double weight;
    private String cargoType;
    private calCargoWeight calculator = new calCargoWeight();

    public Cargo() {}

    public Cargo(String id, String name, double width, double length,double height, double weight ,String cargoType) {
        this.id = id;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.weight = weight;
        this.cargoType = cargoType;
    }

    public String getName() {
        return name;
    }

    public String getCargoType() {
        return cargoType;
    }

    public double getChargeableWeight() {
        //calCargoWeight calculator = new calCargoWeight();
        double volume = calculator.calculateVolume(length, width, height);
        return calculator.getChargeableWeight(weight, volume);
    }
}

class calCargoWeight {
    private static final int v = 6000;

    public double calculateVolume(double length, double width, double height) {
        return (length * width * height) / v;
    }
    public double getChargeableWeight(double actualWeight, double volume) {
        return Math.max(actualWeight, volume);
    }
}
//普通类货物
class NormalRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 35;
        else if (weight < 50) return 30;
        else if (weight < 100) return 25;
        else return 15;
    }
}
//加急类货物
class ExpediteRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 60;
        else if (weight < 50) return 50;
        else if (weight < 100) return 40;
        else return 30;
    }
}
// 危险类货物
class DangerousRate implements calculateRate {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 80;
        else if (weight < 50) return 50;
        else if (weight < 100) return 30;
        else return 20;
    }
}



interface calculateRate {
    public abstract double calculateRate(double weight);
}

class OrderItem {
    private Cargo cargo;
    private double chargeableWeight;
    private double rate;
    private double payment;

    public OrderItem(Cargo cargo, calculateRate rateStrategy) {
        this.cargo = cargo;
        this.chargeableWeight = cargo.getChargeableWeight();
        this.rate = rateStrategy.calculateRate(chargeableWeight); // 关键:调用费率策略
        this.payment = chargeableWeight * rate;
    }

    // Getter方法(供Order类调用)
    public double getChargeableWeight() { return chargeableWeight; }
    public double getPayment() { return payment; }
    public String getCargoName() { return cargo.getName(); }
    public double getRate() { return rate; }
}
class Order {
    private String orderNumber;
    private LocalDate orderDate;
    private Customer customer;
    private Customer sender;
    private Customer receiver;
    private Flight flight;
    private List<OrderItem> orderItems; // 订单明细列表
    private calculateRate rateStrategy; // 费率策略

    // 构造函数
    public Order(List<Cargo> cargos, Flight flight, calculateRate rateStrategy,
                 String orderNumber, LocalDate orderDate, Customer customer,
                 Customer sender, Customer receiver) {
        this.orderNumber = orderNumber;
        this.orderDate = orderDate;
        this.customer = customer;
        this.sender = sender;
        this.receiver = receiver;
        this.flight = flight;
        this.rateStrategy = rateStrategy;

        // 初始化订单明细
        this.orderItems = new ArrayList<>();
        for (Cargo cargo : cargos) {
            orderItems.add(new OrderItem(cargo, rateStrategy));
        }
    }

    // 处理订单核心逻辑
    public void process(String paymentMethod) {
        // 1. 计算总重量和原始运费
        double totalWeight = 0;
        double originalPayment = 0;
        for (OrderItem item : orderItems) {
            totalWeight += item.getChargeableWeight();
            originalPayment += item.getPayment();
        }

        // 2. 应用客户折扣
        double totalPayment = originalPayment;
        
        if ("Corporate".equals(customer.getCustomerType())) {
            totalPayment *= 0.8; // 集团用户8折
        } else if ("Individual".equals(customer.getCustomerType())) {
            totalPayment *= 0.9; // 个人用户9折
        }

        Payment payment = new Payment(paymentMethod, totalPayment);
        //System.out.println(payment.getPaymentInfo());

        // 3.检查航班载重
        if (!flight.canCarry(totalWeight)) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n",
                    flight.getNumber());
            System.exit(0);
        }

        // 4.更新航班剩余载重并显示结果
        flight.reduceCapacity(totalWeight);
        showOrderDetails(totalWeight, totalPayment,paymentMethod);
    }

    // 显示订单详情
    private void showOrderDetails(double totalWeight, double totalPayment,String paymentMethod) {
        // 打印订单基本信息
        System.out.println("客户:" + customer.getName() + "(" + customer.getPhone() + ")订单信息如下:");
        System.out.println("-----------------------------------------");
        System.out.println("航班号:" + flight.getNumber());
        System.out.println("订单号:" + orderNumber);
        System.out.println("订单日期:" + orderDate);
        System.out.println("发件人姓名:" + sender.getName());
        System.out.println("发件人电话:" + sender.getPhone());
        System.out.println("发件人地址:" + sender.getAddress());
        System.out.println("收件人姓名:" + receiver.getName());
        System.out.println("收件人电话:" + receiver.getPhone());
        System.out.println("收件人地址:" + receiver.getAddress());
        System.out.println("订单总重量(kg):" + totalWeight);
        Payment payment = new Payment(paymentMethod, totalPayment);
        System.out.println(payment.getPaymentInfo());

        // 打印货物明细
        System.out.println("\n货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        for (int i = 0; i < orderItems.size(); i++) {
            OrderItem item = orderItems.get(i);
            System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n",
                    i + 1,
                    item.getCargoName(),
                    item.getChargeableWeight(),
                    item.getRate(),
                    item.getPayment());
        }
    }
}

class Customer {
    private String name;
    private String phone;
    private String address;
    private String customerType;

    public Customer(String name, String phone, String address, String customerType) {
        this.name = name;
        this.phone = phone;
        this.address = address;
        this.customerType = customerType;
    }

    public String getName() { return name; }
    public String getPhone() { return phone; }
    public String getAddress() { return address; }
    public String getCustomerType() { return customerType; }
}

class Payment {
    private String method; // Wechat/ALiPay/Cash
    private double amount;

    public Payment(String method, double amount) {
        this.method = method;
        this.amount = amount;
    }

    public String getPaymentInfo() {
        String methodName = switch (method) {
            case "Wechat" -> "微信";
            case "ALiPay" -> "支付宝";
            case "Cash" -> "现金";
            default -> "未知";
        };
        return methodName + "支付金额:" + String.format("%.1f", amount);
    }
}

来大致的过一遍各类的职责与相互关系:

Main类:

作为程序入口,负责协调所有类的交互
创建并连接其他所有类的实例
Customer类:

被Order类持有(作为客户、发件人和收件人)
与Order类是一对多关系(一个客户可以有多个订单)


Cargo类:

被Order类通过OrderItem间接持有
与OrderItem是一对一关系
使用calCargoWeight类计算计费重量(组合关系)


Flight类:

被Order类持有
与Order类是一对多关系(一个航班可以承载多个订单)


Order类:

系统的核心类,协调多个其他类的交互
持有Customer(3个角色)、Flight、OrderItem集合和calculateRate策略
与Payment类协作处理支付


OrderItem类:

作为Order和Cargo之间的中介
使用calculateRate策略计算费用
calculateRate接口及其实现类:

策略模式实现,通过接口与OrderItem交互
三种实现:NormalRate、ExpediteRate、DangerousRate


Payment类:

被Order类使用来处理支付信息
与Order类是临时关联关系

当我们分析图表,可以探索得到以下内容:

  • 复杂度分布特征图中显示复杂度峰值为5级,出现在main方法和费率计算方法
    这种分布表明核心业务逻辑集中在合理位置,没有出现复杂度失控
    特别值得注意的是,工具类方法(如calculateVolume)保持最低复杂度1,验证了单一职责原则的贯彻
  • 代码深度分析
    深度3的代码块占比最大(79语句),对应OrderItem这一层级
    深度4仅出现1次,显示系统避免了过度嵌套
    这种"浅而宽"的分布是良好分层架构的典型特征,优于"深而窄"的调用链
  • 方法质量矩阵
    理想的方法应该位于"低复杂度+少语句数"区间
    图中显示大多数方法落在理想区域,特别是:
    工具类方法集中在左下角(复杂度1,语句3)
    核心业务方法分布在中间区域(复杂度3-5,语句10-20)
    仅main方法略显臃肿(41语句),但考虑到是入口方法尚可接受
  • 架构健康指标
    雷达图显示各项指标均衡发展,没有明显短板
    特别突出的优势:
    合理的抽象层次(Abstraction)
    良好的封装性(Encapsulation)
    可控的复杂度(Complexity)
  • 待改进点:
    注释覆盖率(Comments)相对较低
    个别方法可进一步分解
    分层架构验证
    深度0:架构定义层(9语句)
    深度1:业务流程层(28语句)
    深度2:业务实现层(91语句)
    深度3:工具层(79语句) 这种金字塔结构完美匹配经典的分层架构模式,每层都有明确的职责边界。

 

又到了总结学习心得的时候:  

在未来的代码学习和实践中,我将更加注重**架构设计**与**代码质量**的平衡。通过这次对Order与OrderItem分离设计的分析,我深刻体会到:  

1. 分层思维:代码不是越少越好,而是职责越清晰越好。合理的分层能让系统更易维护、扩展和测试。  
2. 度量驱动:学会借助工具(如复杂度分析、代码覆盖率)量化代码质量,而非仅凭直觉判断。  
3. 设计原则落地:SOLID不是理论,而是实践指南。比如单一职责原则(SRP)直接决定了类该不该拆分。  
4. 迭代优化:即使初期设计不够完美,也要通过重构逐步接近理想状态,而非追求一步到位。  

未来写代码时,我会像建筑师一样思考——先规划结构,再填充细节,让每一行代码都有明确的归属和意义🥴。  

---

编程如航海,有时迷雾重重,但好的设计就像灯塔,指引着代码的航向。当层层抽象拨云见日,当冗余逻辑被一一剥离,系统终会回归它最本真的模样——简洁、优雅、直指核心。  

云散月明谁点缀,天容海色本澄清。好的代码亦如此,褪尽浮华后,唯留清澈逻辑,如皓月当空,海天一色🌙

posted on 2025-05-22 02:00  狛枝z  阅读(30)  评论(0)    收藏  举报