
本文深入探讨Java中MVC模式的正确实践,通过分析一个餐厅管理系统案例,揭示视图层(View)和控制器层(Controller)常见的职责混淆问题。我们将详细阐述模型、视图、控制器的核心职责,并提供具体的代码重构示例,旨在帮助开发者实现更严格的职责分离,提升代码的可维护性、可测试性及UI灵活性,并探讨异常处理的最佳实践。
Model-View-Controller(MVC)是一种软件架构模式,旨在将应用程序的业务逻辑、数据和用户界面分离开来。这种分离有助于提高代码的模块化、可维护性和可扩展性。
在项目初期,开发者常会不自觉地将不同层次的职责混淆,导致代码耦合度高,难以维护。以下是一个餐厅管理系统初始实现中常见的几个问题:
在原始的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())。这种逻辑属于控制器或服务层,将其放在视图中会造成:
虽然提供的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场景下复用。
在初始的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应用应该有一个明确的控制器类来承担这些协调职责。
为了解决上述问题,我们需要对代码进行重构,严格遵循MVC的职责分离原则。
控制器是MVC模式的“胶水”,负责接收用户输入,将其转化为对模型(服务)的操作,并最终选择合适的视图来呈现结果。
重构后的MenuControllers示例:
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); // 控制器指示视图显示菜单
}
// ... 文件操作也由控制器协调
}关键点:
重构后的视图层应该尽可能地“哑巴”,只负责显示信息和收集原始的用户输入,不包含任何业务决策逻辑。
重构后的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(){ /* ... */ }
}关键点:
模型层(包括数据结构和服务)应该完全独立于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));
}
}关键点:
重构后的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中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号