You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

菜单服务设计选型:单父类实现还是拆分三类服务?

菜单管理服务设计方案咨询

我有一个菜单管理服务的练习需求,具体如下:

  • 食品菜单包含早餐、午餐、晚餐三类菜单,每类都包含MenuItem列表(属性:name、price、description、image)
  • 需要为三类菜单实现CRUD操作

初始实现方案

初始模型

FoodMenu模型

public class FoodMenu{
    private BreakfastMenu breakfastMenu;
    private DinnerMenu dinnerMenu;
    private LunchMenu lunchMenu;

    public FoodMenu(BreakfastMenu breakfastMenu, DinnerMenu dinnerMenu, LunchMenu lunchMenu){
        this.breakfastMenu = breakfastMenu;
        this.dinnerMenu = dinnerMenu;
        this.lunchMenu = lunchMenu;
    }
}

BreakfastMenu模型

public class BreakfastMenu {
    private List<MenuItem> menuItemList;
    public BreakfastMenu(List<MenuItem> menuItemList){
        this.menuItemList = menuItemList;
    }
}

DinnerMenu模型

public class DinnerMenu {
    public DinnerMenu(){}
    private List<MenuItem> menuItemList;

    public DinnerMenu(List<MenuItem> menuItemList){
        this.menuItemList = menuItemList;
    }
}

LunchMenu模型

public class LunchMenu {
    private List<MenuItem> menuItemList;
    public LunchMenu(List<MenuItem> menuItemList){
        this.menuItemList = menuItemList;
    }
    public LunchMenu(){}
}

初始Service实现

早餐菜单服务实现(午餐、晚餐服务逻辑完全一致,仅参数类型不同):

public class BreakfastMenuServicesImpl implements BreakfastMenuServices {

    @Override
    public BreakfastMenu addMenuItemsToBreakfastMenu(BreakfastMenu breakfastMenu) {
        Scanner scanner = new Scanner(System.in);

        MenuItem menuItem = new MenuItem();

        List<MenuItem> menuItemList= breakfastMenu.getMenuItemList();

        if(menuItemList == null){
            menuItemList = new ArrayList<MenuItem>();
        }
        System.out.print("\nInsert food name: ");
        menuItem.setNames(scanner.nextLine());
        System.out.print("Insert food description: ");
        menuItem.setDescription(scanner.nextLine());
        System.out.print("Insert food image: ");
        menuItem.setImage(scanner.nextLine());
        System.out.print("Insert food price:");
        menuItem.setPrice(scanner.nextFloat());

        menuItemList.add(menuItem);
        breakfastMenu.setMenuItemList(menuItemList);
        return breakfastMenu;
    }

    @Override
    public void showBreakFastMenu(BreakfastMenu breakfastMenu) {
        System.out.println("\nBreakfast menu:");
        breakfastMenu.getMenuItemList().forEach(System.out::println);
    }

    @Override
    public void updateBreakfastMenu(BreakfastMenu breakfastMenu) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Insert food want to update:");
        String foodItem = scanner.nextLine();
        breakfastMenu.getMenuItemList().forEach((MenuItem menuItem)->{
            if(menuItem.getNames().equals(foodItem)){
                System.out.print("\nInsert food name: ");
                menuItem.setNames(scanner.nextLine());
                System.out.print("Insert food description: ");
                menuItem.setDescription(scanner.nextLine());
                System.out.print("Insert food image: ");
                menuItem.setImage(scanner.nextLine());
                System.out.print("Insert food price:");
                menuItem.setPrice(scanner.nextFloat());
            }
        });
    }

    @Override
    public void deleteBreakfastMenu(BreakfastMenu breakfastMenu) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Insert food want to delete:");
        String foodItem = scanner.nextLine();
        breakfastMenu.getMenuItemList().removeIf(menuItem ->
                                                    menuItem.getNames().equals(foodItem));
    }
}

优化后实现方案

优化后模型

FoodMenu模型

public class FoodMenu{
    private BreakfastMenu breakfastMenu;
    private DinnerMenu dinnerMenu;
    private LunchMenu lunchMenu;

    public FoodMenu(BreakfastMenu breakfastMenu, DinnerMenu dinnerMenu, LunchMenu lunchMenu){
        this.breakfastMenu = breakfastMenu;
        this.dinnerMenu = dinnerMenu;
        this.lunchMenu = lunchMenu;
    }
}

AbstractMenu模型

public class AbstractMenu {
    private List<MenuItem> menuItemList;
    public AbstractMenu(List<MenuItem> menuItemList){
        this.menuItemList = menuItemList;
    }
    public AbstractMenu(){
    }
}

BreakfastMenu模型

public class BreakfastMenu extends AbstractMenu{
}

LunchMenu模型

public class LunchMenu extends AbstractMenu{
}

DinnerMenu模型

public class DinnerMenu extends AbstractMenu{
}

优化后Service

AbstractMenuService接口

public interface AbstractMenuService {
    AbstractMenu addMenuItemsToMenu(AbstractMenu abstractMenu);
    void showMenu(AbstractMenu abstractMenu);
    void updateMenu(AbstractMenu abstractMenu);
    void deleteMenu(AbstractMenu abstractMenu);
}

子类服务接口

// BreakfastMenuServices接口
public interface BreakfastMenuServices extends AbstractMenuService{
}

// DinnerMenuServices接口
public interface DinnerMenuServices extends AbstractMenuService{
}

// LunchServices接口
public interface LunchServices extends AbstractMenuService{
}

优化后实现类(以BreakfastMenuServicesImpl为例)

public class BreakfastMenuServicesImpl implements BreakfastMenuServices {

    @Override
    public AbstractMenu addMenuItemsToMenu(AbstractMenu abstractMenu) {
        return null;
    }

    @Override
    public void showMenu(AbstractMenu abstractMenu) {

    }

    @Override
    public void updateMenu(AbstractMenu abstractMenu) {

    }

    @Override
    public void deleteMenu(AbstractMenu abstractMenu) {

    }
}

咨询问题

  1. 两种方案中哪种更易读且便于维护?
  2. 因继承父接口需重写所有方法,但我无需差异化逻辑而留空实现,这种做法是否存在负面影响?

回答

一、方案对比:哪种更易读易维护

优化后的方案更符合DRY(Don't Repeat Yourself)原则,长期来看更易维护:

  • 初始方案存在大量重复代码:三类菜单模型结构几乎一致,服务CRUD逻辑完全相同,仅参数类型不同。后续修改逻辑(比如新增字段、调整交互)需在三个服务类重复修改,极易出现遗漏或不一致。
  • 优化后的方案通过AbstractMenu统一菜单核心属性,AbstractMenuService定义通用CRUD方法,彻底避免重复代码。后续修改只需调整一处,维护成本大幅降低。
  • 可读性上,优化方案结构清晰:抽象层明确了"所有菜单都具备CRUD能力"的核心逻辑,子类仅需继承,代码意图更直观。

不过当前优化方案的实现不完善(实现类留空),需要补充通用服务实现,比如编写AbstractMenuServiceImpl基类完成核心CRUD逻辑,让子类直接继承即可。

二、空实现的负面影响

留空实现(甚至返回null)会带来诸多问题:

  1. 运行时错误风险:addMenuItemsToMenu返回null会触发空指针异常;空的showMenu、updateMenu会让功能直接失效,用户操作无反馈,排查难度大。
  2. 违背接口契约:接口定义了方法的行为约定,空实现相当于打破契约,会误导其他开发者对方法功能的预期。
  3. 扩展隐患:后续新增依赖这些方法的逻辑时,空实现会导致功能异常,且难以快速定位问题。

改进建议

  1. 编写通用的AbstractMenuServiceImpl实现类,完成CRUD核心逻辑:
public class AbstractMenuServiceImpl implements AbstractMenuService {

    @Override
    public AbstractMenu addMenuItemsToMenu(AbstractMenu abstractMenu) {
        Scanner scanner = new Scanner(System.in);
        MenuItem menuItem = new MenuItem();
        List<MenuItem> menuItemList = abstractMenu.getMenuItemList();

        if (menuItemList == null) {
            menuItemList = new ArrayList<>();
        }
        System.out.print("\nInsert food name: ");
        menuItem.setNames(scanner.nextLine());
        System.out.print("Insert food description: ");
        menuItem.setDescription(scanner.nextLine());
        System.out.print("Insert food image: ");
        menuItem.setImage(scanner.nextLine());
        System.out.print("Insert food price:");
        menuItem.setPrice(scanner.nextFloat());

        menuItemList.add(menuItem);
        abstractMenu.setMenuItemList(menuItemList);
        return abstractMenu;
    }

    @Override
    public void showMenu(AbstractMenu abstractMenu) {
        String menuName = "";
        if (abstractMenu instanceof BreakfastMenu) {
            menuName = "Breakfast";
        } else if (abstractMenu instanceof LunchMenu) {
            menuName = "Lunch";
        } else if (abstractMenu instanceof DinnerMenu) {
            menuName = "Dinner";
        }
        System.out.printf("\n%s menu:\n", menuName);
        abstractMenu.getMenuItemList().forEach(System.out::println);
    }

    @Override
    public void updateMenu(AbstractMenu abstractMenu) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Insert food want to update:");
        String foodItem = scanner.nextLine();
        abstractMenu.getMenuItemList().forEach(menuItem -> {
            if (menuItem.getNames().equals(foodItem)) {
                System.out.print("\nInsert food name: ");
                menuItem.setNames(scanner.nextLine());
                System.out.print("Insert food description: ");
                menuItem.setDescription(scanner.nextLine());
                System.out.print("Insert food image: ");
                menuItem.setImage(scanner.nextLine());
                System.out.print("Insert food price:");
                menuItem.setPrice(scanner.nextFloat());
            }
        });
    }

    @Override
    public void deleteMenu(AbstractMenu abstractMenu) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Insert food want to delete:");
        String foodItem = scanner.nextLine();
        abstractMenu.getMenuItemList().removeIf(menuItem -> menuItem.getNames().equals(foodItem));
    }
}
  1. 子类服务实现直接继承通用实现类:
public class BreakfastMenuServicesImpl extends AbstractMenuServiceImpl implements BreakfastMenuServices {
    // 无需重复实现方法,后续需差异化逻辑时再重写对应方法
}

这样既避免重复代码,又保证方法有实际实现,同时保留了扩展差异化逻辑的灵活性。


内容的提问来源于stack exchange,提问作者David Moore

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:15:44