如何在不违反接口隔离原则(ISP)下实现自动售货机的状态设计模式?
解决自动售货机状态模式违反接口隔离原则的方案
你的问题核心是单一大State接口强迫所有状态类实现不需要的方法,违反了接口隔离原则(ISP)——客户端不应该依赖它不需要的接口。下面是重构方案,通过拆分细粒度接口、按需实现来解决这个问题。
一、重构思路:拆分细粒度接口
将原有庞大的State接口拆分为多个职责单一的小接口,每个接口对应一组相关行为,具体状态类只实现自己需要的接口,避免冗余的异常实现。
拆分后的接口定义
// 顶层标记接口,统一状态类型 public interface State {} // 处理按钮触发行为的接口 public interface Triggerable extends State { void clickOnInsertCoinButton(VendingMachine machine) throws Exception; void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception; } // 处理交易相关行为的接口 public interface Transactional extends State { void insertCoin(VendingMachine machine, Coin coin) throws Exception; void chooseProduct(VendingMachine machine, int codeNumber) throws Exception; int getChange(int returnChangeMoney) throws Exception; Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception; List<Coin> refundFullMoney(VendingMachine machine) throws Exception; } // 处理库存管理的接口 public interface InventoryManageable extends State { void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception; }
二、具体状态类的实现
每个状态类仅实现自身职责对应的接口,无需处理无关方法:
1. IdleState(空闲状态)
空闲状态支持触发投币按钮、更新库存,无需处理交易行为:
public class IdleState implements Triggerable, InventoryManageable { public IdleState(){ System.out.println("当前自动售货机处于空闲状态"); } public IdleState(VendingMachine machine){ System.out.println("当前自动售货机处于空闲状态"); machine.setCoinList(new ArrayList<>()); } @Override public void clickOnInsertCoinButton(VendingMachine machine) throws Exception{ machine.setVendingMachineState(new HasMoneyState()); } @Override public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception { throw new Exception("请先点击投币按钮"); } @Override public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception { machine.getInventory().addItem(item, codeNumber); } }
2. HasMoneyState(已投币状态)
已投币状态支持触发选品按钮、交易相关行为:
public class HasMoneyState implements Triggerable, Transactional { public HasMoneyState() { System.out.println("当前自动售货机处于已投币状态"); } @Override public void clickOnInsertCoinButton(VendingMachine machine) throws Exception { // 已投币状态下可以继续投币,无需切换状态 System.out.println("可继续投币"); } @Override public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception { machine.setVendingMachineState(new SelectionState()); } @Override public void insertCoin(VendingMachine machine, Coin coin) throws Exception { machine.getCoinList().add(coin); System.out.println("已投入硬币:" + coin.getValue()); } @Override public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception { throw new Exception("请先点击选品开始按钮"); } @Override public int getChange(int returnChangeMoney) throws Exception { throw new Exception("未完成商品选择,无法找零"); } @Override public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception { throw new Exception("未完成商品选择,无法出货"); } @Override public List<Coin> refundFullMoney(VendingMachine machine) throws Exception { List<Coin> refund = machine.getCoinList(); machine.setCoinList(new ArrayList<>()); machine.setVendingMachineState(new IdleState(machine)); return refund; } }
3. SelectionState(选品状态)
选品状态仅需处理交易行为:
public class SelectionState implements Transactional { public SelectionState() { System.out.println("当前自动售货机处于选品状态"); } @Override public void insertCoin(VendingMachine machine, Coin coin) throws Exception { throw new Exception("选品状态下无法投币"); } @Override public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception { Item item = machine.getInventory().getItem(codeNumber); // 校验金额、库存逻辑... machine.setVendingMachineState(new DispensedState(machine, codeNumber)); } @Override public int getChange(int returnChangeMoney) throws Exception { // 计算找零逻辑 return returnChangeMoney; } @Override public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception { throw new Exception("请先选择商品"); } @Override public List<Coin> refundFullMoney(VendingMachine machine) throws Exception { List<Coin> refund = machine.getCoinList(); machine.setCoinList(new ArrayList<>()); machine.setVendingMachineState(new IdleState(machine)); return refund; } }
三、调整后的上下文类(VendingMachine)
上下文类保持状态的统一管理,对外暴露操作方法,内部根据当前状态的接口类型委托调用:
public class VendingMachine { private State vendingMachineState; private Inventory inventory; private List<Coin> coinList; public VendingMachine(){ vendingMachineState = new IdleState(); inventory = new Inventory(10); coinList = new ArrayList<>(); } // 对外暴露的投币按钮点击方法 public void clickInsertCoinButton() throws Exception { if (vendingMachineState instanceof Triggerable) { ((Triggerable) vendingMachineState).clickOnInsertCoinButton(this); } else { throw new Exception("当前状态不支持此操作"); } } // 对外暴露的投币方法 public void insertCoin(Coin coin) throws Exception { if (vendingMachineState instanceof Transactional) { ((Transactional) vendingMachineState).insertCoin(this, coin); } else { throw new Exception("当前状态不支持投币"); } } // 其他对外方法同理:clickProductSelectionButton、chooseProduct等 // getter和setter方法 public State getVendingMachineState() { return vendingMachineState; } public void setVendingMachineState(State vendingMachineState) { this.vendingMachineState = vendingMachineState; } public Inventory getInventory() { return inventory; } public void setInventory(Inventory inventory) { this.inventory = inventory; } public List<Coin> getCoinList() { return coinList; } public void setCoinList(List<Coin> coinList) { this.coinList = coinList; } }
四、项目结构示例
按职责划分包结构,保持代码清晰:
src/ ├── com/ │ └── vendingmachine/ │ ├── model/ # 模型类 │ │ ├── VendingMachine.java │ │ ├── Inventory.java │ │ ├── Coin.java │ │ └── Item.java │ └── state/ # 状态相关接口和实现类 │ ├── State.java │ ├── Triggerable.java │ ├── Transactional.java │ ├── InventoryManageable.java │ ├── IdleState.java │ ├── HasMoneyState.java │ ├── SelectionState.java │ └── DispensedState.java
内容的提问来源于stack exchange,提问作者Samarth Mehrotra
相关产品推荐
相关产品推荐

