Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
Java MVC模式实践:构建清晰、可维护的应用程序_创想鸟

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

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())。这种逻辑属于控制器或服务层,将其放在视图中会造成:

职责不清晰:视图不再是纯粹的UI层。维护困难:如果UI需要从命令行改为图形界面(Swing/JavaFX),这些业务逻辑也需要重写,而它们本应独立于UI。测试复杂:难以对视图中的业务逻辑进行单元测试,因为它们与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 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模式实践:构建清晰、可维护的应用程序的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/572327.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Langchain中集成Amazon DocumentDB作为向量存储的指南
上一篇 2025年11月10日 06:35:13
韩国男子杀害女友遗体塞进泡菜冰箱进一年
下一篇 2025年11月10日 06:35:18

相关推荐

  • MySQL数据库如何设计适合大数据量的表结构_案例分析?

    MySQL数据库如何设计适合大数据量的表结构_案例分析?MySQL数据库如何设计适合大数据量的表结构_案例分析?MySQL数据库如何设计适合大数据量的表结构_案例分析?MySQL数据库如何设计适合大数据量的表结构_案例分析?

    设计适合大数据量的mysql表结构,核心在于数据类型选对、索引用好、适当拆分。1. 合理选择字段类型,如根据数据范围选用tinyint/smallint代替bigint,固定值字段用enum类型,大文本字段单独拆表;2. 精准建立索引,高频查询字段建联合索引并遵循最左前缀原则,避免低区分度字段建索引…

    2026年9月21日 用户投稿
    100
  • Java Random类如何生成随机数

    Random类位于java.util包,通过实例化生成伪随机数;无参构造以系统时间作种子,带参构造用固定种子可复现序列;提供nextInt()、nextDouble()等方法生成不同类型随机值;指定范围整数可用rand.nextInt(max-min)+min实现;多线程推荐ThreadLocalR…

    2026年9月21日
    100
  • windows10如何查看S.M.A.R.T.硬盘状态_windows10硬盘S.M.A.R.T.状态查看方法

    电脑运行慢、蓝屏或文件损坏可能是硬盘故障前兆,可通过S.M.A.R.T.技术检测健康状况。1、使用WMIC命令行工具输入“wmic diskdrive get model,status”查看状态,显示Pred Fail需立即备份数据;2、CrystalDiskInfo可深度分析S.M.A.R.T.参…

    2026年9月21日
    100
  • Photopea的AI功能怎么裁剪图片?快速实现高效图片裁剪技巧

    Photopea的AI功能怎么裁剪图片?快速实现高效图片裁剪技巧Photopea的AI功能怎么裁剪图片?快速实现高效图片裁剪技巧Photopea的AI功能怎么裁剪图片?快速实现高效图片裁剪技巧Photopea的AI功能怎么裁剪图片?快速实现高效图片裁剪技巧

    Photopea的AI功能通过智能选择工具与内容感知技术结合,实现高效图片裁剪。首先使用对象选择、快速选择或魔棒工具智能识别主体或背景,再通过“选择并遮住”精细调整边缘,尤其适用于复杂轮廓如发丝。随后可应用图层蒙版透明化背景,并用裁剪工具调整画布范围。结合内容感知填充可移除干扰元素并自动补全画面,内…

    2026年9月21日 用户投稿
    300
  • PHP框架中间件有什么用处_PHP框架中间件设计与实现

    PHP框架中间件是处理请求和响应的过滤器,用于实现身份验证、日志记录、CORS等通用逻辑,核心价值在于解耦和提升可维护性。通过定义中间件接口、具体中间件类及管道调度器可实现自定义中间件,如身份验证或CORS处理。在Laravel中可通过Kernel.php配置全局、分组或路由级中间件,执行顺序按注册…

    2026年9月21日
    000
  • Java中字符到数字转换:解决for循环提前返回的常见陷阱

    本文探讨java中`for`循环在字符到数字转换时,因`return`语句放置不当导致程序提前终止、无法完整处理字符串的问题。我们将分析这种常见陷阱,并提供修正方案,演示如何正确利用循环填充数组,并在循环结束后统一返回最终结果,确保每个字符都能被准确映射和组合。 引言:字符到数字的映射需求 在编程实…

    2026年9月21日
    000
  • win10登录界面不显示用户头像或名称怎么办_恢复登录界面完整显示的操作方法

    登录界面缺少头像或账户名时,先检查账户名一致性,修复头像缓存,重设头像,扫描系统文件,必要时创建新管理员账户验证问题。 如果您在启动Windows 10后,登录界面仅显示密码输入框而缺少用户头像或账户名称,则可能是由于系统设置、缓存异常或账户配置问题导致。以下是恢复登录界面完整显示的详细操作方法。 …

    2026年9月21日
    100
  • 梦幻号虚拟主播电商运营宝典(附新手教程+配套工具清单)

    虚拟主播电商的核心在于“内容驱动销售,人设凝聚用户”,要让“梦幻号”真正动起来并实现带货,必须先赋予其鲜明的人设,包括清晰的定位标签(如美食家、科技宅)、独特的人格魅力(性格、口头禅、小缺点)和与产品的强关联性,使其具备辨识度和故事感,从而建立用户信任;接着通过obs studio、vtube st…

    2026年9月21日
    000
  • win10平板模式下屏幕键盘不自动弹出怎么办_恢复屏幕键盘自动弹出的技巧

    1、检查平板电脑模式设置,确保登录和使用时均启用平板模式并重启;2、在设备→输入中开启“不处于平板模式且未连接键盘时显示触摸键盘”;3、通过注册表编辑器创建InitialKeyboardIndicators值为2(十六进制)以强制启用键盘指示器;4、手动显示触摸键盘按钮并测试各应用兼容性,排查特定软…

    2026年9月21日
    000
  • deepseek下载速度优化_从deepseek下载速度优化官网获取

    deepseek下载速度优化入口在官网https://www.deepseek.com,进入后可通过设置调整响应模式、使用智能路由和数据压缩技术提升速度。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ deepseek下载速度优化入口地址在…

    2026年9月21日
    000
  • Java多线程API调用中Future.get()返回null的解决方案

    本文旨在解决%ignore_a_1%api调用中`future.get()`方法返回`null`的常见问题。当使用`callable`和`executorservice`并发执行api请求并尝试获取结果时,如果流读取逻辑不当,可能导致获取到的数据为空。文章将详细解释问题根源,并提供使用`string…

    2026年9月21日
    000
  • VSCode的自动保存功能如何开启?

    在VSCode中开启自动保存需进入“文件”→“首选项”→“设置”,搜索auto save并选择Files: Auto Save模式,可选afterDelay、onFocusChange或onWindowChange,其中afterDelay可设置延迟时间如1000毫秒,启用后状态栏显示保存状态以确认…

    2026年9月21日
    000
  • 升级后如何检查兼容性

    检查兼容性是升级后确保系统稳定的关键,需先确认硬件配置与驱动支持,再验证软件运行及业务流程正常,最后通过系统日志排查潜在错误,逐步排除风险。 系统或软件升级后,检查兼容性是确保各项功能正常运行的关键步骤。直接进入实际使用前,花时间验证兼容性可以避免数据丢失、服务中断等问题。 检查硬件和驱动支持 某些…

    2026年9月21日
    000
  • windows怎么解决蓝屏问题_windows蓝屏故障排查与修复方法

    蓝屏问题通常由驱动冲突、硬件故障或系统文件损坏引起,需记录错误代码并进入安全模式排查;通过设备管理器检查驱动、使用SFC和DISM修复系统文件,并运行内存与硬盘检测工具确认硬件健康,必要时清洁硬件接触点。 如果您在使用Windows系统时遇到电脑突然黑屏并显示蓝色错误界面,这通常意味着系统遇到了无法…

    2026年9月21日
    000
  • mysql如何排查排序异常

    排查MySQL排序异常需先确认ORDER BY是否生效,检查子查询、UNION及应用层逻辑是否覆盖排序;通过EXPLAIN分析是否使用索引排序,避免Using filesort;确保字段类型、字符集和排序规则(collation)符合预期,处理NULL值和大小写敏感性;关注sort_buffer_s…

    2026年9月21日
    000
  • 即梦AI运镜控制怎么控制_即梦AI视频镜头移动技巧详解

    掌握即梦AI运镜需四步:一、用“镜头缓慢推进”等预设提示词生成标准运动;二、通过动效画板框选主体并绘制运动路径;三、设置首尾帧引导转场,实现穿越或循环效果;四、结合“希区柯克式变焦”“时间冻结环绕”等高级技巧增强视觉表现。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 Dee…

    2026年9月21日
    000
  • windows怎么查看电脑型号_Windows查看电脑硬件型号方法

    通过系统信息工具查看:按Win+R输入msinfo32,查找“系统型号”获取电脑型号;2. 使用命令提示符执行wmic csproduct get name查询型号;3. 在Windows 11设置中进入“系统-关于”,查看“设备规格”下的“设备型号”;4. 利用PowerShell运行Get-Wm…

    2026年9月21日
    100
  • .com网站安全维护_保障.com网站稳定的措施

    答案:保障.com网站稳定需加强安全防护、定期备份、实时监控和应急准备。部署防火墙、更新系统、使用HTTPS、限制端口;制定自动备份并异地存储,定期恢复测试;利用监控工具检测可用性与异常流量,优化加载速度;建立应急流程,严格权限管理,定期演练。细节执行到位才能确保长期安全稳定运行。 确保.com网站…

    2026年9月21日
    100
  • 三星电视携手京东开启艺术视听盛典以科技美学重塑家居生活新模式

    三星电视携手京东开启艺术视听盛典以科技美学重塑家居生活新模式三星电视携手京东开启艺术视听盛典以科技美学重塑家居生活新模式三星电视携手京东开启艺术视听盛典以科技美学重塑家居生活新模式三星电视携手京东开启艺术视听盛典以科技美学重塑家居生活新模式

    随着消费理念升级与需求日益多样化,电视已不再仅仅是观看节目和影音娱乐的工具,而是逐渐演变为承载家居美学、传递情感温度、连接智慧生活的艺术载体。在这一变革浪潮中,三星率先引领艺术电视领域的创新风向,theframe画壁艺术电视与theserif画境艺术电视成功打破科技与艺术之间的界限,将电视升华为可观…

    2026年9月21日 用户投稿
    100
  • 如何在Weka中处理向量属性:ARFF格式的限制与解决方案

    本文探讨了weka中arff格式对直接向量属性表示的限制,并提供了两种主要解决方案。对于时间序列数据,建议利用weka的内置时间序列分析功能。对于非时间序列数据,核心在于通过特征工程(如使用addexpression、multifilter等)将向量拆解并转换为可被weka有效处理的独立特征,以揭示…

    2026年9月21日
    000

发表回复

登录后才能评论
关注微信