NCHU_OOP_航空货运 管理系统

0. Index

咦惹,怎么这回只做两次作业就要开始写博客了,难道是时间不够了?🤪倒也不是不好,毕竟要迭代我也想不到再怎么迭代了。

只是,这博客是真的难写呀😭总结是真不知道总结什么,这一次不就是更好的类设计嘛,单一职责原则、里氏代换原则、开闭原则以及合成复用原则、依赖倒转原则。

1. 题干

输入格式

客户编号
客户姓名
客户电话
客户地址
运送货物数量
[
货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
]
// []内的内容输入次数取决于“运送货物数量”,输入不包含“[]”
航班号
航班起飞机场
航班降落机场
航班日期(格式为YYYY-MM-DD)
航班最大载重量
订单编号
订单日期(格式为YYYY-MM-DD)
发件人地址
发件人姓名
发件人电话
收件人地址
收件人姓名
收件人电话

输出格式

客户:姓名(电话)订单信息如下:


航班号:
订单号:
订单日期:
发件人姓名:
发件人电话:
发件人地址:
收件人姓名:
收件人电话:
收件人地址:
订单总重量(kg):
微信支付金额:

货物明细如下:


明细编号 货物名称 计费重量 计费费率 应交运费
1 ...
2 ...

其他信息

计费重量等于MAX(实际质量, 体积重量);体积重量等于 体积 / 6000。

货物费率Rate按照重量分四档:

计费重量 (, 20) [20, 50) [50, 100) [100, )
Rate 35 30 25 15

2. 分析

题干就那样啦,可能看题干要点时间,做PTA和打算法比赛(基础)打多了,其实也有读题技巧🤭,就是直接从输入开始读信息,所以我们看一下输入信息,容易看出来我们需要一个客户类货物类航班类订单类收件人类发件人类

然后对这些整理一下,客户类(Customer)、收件人类(Recipient)、发件人类(Sender),都可以继承自人类(Person),它们都有姓名、电话、地址,客户类再多一个编号。那么这三个的类图设计就如下所示哟(注意这里采用的是合成复用,而不是继承)👇

image

航班

对于航班类,除了一些基本属性,还需要有一个判断是否超载的方法,而按照面向对象的设计原则、以及考虑拓展性,应当单独给一个接口用于判断是否合法,这个接口后续也可被拓展用于判断除超载外其他方面是否非法,达到了面向接口编程的效果👇

image

货物

货物类和航班类类似,除了基本属性,还有一个计算费率的方法,这个可以单独提出一个接口,当计算费率发生改变的时候,只需要改变这个接口的实现👇

image

订单

最后是订单类,一个订单类里面需要包含客户信息、航班信息、货物列表、收/发件人信息、以及金额信息,订单类还包含一个计算金额的方法和一个展示订单信息的方法😶‍🌫️。除此之外,订单不能凭空产生,一般会有订单管理器,所以另外写一个订单管理器,订单管理器可以生成一份订单,而生成过程也就是从输入读取的过程

image image

3. 迭代

第二周,对第一周的航空货运管理系统进行扩展,第二周的扩展是:

🤵‍♂️给客户增加了类型,有个体类型和集团类型,

🎲给货物增加的类型,分为普通类、加急类、危险类,不同类型的货物的计费Rate不一样,

💰给支付方式添加了类型,有微信支付、支付宝支付、现金支付。

其他没有变化,那么思考一下应该怎么去拓展这些内容,首先,对于这各种各样的类型,肯定不能写死在类里面,不然万一还要拓展呢🥱,可以单独写enum类型,然后相应的类里面包含一个enum对象:

image

image image image

有一点是,虽然题干说是货物有类型,但是在本次开发中应当把这个类型分配给订单,把货物类型变为订单类型,因为一批订单要么加急要么危险要么普通,加急的和普通的货物不会出现在同一份订单里面😁😁

此外,对于支付方式,英文是它真正的类型,而输出的时候需要它的中文别名.😒

此外,因为不同类型的订单它的货物计费Rate是不一样的,如果写3×4个if-else会相当的冗余,如果后续还要加就会更加冗余,所以这里应当用数组,把不同类型的计费Rate写到数组中,后面再写一个if-else就够了

image

4. 代码实现

点击查看代码
package 航空货运管理系统;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


// 客户类型枚举
enum CustomerType {
    Individual, // 个人
    Corporate   // 集团
}


// 商品类型枚举
enum OrderType {
    Normal,     // 常规
    Expedite,   // 加急
    Dangerous   // 危险
}


// 支付类型枚举
enum PayType {
    Wechat("微信"),
    Alipay("支付宝"),
    Cash("现金");

    private final String description;

    PayType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}


// 人类(基类)
class Person {
    // 字段区
    private String name;
    private String phone;
    private String address;

    // 构造区
    public Person() {}
    public Person(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; }

    public void setName(String name) { this.name = name; }
    public void setPhone(String phone) { this.phone = phone; }
    public void setAddress(String address) { this.address = address; }

    // 方法区
}


// 客户类(合成复用人类)
class Customer {
    // 字段区
    private String id;
    private CustomerType type;
    private final Person person;

    // 构造区
    public Customer() {
        this.person = new Person();
    }
    public Customer(String id, CustomerType type, Person person) {
        this.id = id;
        this.type = type;
        this.person = person;
    }

    // 属性区
    public CustomerType getType() { return type; }
    public String getName() { return person.getName(); }
    public String getPhone() { return person.getPhone(); }
    
    // 方法区
}


// 收件人类(合成复用人类)
class Recipient {
    private final Person person;

    public Recipient() {
        this.person = new Person();
    }

    public String getName() { return person.getName(); }
    public void setName(String name) { person.setName(name); }

    public String getPhone() { return person.getPhone(); }
    public void setPhone(String phone) { person.setPhone(phone); }

    public String getAddress() { return person.getAddress(); }
    public void setAddress(String address) { person.setAddress(address); }
}


// 发件人类(合成复用人类)
class Sender {
    // 字段区
    private final Person person;

    // 构造区
    public Sender() {
        this.person = new Person();
    }

    // 属性区
    public String getName() { return person.getName(); }
    public String getPhone() { return person.getPhone(); }
    public String getAddress() { return person.getAddress(); }

    public void setName(String name) { person.setName(name); }
    public void setPhone(String phone) { person.setPhone(phone); }
    public void setAddress(String address) { person.setAddress(address); }

    // 方法区
}


// 航班类
class Flight {
    // 字段区
    private String number;
    private String begin;
    private String end;
    private Date date;
    private double maxCarryWeight;

    // 构造区
    public Flight() {}

    // 属性区
    public String getNumber() { return number; }
    public double getMaxCarryWeight() { return maxCarryWeight; }

    public void setNumber(String number) { this.number = number; }
    public void setBegin(String begin) { this.begin = begin; }
    public void setEnd(String end) { this.end = end; }
    public void setDate(Date date) { this.date = date; }
    public void setMaxCarryWeight(double maxCarryWeight) { this.maxCarryWeight = maxCarryWeight; }

    // 方法区
}


// 订单类
class Order {
    // 字段区
    private String id;
    private Customer customer;
    private Flight flight;
    private Date date;
    private Sender sender;
    private Recipient recipient;
    private OrderType type;
    private ArrayList<Product> products;
    private PayType payType;
    private double totalWeight;
    private double totalFee;

    // 构造区
    public Order() {
        this.customer = new Customer();
        this.flight = new Flight();
        this.sender = new Sender();
        this.recipient = new Recipient();
        this.products = new ArrayList<>();
    }

    // 属性区
    public String getId() { return id; }
    public Date getDate() { return date; }
    public ArrayList<Product> getProducts() { return products; }

    public void setId(String id) { this.id = id; }
    public void setCustomer(Customer customer) { this.customer = customer; }
    public void setFlight(Flight flight) { this.flight = flight; }
    public void setDate(Date date) { this.date = date; }
    public void setSender(Sender sender) { this.sender = sender; }
    public void setRecipient(Recipient recipient) { this.recipient = recipient; }
    public void setType(OrderType type) { this.type = type; }
    public void setProducts(ArrayList<Product> products) { this.products = products; }
    public void setPayType(PayType payType) { this.payType = payType; }
    public void setTotalWeight(double totalWeight) { this.totalWeight = totalWeight; }

    // 方法区
    public double Fee() {
        for(Product product : this.products) {
            this.totalFee += product.Fee(type);
        }
        switch(customer.getType()) {
            case Individual -> this.totalFee *= 0.9;
            case Corporate -> this.totalFee *= 0.8;
        }
        return this.totalFee;
    }

    public void show() {
        System.out.printf("客户:%s(%s)订单信息如下:\n", this.customer.getName(), this.customer.getPhone());
        System.out.println("-----------------------------------------");
        System.out.println("航班号:" + this.flight.getNumber());
        System.out.println("订单号:" + this.getId());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        System.out.println("订单日期:" + sdf.format(this.getDate()));
        System.out.println("发件人姓名:" + this.sender.getName());
        System.out.println("发件人电话:" + this.sender.getPhone());
        System.out.println("发件人地址:" + this.sender.getAddress());
        System.out.println("收件人姓名:" + this.recipient.getName());
        System.out.println("收件人电话:" + this.recipient.getPhone());
        System.out.println("收件人地址:" + this.recipient.getAddress());
        System.out.printf("订单总重量(kg):%.1f%n", this.totalWeight);
        System.out.printf("%s支付金额:%.1f%n", this.payType.getDescription(), this.Fee());
        System.out.println();
        System.out.println("货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        int i = 1;
        for(Product product : this.products) {
            System.out.printf("%d\t", i);
            System.out.printf("%s\t%.1f\t%.1f\t%.1f%n", product.getName(), product.getWeight(), product.getRate(), product.getFee());
            i++;
        }
    }
}


// 商品类
class Product {
    // 字段区
    private final String name;
    private final double width;
    private final double length;
    private final double height;
    private double weight;
    private double fee;
    private double rate;

    // 构造区
    public Product(String id, String name, double length, double width, double height, double weight) {
        this.name = name;
        this.length = length;
        this.width = width;
        this.height = height;
        this.weight = weight;
    }

    // 属性区
    public String getName() { return name; }
    public double getWidth() { return width; }
    public double getLength() { return length; }
    public double getHeight() { return height; }
    public double getWeight() { return weight; }
    public double getFee() { return fee; }
    public double getRate() { return this.rate; }

    public void setWeight(double weight) { this.weight = weight; }

    // 方法区
    public double Fee(OrderType OrType) {
        ArrayList<Double> rates = new ArrayList<>();
        switch(OrType) {
            case Normal:
                rates.addAll(Arrays.asList(35.0, 30.0, 25.0, 15.0));
                break;
            case Expedite:
                rates.addAll(Arrays.asList(60.0, 50.0, 40.0, 30.0));
                break;
            case Dangerous:
                rates.addAll(Arrays.asList(80.0, 50.0, 30.0, 20.0));
        }
        if(this.weight < 20) {
            this.rate = rates.get(0);
        }
        else if (this.weight >= 20 && this.weight < 50) {
            this.rate = rates.get(1);
        }
        else if (this.weight >= 50 && this.weight < 100) {
            this.rate = rates.get(2);
        }
        else {
            this.rate = rates.get(3);
        }
        this.fee = rate*this.weight;
        return this.fee;
    }
}


// 输入处理器抽象类
abstract class InputHandler {
    // 字段区
    protected Scanner scanner;

    // 构造区
    public InputHandler() {
        this.scanner = new Scanner(System.in);
    }

    // 属性区

    // 方法区
    public abstract Customer inputCustomer();

    public abstract OrderType inputOrderType();

    public abstract ArrayList<Product> inputProducts();

    public abstract Flight inputFlight();

    public abstract String inputOrderId();

    public abstract Date inputOrderDate();

    public abstract Sender inputSender();

    public abstract Recipient inputRecipient();

    public abstract PayType inputPayType();
}


//  输入处理器具体实现类
class DefaultInputHandler extends InputHandler {
    @Override
    public Customer inputCustomer() {
        String type = scanner.next();
        CustomerType customerType = CustomerType.Individual;
        for(CustomerType CusType : CustomerType.values()) {
            if(CusType.name().equals(type)) {
                customerType = CusType;
                break;
            }
        }
        return new Customer(
                                        scanner.next(),
                                        customerType,
                                        new Person(scanner.next(), scanner.next(), scanner.next())
        );
    }

    @Override
    public OrderType inputOrderType() {
        String type = scanner.next();
        for(OrderType OrType : OrderType.values()) {
            if(OrType.name().equals(type)) {
                return OrType;
            }
        }
        return OrderType.Normal;
    }

    @Override
    public ArrayList<Product> inputProducts() {
        ArrayList<Product> products = new ArrayList<>();
        int numProducts = scanner.nextInt();
        for (int i = 0; i < numProducts; i++) {
            Product product = new Product(
                                        scanner.next(), scanner.next(),
                                        scanner.nextDouble(), scanner.nextDouble(), scanner.nextDouble(),
                                        scanner.nextDouble()
            );
            double volumeWeight = product.getLength() * product.getWidth() * product.getHeight() / 6000.0;
            product.setWeight(Math.max(volumeWeight, product.getWeight()));
            products.add(product);
        }
        return products;
    }

    @Override
    public Flight inputFlight() {
        Flight flight = new Flight();
        flight.setNumber(scanner.next());
        flight.setBegin(scanner.next());
        flight.setEnd(scanner.next());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            flight.setDate(dateFormat.parse(scanner.next()));
        }
        catch (ParseException e) {
            System.out.println();
        }
        flight.setMaxCarryWeight(scanner.nextDouble());
        return flight;
    }

    @Override
    public String inputOrderId() {
        return scanner.next();
    }

    @Override
    public Date inputOrderDate() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return dateFormat.parse(scanner.next());
        } catch (ParseException e) {
            System.out.println();
            return null;
        }
    }

    @Override
    public Sender inputSender() {
        Sender sender = new Sender();
        sender.setAddress(scanner.next());
        sender.setName(scanner.next());
        sender.setPhone(scanner.next());
        return sender;
    }

    @Override
    public Recipient inputRecipient() {
        Recipient recipient = new Recipient();
        recipient.setAddress(scanner.next());
        recipient.setName(scanner.next());
        recipient.setPhone(scanner.next());
        return recipient;
    }

    @Override
    public PayType inputPayType() {
        String type = scanner.next();
        for(PayType payType : PayType.values()) {
            if(payType.name().equals(type)) {
                return payType;
            }
        }
        return PayType.Alipay;
    }
}


// 航班验证器接口
interface FlightValidator {
    boolean validateFlight(Flight flight, double totalWeight);
}


// 航班验证器接口实现类
class DefaultFlightValidator implements FlightValidator {
    @Override
    public boolean validateFlight(Flight flight, double totalWeight) {
        return flight.getMaxCarryWeight() >= totalWeight;
    }
}


// 订单管理器类
class OrderManager {
    // 字段区
    private final InputHandler inputHandler;
    private final FlightValidator flightValidator;

    // 构造区
    public OrderManager(InputHandler inputHandler, FlightValidator flightValidator) {
        this.inputHandler = inputHandler;
        this.flightValidator = flightValidator;
    }

    // 属性区

    // 方法区
    public Order createOrder() {
        Order order = new Order();
        // 输入客户信息
        order.setCustomer(inputHandler.inputCustomer());
        // 输入订单类型
        order.setType(inputHandler.inputOrderType());
        // 输入货物列表
        order.setProducts(inputHandler.inputProducts());
        // 计算总重量
        double totalWeight = 0;
        for (Product product : order.getProducts()) {
            totalWeight += product.getWeight();
        }
        order.setTotalWeight(totalWeight);
        // 输入航班信息
        Flight flight = inputHandler.inputFlight();
        // 是否超重
        if (!flightValidator.validateFlight(flight, totalWeight)) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flight.getNumber());
            System.exit(0);
        }
        order.setFlight(flight);
        // 输入订单编号
        order.setId(inputHandler.inputOrderId());
        // 输入订单日期
        order.setDate(inputHandler.inputOrderDate());
        // 输入寄件人信息
        order.setSender(inputHandler.inputSender());
        // 输入收件人信息
        order.setRecipient(inputHandler.inputRecipient());
        // 输入支付方式
        order.setPayType(inputHandler.inputPayType());

        return order;
    }
}


// 主函数
public class Main {
    public static void main(String[] args) {
        InputHandler inputHandler = new DefaultInputHandler();
        FlightValidator flightValidator = new DefaultFlightValidator();
        OrderManager orderManager = new OrderManager(inputHandler, flightValidator);
        Order order = orderManager.createOrder();
        order.show();
    }
}

5. 代码质量分析

image

一言难尽,我只能说这个SourceMonitor有待提高,或者说这次的代码确实不适合用这个工具做代码质量分析😡

下面是最终的代码类图(删去一部分无用代码)👀

image

这份代码的优点🤩:

  1. 单一职责原则:

每个类职责明确分离,Person只处理人员基本信息,Customer/Recipient/Sender各自处理特定角色OrderManager,专注订单创建流程,InputHandler只处理输入逻辑

  1. 开闭原则:

通过enum类型(如OrderType)管理分类,抽象类InputHandler和接口FlightValidator允许扩展新实现(如新增AdvancedInputHandler)而不修改原有代码

  1. 里氏代换原则:

DefaultInputHandler完全实现InputHandler抽象类的契约,DefaultFlightValidator严格遵循FlightValidator接口定义

  1. 合成复用原则:

优先使用组合,Customer/Recipient/Sender通过组合复用Person的功能(而非继承),Order组合了多个对象(Customer,Flight,Product等)

  1. 依赖倒置原则:

高层模块不依赖低层细节,OrderManager依赖抽象的InputHandler和FlightValidator通过构造函数注入具体实现(体现控制反转)

  1. 接口隔离原则:

接口职责单一,FlightValidator仅包含航班验证方法,没有出现臃肿的"上帝接口"

  1. 迪米特法则:

Order类通过customer.getName()获取客户名(而非直接访问person.name),Recipient/Sender对外仅暴露必要方法(如getName()),隐藏内部Person对象细节

  1. 组合优于继承原则:

Customer/Recipient/Sender通过组合复用Person功能,未使用继承实现角色分类

  1. 工厂方法模式:

InputHandler抽象类定义输入对象的创建接口DefaultInputHandler实现具体创建逻辑

  1. 关注点分离:

输入处理(InputHandler、业务逻辑(Order)、验证逻辑(FlightValidator))分离

6. 结束啦

可恶的Bolg终于写完了,总结:类设计好难!类设计好难!设计原则好难!设计原则好难!😭

posted @ 2025-05-25 17:31  白蓉  阅读(57)  评论(0)    收藏  举报