首页 > Java > java教程 > 正文

Java MVC模式实践:构建清晰、可维护的应用程序

碧海醫心
发布: 2025-11-08 23:38:01
原创
869人浏览过

java mvc模式实践:构建清晰、可维护的应用程序

本文深入探讨Java中MVC模式的正确实践,通过分析一个餐厅管理系统案例,揭示视图层(View)和控制器层(Controller)常见的职责混淆问题。我们将详细阐述模型、视图、控制器的核心职责,并提供具体的代码重构示例,旨在帮助开发者实现更严格的职责分离,提升代码的可维护性、可测试性及UI灵活性,并探讨异常处理的最佳实践。

1. MVC模式概述

Model-View-Controller(MVC)是一种软件架构模式,旨在将应用程序的业务逻辑、数据和用户界面分离开来。这种分离有助于提高代码的模块化、可维护性和可扩展性。

  • Model(模型):负责管理应用程序的数据和业务逻辑。它独立于用户界面,处理数据的存储、检索、处理和验证。模型通常包含数据结构(如DailyMenu、MenuItem)和与数据操作相关的服务(如DailyMenuServices)。
  • View(视图):负责显示模型中的数据,并接收用户的输入。视图是用户界面的表示,它不包含任何业务逻辑,只负责数据的渲染和用户交互的收集。在命令行应用中,视图负责打印提示信息和读取用户输入。
  • Controller(控制器):作为模型和视图之间的协调者。它接收并解析用户的输入(来自视图),调用相应的业务逻辑(通过模型或服务),然后更新模型,并指示视图显示更新后的结果。控制器是应用程序的“大脑”,决定了如何响应用户操作。

2. 初始实现中的MVC模式误区分析

在项目初期,开发者常会不自觉地将不同层次的职责混淆,导致代码耦合度高,难以维护。以下是一个餐厅管理系统初始实现中常见的几个问题:

2.1 视图层(MenuView)包含业务逻辑

在原始的MenuView实现中,存在如下问题:

立即学习Java免费学习笔记(深入)”;

// 原始MenuView片段
public DailyMenu getMenuTypes(Menu menu){;
    menu(); // 打印菜单选项
    int option = Integer.parseInt(scanner.nextLine()); // 获取用户输入
    MenuTypes menuTypes = MenuTypes.get(option-1);
    switch (menuTypes){ // 包含业务决策逻辑
        case FOODMENU -> {return getFoodMenuTypes(menu.getFoodMenu());}
        case DRINKMENU -> {return getDrinkMenuTypes(menu.getDrinkMenu());}
        default -> {return null;}
    }
}
// 类似地,getFoodMenuTypes 和 getDrinkMenuTypes 也包含 switch 逻辑
登录后复制

问题分析: 视图的职责是显示信息和收集用户输入,但上述代码中的getMenuTypes方法不仅显示了菜单选项并获取输入,还包含了根据用户选择进行业务决策switch语句)和数据导航(menu.getFoodMenu())。这种逻辑属于控制器或服务层,将其放在视图中会造成:

  1. 职责不清晰:视图不再是纯粹的UI层。
  2. 维护困难:如果UI需要从命令行改为图形界面(Swing/JavaFX),这些业务逻辑也需要重写,而它们本应独立于UI。
  3. 测试复杂:难以对视图中的业务逻辑进行单元测试,因为它们与UI输入输出紧密耦合。

2.2 服务层(DailyMenuServicesImpl)与视图层耦合

虽然提供的DailyMenuServicesImpl代码片段中没有直接的打印输出,但在实际开发中,服务层有时会错误地包含UI相关的输出逻辑,例如:

// 假设DailyMenuServicesImpl中存在类似代码(反例)
public void updateMenu(DailyMenu dailyMenu,MenuItem updateMenuItem,String itemName) {
    // menuPrinter.printMenu(dailyMenu); // 错误:服务层不应进行UI打印
    dailyMenu.getMenuItemList().forEach(/* ... */);
}
登录后复制

问题分析: 服务层(Model的一部分)应专注于业务逻辑的实现,而不应关心数据如何展示给用户。menuPrinter.printMenu()是一个典型的视图操作。将UI打印逻辑嵌入服务层,会破坏模型层的独立性,使其难以在不同的UI环境或无UI场景下复用。

2.3 主方法(Main)中的直接协调

在初始的Main方法中,它直接实例化MenuView和DailyMenuServicesImpl,并根据用户输入直接调用它们的方法来执行操作:

// 原始Main方法片段
public static void menuMain(Menu menu) throws IOException{
    // ...
    DailyMenuServices dailyMenuServices = new DailyMenuServicesImpl();
    MenuView menuView = new MenuView();
    // ...
    switch (actions) {
        case CREATE -> {
            MenuItem menuItem = menuView.createMenuItem();
            DailyMenu dailyMenu = menuView.getMenuTypes(menu); // 视图中包含逻辑
            dailyMenuServices.addMenuItemsToMenu(dailyMenu,menuItem);
        }
        // ...
    }
}
登录后复制

问题分析: Main方法在此充当了一个隐式的控制器,但这种做法缺乏结构性。它直接处理用户输入、调用视图获取数据、再调用服务执行业务,导致Main方法变得臃肿且职责不清。一个成熟的MVC应用应该有一个明确的控制器类来承担这些协调职责。

3. 重构实践:构建职责分明的MVC组件

为了解决上述问题,我们需要对代码进行重构,严格遵循MVC的职责分离原则。

3.1 控制器(Controller)的核心作用

控制器是MVC模式的“胶水”,负责接收用户输入,将其转化为对模型(服务)的操作,并最终选择合适的视图来呈现结果。

重构后的MenuControllers示例

无阶未来模型擂台/AI 应用平台
无阶未来模型擂台/AI 应用平台

无阶未来模型擂台/AI 应用平台,一站式模型+应用平台

无阶未来模型擂台/AI 应用平台 35
查看详情 无阶未来模型擂台/AI 应用平台
public class MenuControllers {
    private final MenuView view;
    private final DailyMenuServices  dailyMenuServices;
    private final MenuFileHandlingServices menuFileHandlingServices ;

    // 依赖注入:通过构造函数获取视图和服务实例
    public MenuControllers(){
        this.view  = MenuView.getInstance(); // 使用单例获取视图实例
        this.dailyMenuServices = DailyMenuServicesImpl.getInstance(); // 使用单例获取服务实例
        this.menuFileHandlingServices = MenuFileHandlingServicesImpl.getInstance();
    }

    public void add(Menu menu){
        MenuItem  menuItem = view.createMenuItem(); // 视图只负责获取原始数据
        int option = view.getMenuTypes(); // 视图只返回用户选择的整数
        MenuTypes menuTypes = MenuTypes.get(option-1); // 控制器解析用户选择

        switch (menuTypes){ // 控制器包含业务决策逻辑
            case FOODMENU -> addToFoodMenu(menu.getFoodMenu(),menuItem);
            case DRINKMENU -> addToDrinkMenu(menu.getDrinkMenu(),menuItem);
            default -> System.out.println("Invalid menu type selected."); // 错误提示也可以通过View
        }
    }

    // 辅助方法,将具体操作进一步细化
    public void addToFoodMenu(FoodMenu foodMenu, MenuItem menuItem){
        int option = view.getFoodMenuTypes();
        FoodMenuTypes foodMenuTypes = FoodMenuTypes.get(option-1);
        switch (foodMenuTypes){
            case BREAKFASTMENU -> dailyMenuServices.addMenuItemsToMenu(foodMenu.getBreakfastMenu(),menuItem);
            case LUNCHMENU -> dailyMenuServices.addMenuItemsToMenu(foodMenu.getLunchMenu(),menuItem);
            case DINNERMENU -> dailyMenuServices.addMenuItemsToMenu(foodMenu.getDinnerMenu(),menuItem);
            default -> System.out.println("Invalid food menu type selected.");
        }
    }
    // ... 其他 update, delete, showMenu 等方法类似
    public void showMenu(Menu menu){
        view.printMenu(menu); // 控制器指示视图显示菜单
    }
    // ... 文件操作也由控制器协调
}
登录后复制

关键点

  • MenuControllers通过构造函数接收MenuView和DailyMenuServices的实例,实现了依赖注入,降低了耦合。
  • 控制器从MenuView获取原始的用户输入(如整数选项、字符串),然后由控制器来解析这些输入,并根据解析结果执行相应的业务逻辑。
  • 所有的switch决策逻辑都从视图移到了控制器中,使得控制器成为业务流程的协调者。
  • 控制器在完成业务操作后,会指示视图进行相应的显示(例如view.printMenu(menu))。

3.2 视图(View)的纯粹化

重构后的视图层应该尽可能地“哑巴”,只负责显示信息和收集原始的用户输入,不包含任何业务决策逻辑。

重构后的MenuView示例

public class MenuView {
    private Scanner scanner = new Scanner(System.in);
    private final MenuPrinter menuPrinter = MenuPrinterImpl.getInstance(); // 视图依赖打印器

    // 单例模式
    private MenuView(){ }
    public static MenuView getInstance(){
        return MenuViewHelper.menuView;
    }
    private static class MenuViewHelper{
        private static final MenuView menuView = new MenuView();
    }

    public int getMenuTypes(){
        menu(); // 仅打印菜单选项
        return Integer.parseInt(scanner.nextLine()); // 仅返回用户选择的整数
    }
    public int getFoodMenuTypes(){
        foodMenu();
        return Integer.parseInt(scanner.nextLine());
    }
    public int getDrinkMenuTypes(){
        drinkMenu();
        return Integer.parseInt(scanner.nextLine());
    }
    // createMenuItem 和 getMenuItemName 负责收集用户输入并返回数据对象或字符串
    public MenuItem createMenuItem(){ /* ... */ return menuItem; }
    public String getMenuItemName(){ /* ... */ return scanner.nextLine(); }

    public void printMenu(Menu menu){ // 视图通过打印器来显示菜单
        menuPrinter.printMenu(menu);
    }

    // 静态方法用于打印具体的菜单提示信息
    public static void menu(){ /* ... */ }
    public static void drinkMenu(){ /* ... */ }
    public static void foodMenu(){ /* ... */ }
}
登录后复制

关键点

  • MenuView中的getMenuTypes、getFoodMenuTypes等方法现在只负责打印提示信息并返回用户输入的原始整数值,不再包含switch语句或任何业务决策。
  • 视图通过MenuPrinter接口来执行实际的打印操作,进一步解耦了UI的渲染逻辑。
  • 视图的职责被严格限制在“输入”和“输出”上。

3.3 模型与服务层(Model/Service)的独立性

模型层(包括数据结构和服务)应该完全独立于UI,专注于数据管理和业务逻辑的实现。

重构后的DailyMenuServicesImpl示例

public class DailyMenuServicesImpl implements DailyMenuServices {

    // 单例模式
    private DailyMenuServicesImpl(){}
    public static DailyMenuServicesImpl getInstance(){
        return DailyMenuServicesImplHelper.dailyMenuServicesImpl;
    }
    private static class DailyMenuServicesImplHelper{
        private static final DailyMenuServicesImpl dailyMenuServicesImpl = new DailyMenuServicesImpl();
    }

    @Override
    public void addMenuItemsToMenu(DailyMenu dailyMenu,MenuItem menuItem) {
        List<MenuItem> menuItemList = dailyMenu.getMenuItemList();
        menuItemList.add(menuItem);
    }

    @Override
    public void updateMenu(DailyMenu dailyMenu,MenuItem updateMenuItem,String itemName) {
        // 服务层只执行业务逻辑,不进行任何UI打印
        dailyMenu.getMenuItemList().stream()
                                    .filter(menuItem -> menuItem.getNames().equals(itemName))
                                    .findFirst()
                                    .ifPresentOrElse(menuItem -> {
                                        menuItem.setNames(updateMenuItem.getNames());
                                        menuItem.setPrice(updateMenuItem.getPrice());
                                        menuItem.setDescription(updateMenuItem.getDescription());
                                        menuItem.setImage(updateMenuItem.getImage());
                                    },()->{
                                        throw new NullPointerException("Wrong menu Item name !!!"); // 抛出业务异常
                                    });
    }

    @Override
    public void deleteMenu(DailyMenu dailyMenu,String itemName) {
        dailyMenu.getMenuItemList().removeIf(menuItem ->
                menuItem.getNames().equals(itemName));
    }
}
登录后复制

关键点

  • DailyMenuServicesImpl完全专注于菜单项的增删改查业务逻辑。
  • 它不包含任何System.out.println或Scanner相关的代码。
  • 当业务操作失败时,它会抛出业务相关的异常(如NullPointerException,尽管更推荐自定义业务异常),而不是直接打印错误信息。

3.4 Main方法作为启动入口

重构后的Main方法将应用程序的控制权交给控制器,自身只负责初始化和启动。

public class Main {
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) throws IOException {
        int option;
        Menu menu = new Menu();
        Bill bill = new Bill();
        while (true){
            System.out.println("\n1.Menu management");
            System.out.println("2.Bill management");
            System.out.print("Please choose which types Management you want to work with:");
            option = Integer.parseInt(scanner.nextLine());
            ManagementTypes types = ManagementTypes.get(option-1);
            switch (types){
                case MENU -> menuMain(menu); // 将控制权交给菜单管理的主控制器
                case BILL -> billMain(bill,menu); // 将控制权交给账单管理的主控制器
                default -> {}
            }
        }
    }

    public static void menuMain(Menu menu) throws IOException{
        int option = 0;
        MenuControllers menuControllers =  new MenuControllers(); // 实例化主控制器
        // MenuPrinter menuPrinter = MenuPrinterImpl.getInstance(); // 打印器现在由MenuView持有

        try {
            while (option != 7) {
                menu(); // 
登录后复制

以上就是Java MVC模式实践:构建清晰、可维护的应用程序的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号