Java面向对象和MVC模式设计练习——快递管理系统

一、任务概述

本练习用于熟悉快递管理业务,完成快递管理控制台项目,具体需求如下:

采用二维数组的方式存储快递

  • 管理员
    • 录入快递
      • 柜子位置(系统产生,不能重复)
      • 快递单号(输入)
      • 快递公司(输入)
      • 6位取件码(系统产生,不能重复)
    • 删除快递(根据单号)
    • 修改快递(根据单号)
    • 查询所有快递(遍历)
  • 普通用户
    • 快递取出
      • 输入取件码:显示快递的信息 和 在哪个柜子中,从柜子中移除这个快递

功能实现如下:

存快递:
在这里插入图片描述
删除快递:
在这里插入图片描述
修改快递:
在这里插入图片描述

查询所有快递:
在这里插入图片描述

取件:
在这里插入图片描述

此习题来自 开课吧:《新职课JavaEE软件开发工程师》课程
第三章:面向对象
第6节:异常处理

二、MVC设计模式

1、定义

MVC模式(Model–view–controller)是软件工程中的一种软件架构模式,它把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
在这里插入图片描述
MVC模式中三个组件的详细介绍如下:

  • 模型(Model):用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“Model”有对数据直接访问的权力,例如对数据库的访问。“Model”不依赖“View”和“Controller”,也就是说, Model 不关心它会被如何显示或是如何被操作;
  • 视图(View):能够实现数据有目的的显示(理论上,这不是必需的)。在 View 中一般没有程序上的逻辑。为了实现 View 上的刷新功能,View 需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册;
  • 控制器(Controller):起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变。

2、优点

a、低耦合

通过将视图层和业务层分离,允许更改视图层代码而不必重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变,只需要改动MVC的模型层(及控制器)即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则。

由于运用 MVC 的应用程序的三个部件是相互独立,改变其中一个部件并不会影响其它两个,所以依据这种设计思想能构造出良好的松耦合的构件。

b、复用性强

随着技术的不断进步,当前需要使用越来越多的方式来访问应用程序了。MVC模式允许使用各种不同样式的视图来访问同一个服务端的代码,这得益于多个视图(如WEB(HTTP)浏览器或者无线浏览器(WAP))能共享一个模型。

比如,用户可以通过电脑或通过手机来订购某样产品,虽然订购的方式不一样,但处理订购产品的方式(流程)是一样的。由于模型返回的数据没有进行格式化,所以同样的构件能被不同的界面(视图)使用。例如,很多数据可能用 HTML 来表示,但是也有可能用 WAP 来表示,而这些表示的变化所需要的是仅仅是改变视图层的实现方式,而控制层和模型层无需做任何改变。

由于已经将数据和业务规则从表示层分开,所以可以最大化的进行代码重用了。

三、思路梳理

1、MVC架构设计

a、Controller

严格按照MVC模式来设计架构,Controller的作用是流程控制,用来将数据和视图连结。所以Controller中只包含逻辑判断代码,调用Model、View的代码,不包含任何业务运算代码和视图类的代码。

Controller中应当包含快递程序的主入口、判断分支后的各类方法。

b、Model

所有的业务内容,数据存储,数据修改方法等都在Model当中,不与View有任何的耦合关系。此练习只做面向对象练习,所以采用二维数组和快递类对象的方式来储存快递,不使用数据库。

c、View

此练习只做面向对象和MVC设计模式练习,用户交互页面使用打印和Scanner获取。View 中包含所有打印的方法和获取用户输入的方法,不包含任何逻辑和业务代码。

2、流程控制

首先考虑多层分支结构,获取用户输入的信息,分层判断

3、快递信息存储

创建Model类型的10×10的二维数据,来代替10×10的快递柜,可以存放100个快递对象。

快递使用对象的方式来存储,对象中包含快递单号、快递公司和取件码3个属性。

管理员存入快递时创建快递对象,其中快递单号和快递公司由管理员键盘输入并赋值给对象,取件码需要随机生成,同时保证单号和取件码与其他快递不重复。

4、随机生成取件码

采用Random类中的random(int bound)方法来随机生成6位取件码,bound为随机数的边界,当bound为100000时,会生成0~999999之间的随机数,如下:

Random random = new Random();
int code = random.nextInt(1000000);

但因为是int类型的随机数,所以有可能出现随机数不是6位数的情况,例如随机到了278,显然我们想要的是6位的“000278”,同时我们生活中很多常见的取件码都不以0作为开头,所以可以用如下方案生成100000~999999之间的取件码:

int code = random.nextInt(900000)+100000;

在生成取件码之后,我们还需要添加去重功能,同时用户需要通过取件码来查找快递,所以还需要编写一个方法,通过取件码来查询快递。还需要通过快递单号来查询快递的方法,用户管理员增删修改快递。

5、查找快递

a、按快递单号查找

需要编写一个方法,通过快递单号来查询二维数组中对应的快递对象,管理员添加快递输入单号时可以查重,删除修改和遍历快递也需要用此方法。

可以通过双层for循环,遍历二维数组,取出其中的快递对象,调用对象的get()方法,获取它的快递单号,来和我们给方法输入的单号做对比。

b、按取件码查找

与上同理,需要编写通过取件码查询快递的方法,用于用户取件和管理员存快递生成取件码时去重。

6、删除与修改

有了查找快递的方法之后,用户取件、快递员删除和修改快递,都可以先查找到对象,再在model类中对对象进行相应操作即可。主体思路框架有了,接下来开始撸代码。

四、任务拆解与代码

1、创建model、view、controller三个类

a、view

视图类,只包含Scanner对象,和各种打印方法。

import java.util.Scanner;

public class ExpressView {
    private final Scanner input = new Scanner(System.in);
    // 后续编写所有的视图打印方法
}

b、model

创建保存快递对象的二维数组,创建对象中的变量信息,String类型的快递单号,快递公司,和int类型的取件码,并为参数生成get和set方法(取件码只有get没有set方法),生成类的构造方法。

另外创建一个int类型的变量size,用来记录快递柜中已存入快递的数量。

public class ExpressModel {
    private ExpressModel express[][] = new ExpressModel[10][10];
    private String expressNumber;
    private String expressCompany;
    private int expressCode;
    private int size;

	public ExpressModel() {}
	public ExpressModel(String expressNumber, String expressCompany, int expressCode) {
        this.expressNumber = expressNumber;
        this.expressCompany = expressCompany;
        this.expressCode = expressCode;
    }

    public String getExpressNumber() {
        return expressNumber;
    }

    public void setExpressNumber(String expressNumber) {
        this.expressNumber = expressNumber;
    }

    public String getExpressCompany() {
        return expressCompany;
    }

    public void setExpressCompany(String expressCompany) {
        this.expressCompany = expressCompany;
    }

    public int getSize() {
        return size;
    }

    public int getExpressCode() {
        return expressCode;
    }
    // 后续编写业务方法
}

c、controller

Controller中需要调度view和model,创建实例对象。包含快递程序的一切流程,创建一个主方法作为快递功能的主入口。

public class ExpressController {
    ExpressView view = new ExpressView();
    ExpressModel model = new ExpressModel();

    public void expressMain() {}
}

2、视图类所有方法

视图方法可以随着Controller的编写逐个添加,这里为了方便下文阅读理解,提前列出视图类所有方法参阅。为保证程序正常运行,全部采用Scanner类中的nextLine()方法来接受用户输入的数据。

package pers.Enrico.ExpressMVC;

import java.util.Scanner;

public class ExpressView {
    private final Scanner input = new Scanner(System.in);

    public void printTitle() {
        System.out.println("快递单号\t快递公司\t取件码");
    }

    public void printExpress(ExpressModel express) {
        System.out.println(express.getExpressNumber()+"\t"+express.getExpressCompany()+"\t"+express.getExpressCode());
    }

    public void expressInEmpty() {
        System.out.println("当前快递柜内无快递。");
    }
    public void welcome() {
        System.out.println("\n===欢迎使用新职课快递柜===");
    }

    public void bye() {
        System.out.println("===欢迎再次使用新职课快递柜===");
    }

    public String getUser() {
        System.out.println("请输入您的身份:1-快递员,2-用户,0-退出");
        return input.nextLine();
    }

    public String getCourierOperation() {
        System.out.println("请选择操作:1-存快递 2-删除快递 3-修改快递信息 4-查看所有快递");
        return input.nextLine();
    }

    public String getExpressCode() {
        System.out.println("请输入取件码:");
        return input.nextLine();
    }

    public void getExpressOver() {
        System.out.println("取件成功!");
    }

    public void getExpressFail() {
        System.out.println("查无此取件码对应的快递,请检查取件码。");
    }

    public String getExpressNumber() {
        System.out.println("请输入快递单号:(输入0返回上一层)");
        return input.nextLine();
    }

    public String getExpressCompany() {
        System.out.println("请输入快递公司:");
        return input.nextLine();
    }

    public void addExpressOver(int code) {
        System.out.println("快递已成功存入!取件码为:"+code);
    }

    public void addExpressNumberError() {
        System.out.println("该单号的快递已存入,请检查快递单号。");
    }

    public void addExpressFull() {
        System.out.println("快递柜已满,无法存入");
    }

    public void updateExpressOver() {
        System.out.println("快递信息已修改,新信息如下:");
    }

    public void delExpressOver() {
        System.out.println("快递删除成功!");
    }

    public void expressNotExist() {
        System.out.println("该单号的快递不存在,请检查快递单号。");
    }

    public void reInput() {
        System.out.println("您输入的信息有误,请重新输入。");
    }
}

3、主流程控制

Controller中只包含流程控制代码,实现视图功能需要调用view中的方法,修改数据功能需要调用model中的方法,具体方法之后逐步完善。

快递主入口,包含欢迎语和结束语,以及获取用户输入的信息来确定用户身份的方法,做进一步分支流程判断。

调用view.getUser()方法,提示用户输入身份信息: 0:退出 1:管理员 2:普通用户

将用户输入的数据转化为int类型,并用以判断用户身份。稍后完善changeStringToInt()方法。

public class ExpressController {
    ExpressView view = new ExpressView();
    ExpressModel model = new ExpressModel();

	public void expressMain() {
	    while (true) {
	        view.welcome();
	        // 调用view.getUser()方法,提示用户输入身份信息:
	        // 将用户输入的数据转化为int类型,并用以判断用户身份。
	        int user = model.changeStringToInt(view.getUser());
	        // 下一步完善changeStringToInt()方法,并写进一步分支流程
	    }
	}
}

完善model类中的changeStringToInt()方法,将用户输入的数据转换为int类型,类型不匹配时返回-1,在Controller中用view.reInput()方法提示用户重新输入。

// Model类中
public int changeStringToInt(String s) {
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        return -1;
    }
}

4、判断用户身份

我们通过model.changeStringToInt(view.getUser())获取了用户输入的信息,返回值为0(退出)、1(管理员)、2(普通用户)或其他数字,通过返回值写下一步分支流程,完成主流程控制部分的代码编写。

// Controller类中
public void expressMain() {
    while (true) {
        view.welcome();
        // 将用户输入的数据转化为int类型,并用以判断用户身份。
        int user = model.changeStringToInt(view.getUser());
        // 用户输入0,打印结束语,通过break跳出循环,结束整个快递程序方法。
        if (user == 0) {
            view.bye();
            break;
        }
        // 用户为快递管理员,让管理员进一步输入指令,转化为int类型,指引进一步操作。
        else if (user == 1) {
        	// 下一步完善管理员进一步操作的分支方法courierOperation(int num)
            courierOperation(model.changeStringToInt(view.getCourierOperation()));
        }
        // 用户为普通用户,让用户输入取件码,进行取件操作。
        else if (user == 2) {
        	// 稍后完善用户通过取件码取件的方法userOperation(int code)
            userOperation(model.changeStringToInt(view.getExpressCode()));
        } else {
            view.reInput();
        }
    }
}

5、管理员分支判断

当管理员输入对应的数字后,分支判断管理员不同的增删改查操作。

// Controller类中
public void courierOperation(int courierOperationNum) {
    if (courierOperationNum == 1) {
        addPress();
    }else if (courierOperationNum == 2) {
        delPress();
    } else if (courierOperationNum == 3) {
        updatePress();
    } else if (courierOperationNum == 4) {
        showAllPress();
    } else {
        view.reInput();
    }
}

6、查找快递

快递员添加快递的时候,需要输入快递单号,但是为了保证存入的快递单号不重复,我们需要在快递员输入单号后先做去重。

具体做法是需要编写一个通过快递单号查找快递对象的方法,后续删除快递、修改快递都需要。因为不属于流程控制,而属于与数据相关的业务代码,固需要写在model类当中。

通过两层for循环来遍历存放快递对象的二维数组,取出每个快递对象后调用它的getExpressNumber()方法获取其快递单号,和输入的快递单号作比较,如果相等,说明通过该单号找到了快递,则返回该快递对象,反之则返回null。

// Model类中
public ExpressModel findExpressByNumber(String expressNumber) {
    for (int x=0;x<10;x++) {
        for (int y=0;y<10;y++) {
        	// 需要先判断express[x][y]!=null,否则空对象调用get方法时会报空指针异常
            if (express[x][y]!=null && Objects.equals(express[x][y].getExpressNumber(), expressNumber)) {
                return express[x][y];
            }
        }
    }
    return null;
}

同样的,当用户取件的时候并不知道快递单号,而是通过取件码来查找快递。同时快递员存入快递时系统会随机生成6位取件码,也需要去重功能。所以还需要写一个通过取件码来查找快递的方法,与上方法同理。

// Model类中
public ExpressModel findExpressByCode(int code) {
    for (int x=0;x<10;x++) {
        for (int y=0;y<10;y++) {
            if (express[x][y]!=null && express[x][y].getExpressCode() == code) {
                return express[x][y];
            }
        }
    }
    return null;
}

最后,管理员选择查看所有快递信息时,并不会手动输入每一个快递单号,而需要两层循环遍历整个数组,需要通过角标获取快递对象的方法。

// Model类中
public ExpressModel getExpressByIndex(int x, int y) {
    return express[x][y];
}

我们在Model中可以直接通过express[x][y]获取快递对象,为什么要这样“多此一举”写一个方法返回此对象?因为管理员需要查询所有快递,而Model中的二维数组express[][]是private修饰的。

7、查询所有快递

先判断快递柜内是否有快递,通过两层循环遍历二维数组,当对象不为空时打印出该对象。

为什么不把此方法写在Model中作为一个业务功能,因为在循环的过程中需要调用view视图类中的方法来打印对象,而MVC模式中Model与View是严格剥离的,只能通过Controller粘合在一起,所以此功能应属于流程控制类中的方法。

// Controller类中
public void showAllPress() {
    if (model.getSize() == 0) {
        view.expressInEmpty();
    } else {
        // 打印表头
        view.printTitle();
        // 遍历打印所有快递对象
        for (int x = 0; x < 10; x++) {
            for (int y = 0; y < 10; y++) {
                // 通过getByIndex方法获取x,y下标对应的对象
                ExpressModel expressTemp = model.getExpressByIndex(x, y);
                // 打印出所有不为空的对象
                if (expressTemp != null) {
                    view.printExpress(expressTemp);
                }
            }
        }
    }
}

8、存储快递

a、快递单号去重

管理员存储快递,首先需要输入快递单号,需要有去重功能,而且修改快递信息时,也需要输入快递单号。所以单独写一个通用的让管理员输入不重复的快递单号的方法。

获取管理员输入的单号后,调用通过单号查找快递的方法,如果能查找到快递,说明此单号以存在,提示管理员重新输入,反之说明此单号不存在,直接返回此单号。

// Controller类中
public String getUniqueNumber() {
    while (true) {
        String expressNumber = view.getExpressNumber();
        if (model.findExpressByNumber(expressNumber) != null) {
            view.addExpressNumberError();
        } else {
            return expressNumber;
        }
    }
}

b、存储快递主流程

跳出此循环方法只能输入正确的不重复的快递单号,而有可能存在管理员忘记单号或点错进入此方法的可能,所以出现上述情况时提示管理员输入0,让方法返回0,在下一步流程控制中对单号是否为0做进一步流程判断。

编写储存快递的流程控制方法,先获取单号,然后提供管理员输入0时跳出方法的出口。如果正常获取单号,需要检查快递柜(二维数组)是否还有空位。如果有空位,需要进一步提示管理员获取快递公司。

有了这些信息后,需要与数据信息对接了,数据业务的代码需要写在model当中,通过model中添加快递的方法,把单号和快递公司的信息传入。

储存好快递后,需要在控制台提示管理员快递存入成功,并显示生成的取件码。下一步完善model.addExpress(String numer, String company)方法。

// Controller类中
public void addPress() {
    // 管理员通过getUniqueNumber方法输入要添加的快递单号,确保单号是唯一的
    String expressNumber = getUniqueNumber();
    // 管理员按错或者忘记单号时输入0,提供跳出方法的出口
    if (Objects.equals(expressNumber, "0")) {
        return;
    }
    // 判断快递柜是否已满
    if (model.getSize() == 100) {
        view.addExpressFull();
    } else {
        // 调用addExpress方法,将快递单号和快递公司信息传入
        ExpressModel expressTemp = model.addExpress(expressNumber, view.getExpressCompany());
        // 取出addExpress方法中生成的取件码
        int codeTemp = expressTemp.getExpressCode();
        view.addExpressOver(codeTemp);
    }
}

c、数据修改与存储

当我们调用这个方法传入快递单号和公司的时候,已经确定了二维数组是有空位的,通过对二维数组的两个角标取随机数,获取一个随机的快递抽屉,然后判断当这个抽屉为空的时候,随机生成一个取件码,然后通过有参构造方法创建一个快递对象,存入这个快递抽屉当中,同时需要给快递数量size变量+1。下一步完善随机生成取件码的方法。

// Model类中
public ExpressModel addExpress(String expressNumber, String expressCompany) {
    while (true) {
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        if (express[x][y] == null) {
            express[x][y] = new ExpressModel(expressNumber, expressCompany, randomCode());
            size++;
            return express[x][y];
        }
    }
}

d、随机生成取件码

思路梳理中,我们已经讨论了随机生成100000~999999六位取件码并去重的方法,不再赘述,上代码。

// Model类中
public int randomCode() {
    while (true) {
        int code = random.nextInt(900000)+100000;
        if (findExpressByCode(code) == null) {
            return code;
        }
    }
}

9、删除快递

while循环让管理员输入匹配的快递单号,同样提供无法匹配时跳出循环的出口。同样设计数据业务,需要在Model类中编写方法。通过调用Model类中的delExpress(String expressNumber)方法来删除快递,下一步完善。

// Controller类中
public void delPress() {
    while (true) {
        // 管理员输入要删除的快递单号
        String expressNumber = view.getExpressNumber();
        // 当快递柜内无快递或管理员忘记快递单号时,提供跳出循环的出口
        if (Objects.equals(expressNumber, "0")) {
            break;
        }
        // 通过delExpress方法来判断该单号是否存在于快递柜中,如果存在则删除快递
        if (model.delExpress(expressNumber)) {
            view.delExpressOver();
            break;
        } else {
            view.expressNotExist();
        }
    }
}

这里不在Controller中采用findByNumber()方法来查重,因为这种方法我们查到快递对象后,只能得到方法返回的对象,我们无法通过令对象为null的方法来删除快递,这种方法只是令二维数组这个地址指引的某个具体实例化对象为null,无法从源头上让这个快递抽屉为空。只能通过遍历查找二维数组后,通过角标来定位数组中的位置,用express[x][y] = null来实现删除快递的功能。

// Model类中
public boolean delExpress(String expressNumber) {
    for (int x=0;x<10;x++) {
        for (int y=0;y<10;y++) {
            if (express[x][y]!=null && Objects.equals(express[x][y].getExpressNumber(), expressNumber)) {
                express[x][y] = null;
                size--;
                return true;
            }
        }
    }
    return false;
}

10、用户取快递

我们在进入快递系统主流程判断用户身份的时候,如果用户输入的是2(普通用户),则让用户输入取件码,进入userOperation(int code)方法

else if (user == 2) {
    userOperation(model.changeStringToInt(view.getExpressCode()));
}

下面完善该流程控制方法,当输入非int类型时,changeStringToInt方法会返回-1,此时进入view.reInput()方法,提示用户输入有误。当用户输入int类型的取件码时,调用Model类中的gerExpress(code)方法来取件,下一步完善该方法。

// Controller类中
public void userOperation(int code) {
    if (code == -1) {
        view.reInput();
    } else {
        if (model.getExpress(code)) {
            view.getExpressOver();
        } else {
            view.getExpressFail();
        }
    }
}

用户取快递和管理员删除快递其实是非常近似的,只不过一个是通过快递单号,一个是通过取件码。既然如此,我们就可以复用Model类中的delExpress(String number)方法来实现用户取快递。

先通过用户输入的取件码,调用findExpressByCode(int code)方法查找快递,如果不存在该取件码对应的快递则返回null对象。如果找到了快递对象,通过get方法取出快递单号,然后调用管理员通过单号删除快递的方法即可。

// Model类中
public boolean getExpress(int expressCode) {
    ExpressModel express = findExpressByCode(expressCode);
    if (express == null) {
        return false;
    } else {
        return delExpress(express.getExpressNumber());
    }
}

11、修改快递

先让管理员输入要修改的快递单号,同样提供跳出循环的出口。然后通过该单号查找快递,如果为空则提示用户核实快递单号,不为空则获取该快递对象,然后调用getUniqueNumber()方法提示管理员重新输入要修改的去重的快递单号,之后输入快递公司,然后再把该对象、修改后的单号和公司信息传给Model类中的updateExpress(express, expressNumber, expressCompany)方法,来修改快递信息。下一步完善该方法。

// Controller类中
public void updatePress() {
    while (true) {
        // 管理员输入要修改的快递单号
        String expressNumberTemp = view.getExpressNumber();
        // 当快递柜内无快递或管理员忘记快递单号时,提供跳出循环的出口
        if (Objects.equals(expressNumberTemp, "0")) {
            break;
        }
        // 通过findExpressByNumber方法来判断该单号是否存在于快递柜中
        ExpressModel express = model.findExpressByNumber(expressNumberTemp);
        // 该单号的快递在快递柜中,进一步修改快递信息
        if (express != null) {
            // 打印修改前的快递信息
            view.printTitle();
            view.printExpress(express);
            // 管理员输入修改后的快递单号
            String expressNumber = getUniqueNumber();
            if (Objects.equals(expressNumberTemp, "0")) {
                break;
            }
            // 管理员输入修改后的快递公司
            String expressCompany = view.getExpressCompany();
            model.updateExpress(express, expressNumber, expressCompany);
            view.updateExpressOver();
            view.printTitle();
            view.printExpress(express);
            break;
        // 该单号的快递不在快递柜中,需要重新进入循环获取正确的单号
        } else {
            view.expressNotExist();
        }
    }
}

获得管理员要修改的对象和修改后的单号和公司后,使用set方法来修改快递对象的参数即可

public void updateExpress(ExpressModel express, String expressNumber, String expressCompany) {
    express.setExpressNumber(expressNumber);
    express.setExpressCompany(expressCompany);
}

至此,快递系统所有功能以完善。

五、完整代码

1、View

package pers.Enrico.ExpressMVC;

import java.util.Scanner;

public class ExpressView {
    private final Scanner input = new Scanner(System.in);

    public void printTitle() {
        System.out.println("快递单号\t快递公司\t取件码");
    }

    public void printExpress(ExpressModel express) {
        System.out.println(express.getExpressNumber()+"\t"+express.getExpressCompany()+"\t"+express.getExpressCode());
    }

    public void expressInEmpty() {
        System.out.println("当前快递柜内无快递。");
    }
    public void welcome() {
        System.out.println("\n===欢迎使用新职课快递柜===");
    }

    public void bye() {
        System.out.println("===欢迎再次使用新职课快递柜===");
    }

    public String getUser() {
        System.out.println("请输入您的身份:1-快递员,2-用户,0-退出");
        return input.nextLine();
    }

    public String getCourierOperation() {
        System.out.println("请选择操作:1-存快递 2-删除快递 3-修改快递信息 4-查看所有快递");
        return input.nextLine();
    }

    public String getExpressCode() {
        System.out.println("请输入取件码:");
        return input.nextLine();
    }

    public void getExpressOver() {
        System.out.println("取件成功!");
    }

    public void getExpressFail() {
        System.out.println("查无此取件码对应的快递,请检查取件码。");
    }

    public String getExpressNumber() {
        System.out.println("请输入快递单号:(输入0返回上一层)");
        return input.nextLine();
    }

    public String getExpressCompany() {
        System.out.println("请输入快递公司:");
        return input.nextLine();
    }

    public void addExpressOver(int code) {
        System.out.println("快递已成功存入!取件码为:"+code);
    }

    public void addExpressNumberError() {
        System.out.println("该单号的快递已存入,请检查快递单号。");
    }

    public void addExpressFull() {
        System.out.println("快递柜已满,无法存入");
    }

    public void updateExpressOver() {
        System.out.println("快递信息已修改,新信息如下:");
    }

    public void delExpressOver() {
        System.out.println("快递删除成功!");
    }

    public void expressNotExist() {
        System.out.println("该单号的快递不存在,请检查快递单号。");
    }

    public void reInput() {
        System.out.println("您输入的信息有误,请重新输入。");
    }
}

2、Model

package pers.Enrico.ExpressMVC;

import java.util.Objects;
import java.util.Random;

public class ExpressModel {
    private String expressNumber;
    private String expressCompany;
    private int expressCode;
    private ExpressModel express[][] = new ExpressModel[10][10];
    private int size;
    Random random = new Random();

    public int changeStringToInt(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    public ExpressModel findExpressByNumber(String expressNumber) {
        for (int x=0;x<10;x++) {
            for (int y=0;y<10;y++) {
                if (express[x][y]!=null && Objects.equals(express[x][y].getExpressNumber(), expressNumber)) {
                    return express[x][y];
                }
            }
        }
        return null;
    }

    public ExpressModel findExpressByCode(int code) {
        for (int x=0;x<10;x++) {
            for (int y=0;y<10;y++) {
                if (express[x][y]!=null && express[x][y].getExpressCode() == code) {
                    return express[x][y];
                }
            }
        }
        return null;
    }

    public ExpressModel getExpressByIndex(int x, int y) {
        return express[x][y];
    }

    public boolean getExpress(int expressCode) {
        ExpressModel express = findExpressByCode(expressCode);
        if (express == null) {
            return false;
        } else {
            return delExpress(express.getExpressNumber());
        }
    }

    public ExpressModel addExpress(String expressNumber, String expressCompany) {
        while (true) {
            int x = random.nextInt(10);
            int y = random.nextInt(10);
            if (express[x][y] == null) {
                express[x][y] = new ExpressModel(expressNumber, expressCompany, randomCode());
                size++;
                return express[x][y];
            }
        }
    }

    public int randomCode() {
        while (true) {
            int code = random.nextInt(900000)+100000;
            if (findExpressByCode(code) == null) {
                return code;
            }
        }
    }

    public boolean delExpress(String expressNumber) {
        for (int x=0;x<10;x++) {
            for (int y=0;y<10;y++) {
                if (express[x][y]!=null && Objects.equals(express[x][y].getExpressNumber(), expressNumber)) {
                    express[x][y] = null;
                    size--;
                    return true;
                }
            }
        }
        return false;
    }

    public void updateExpress(ExpressModel express, String expressNumber, String expressCompany) {
        express.setExpressNumber(expressNumber);
        express.setExpressCompany(expressCompany);
    }

    public ExpressModel(String expressNumber, String expressCompany, int expressCode) {
        this.expressNumber = expressNumber;
        this.expressCompany = expressCompany;
        this.expressCode = expressCode;
    }

    public ExpressModel() {}

    public String getExpressNumber() {
        return expressNumber;
    }

    public void setExpressNumber(String expressNumber) {
        this.expressNumber = expressNumber;
    }

    public String getExpressCompany() {
        return expressCompany;
    }

    public void setExpressCompany(String expressCompany) {
        this.expressCompany = expressCompany;
    }

    public int getSize() {
        return size;
    }

    public int getExpressCode() {
        return expressCode;
    }

    public void setExpressCode(int expressCode) {
        this.expressCode = expressCode;
    }
}

3、Controller

package pers.Enrico.ExpressMVC;

import java.util.Objects;

public class ExpressController {
    ExpressView view = new ExpressView();
    ExpressModel model = new ExpressModel();

    /**
     * 快递柜的主程序,包含欢迎、流程控制、和结束语。
     */
    public void expressMain() {
        while (true) {
            view.welcome();
            // 将用户输入的数据转化为int类型,并用以判断用户身份。
            int user = model.changeStringToInt(view.getUser());
            if (user == 0) {
                view.bye();
                break;
            }
            // 用户为快递管理员,让管理员进一步输入指令,转化为int类型,指引进一步操作。
            else if (user == 1) {
                courierOperation(model.changeStringToInt(view.getCourierOperation()));
            }
            // 用户为普通用户,让用户输入取件码,进行取件操作。
            else if (user == 2) {
                userOperation(model.changeStringToInt(view.getExpressCode()));
            } else {
                view.reInput();
            }
        }
    }

    /**
     * 用户取件的方法
     * @param code 让用户输入的取件码
     */
    public void userOperation(int code) {
        if (code == -1) {
            view.reInput();
        } else {
            if (model.getExpress(code)) {
                view.getExpressOver();
            } else {
                view.getExpressFail();
            }
        }
    }

    /**
     * 快递管理员执行进一步操作的指令分流方法。
     * @param courierOperationNum 管理员输入的操作指令
     */
    public void courierOperation(int courierOperationNum) {
        if (courierOperationNum == 1) {
            addPress();
        }else if (courierOperationNum == 2) {
            delPress();
        } else if (courierOperationNum == 3) {
            updatePress();
        } else if (courierOperationNum == 4) {
            showAllPress();
        } else {
            view.reInput();
        }
    }

    /**
     * 管理员添加快递的方法
     */
    public void addPress() {
        // 管理员通过getUniqueNumber方法输入要添加的快递单号,确保单号是唯一的
        String expressNumber = getUniqueNumber();
        if (Objects.equals(expressNumber, "0")) {
            return;
        }
        // 判断快递柜是否已满
        if (model.getSize() == 100) {
            view.addExpressFull();
        } else {
            // 调用addExpress方法,将快递单号和快递公司信息传入
            ExpressModel expressTemp = model.addExpress(expressNumber, view.getExpressCompany());
            // 取出addExpress方法中生成的取件码
            int codeTemp = expressTemp.getExpressCode();
            view.addExpressOver(codeTemp);
        }
    }

    /**
     * 管理员删除快递的方法
     */
    public void delPress() {
        while (true) {
            // 管理员输入要删除的快递单号
            String expressNumber = view.getExpressNumber();
            // 当快递柜内无快递或管理员忘记快递单号时,提供跳出循环的出口
            if (Objects.equals(expressNumber, "0")) {
                break;
            }
            // 通过delExpress方法来判断该单号是否存在于快递柜中
            if (model.delExpress(expressNumber)) {
                view.delExpressOver();
                break;
            } else {
                view.expressNotExist();
            }
        }
    }

    /**
     * 管理员修改快递的方法
     */
    public void updatePress() {
        while (true) {
            // 管理员输入要修改的快递单号
            String expressNumberTemp = view.getExpressNumber();
            // 当快递柜内无快递或管理员忘记快递单号时,提供跳出循环的出口
            if (Objects.equals(expressNumberTemp, "0")) {
                break;
            }
            // 通过findExpressByNumber方法来判断该单号是否存在于快递柜中
            ExpressModel express = model.findExpressByNumber(expressNumberTemp);
            // 该单号的快递在快递柜中,进一步修改快递信息
            if (express != null) {
                // 打印修改前的快递信息
                view.printTitle();
                view.printExpress(express);
                // 管理员输入修改后的快递单号
                String expressNumber = getUniqueNumber();
                if (Objects.equals(expressNumberTemp, "0")) {
                    break;
                }
                // 管理员输入修改后的快递公司
                String expressCompany = view.getExpressCompany();
                model.updateExpress(express, expressNumber, expressCompany);
                view.updateExpressOver();
                view.printTitle();
                view.printExpress(express);
                break;
            // 该单号的快递不在快递柜中,需要重新进入循环获取正确的单号
            } else {
                view.expressNotExist();
            }
        }
    }

    /**
     * 管理员查看所有快递的方法
     */
    public void showAllPress() {
        if (model.getSize() == 0) {
            view.expressInEmpty();
        } else {
            // 打印表头
            view.printTitle();
            // 遍历打印所有快递对象
            for (int x = 0; x < 10; x++) {
                for (int y = 0; y < 10; y++) {
                    // 通过findByIndex方法获取x,y下标对应的对象
                    ExpressModel expressTemp = model.getExpressByIndex(x, y);
                    // 打印出所有不为空的对象
                    if (expressTemp != null) {
                        view.printExpress(expressTemp);
                    }
                }
            }
        }
    }

    /**
     * 让管理员输入不重复的快递单号
     * @return 返回唯一的不重复的快递单号
     */
    public String getUniqueNumber() {
        while (true) {
            String expressNumber = view.getExpressNumber();
            if (model.findExpressByNumber(expressNumber) != null) {
                view.addExpressNumberError();
            } else {
                return expressNumber;
            }
        }
    }
}

4、Test

package pers.Enrico.ExpressMVC;

public class ExpressTest {
    public static void main(String[] args) {
        ExpressController controller = new ExpressController();
        controller.expressMain();
    }
}

六、测试代码

在这里插入图片描述


版权声明:本文为WE_Enrico原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。