如何在 Java 中使用状态设计模式?
状态(State)设计模式是一种行为模式。 状态模式看起来类似于策略模式,但它有助于管理对象状态,从而使它们在不同状态下表现不同。 在这个例子中,我们将采用一个著名的面向对象设计面试问题,用 Java 实现 Vending Machine(自动售货机)。 过去,我们在不使用任何设计模式的情况下解决了这个问题,但在这里我们将使用状态设计模式来创建具有不同状态的自动售货机。 这个例子不仅会帮助我们理解如何在 Java 中实现状态设计模式,还会让我们体验何时在应用程序中使用状态模式。
如果了解自动售货机的工作原理,可以将其运行主要分为售罄、闲置、处理中、售出四种状态。 售货机刚启动未初始化,或所有商品已售完时售罄。
闲置,当自动售货机正在等待客户选择商品时; 处理中,一旦客户选择了商品并开始投币;售出 , 当客户支付了金额时。
我们可以使用状态设计模式来对自动售货机的这些状态进行建模。 在这个例子中,我们有一个名为 State
的抽象类来表示Vending Machine的状态,它提供了各种方法的默认实现,这些方法被Context(本例中为Vending Machine)调用,我们的每一个State,比如Idle,Processing , Sold, SoldOut 然后扩展这个抽象类并重写方法,可以在那些状态下调用。
所有其他不应该在这些状态上调用的方法将执行默认操作,可以是什么都不做或抛出异常,如 State 抽象类中所定义。 每个 State 类都保留对 Context 的引用,它们通过 Context 关联,并且它们还进行状态转换,我的意思是更改 Vending Machine 的当前状态。
如何使用状态设计模式在 Java 中设计自动售货机 - 示例
这是我们使用 Java 中的状态设计模式实现自动售货机的完整代码示例。 当 Vending Machine 启动时,它最初处于 SoldOut 状态,然后在使用默认数量的 Items 和默认数量的硬币初始化后进入 Idle 状态。
Vending Machine 还提供了一个存在于 State 抽象类中的方法,如 select(Item i)
、insert(Coin c)
、refund()
等,但它们被委托给当前状态。 当客户通过在 Vending Machine 上调用 select(Item i)
选择产品时,它会委托给 currentState.select(i)
,如果 Machine 处于 Idle 状态则有效,然后它将进入 Processing 但如果在其他机器上调用则会抛出 IllegalStateException 状态。
现在,让我们看看代码,它与我们的自动售货机问题代码非常相似,但这次,我使用状态模式来解决问题。
- Coin.java
- Idle.java
- inventory.java
- Item.java
- NotSufficientChangeException.java
- Processing.java
- Sold.java
- SoldOut.java
- State.java
- VendingMachine.java
- VendingMachineTest.java
Coin.java
public enum Coin { PENNY(1), NICKLE(5), DIME(10), QUARTER(25); private int value; private Coin(int value){ this.value = value; } public int value(){ return value; } }
State.java
import java.util.List; public class State { public void insert(Coin c){ throw new IllegalStateException(); } public List refund(){ throw new IllegalStateException(); } public int choose(Item i){ throw new IllegalStateException(); } public Item dispense(){ throw new IllegalStateException(); } public List getChange() { throw new IllegalStateException(); } }
Idle.java
public class Idle extends State{ private VendingMachine machine; public Idle(VendingMachine machine){ this.machine = machine; } @Override public int choose(Item i) { if(machine.itemInvertory.getCount(i) >= 1){ machine.currentItem = i; machine.setState(new Processing(machine)); }else{ System.out.println(i + " sold out, Please try another drink"); } return i.getPrice(); } }
Sold.java
import java.util.List; public class Sold extends State{ private VendingMachine machine; public Sold(VendingMachine machine){ this.machine = machine; } @Override public Item dispense(){ if(machine.itemInvertory.isEmpty()){ machine.setState(new SoldOut(machine)); } machine.balance = machine.balance - machine.currentItem.getPrice(); machine.itemInvertory.take(machine.currentItem); Item sold = machine.currentItem; machine.currentItem = null; return sold; } @Override public List getChange(){ List change = machine.getChange(machine.balance); return change; } }
SoldOut.java
public class SoldOut extends State{ private VendingMachine machine; public SoldOut(VendingMachine machine){ this.machine = machine; } }
Inventory.java
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class Inventory { private Map store = new ConcurrentHashMap(); public int getCount(I item){ Integer count = store.get(item); return count != null ? count : 0; } public void add(I item){ int count = getCount(item); store.put(item, ++count); } public void take(I item){ int count = getCount(item); store.put(item, --count); } public boolean isEmpty(){ return store.isEmpty(); } boolean has(I i) { return getCount(i) > 0; } }
Item.java
public enum Item { COKE(70), PEPSI(80), SPRITE(90); private int price; private Item(int price){ this.price = price; } public int getPrice(){ return price; } }
NotSufficientChangeException.java
public class NotSufficientChangeException extends RuntimeException { private String message; public NotSufficientChangeException(String string) { this.message = string; } @Override public String getMessage() { return message; } }
Processing.java
import java.util.List; public class Processing extends State{ private VendingMachine machine; public Processing(VendingMachine machine){ this.machine = machine; } @Override public void insert(Coin c) { machine.coinInvertory.add(c); machine.balance = machine.balance + c.value(); if (machine.balance >= machine.currentItem.getPrice()) { if (machine.hasChange(machine.balance - machine.currentItem.getPrice())) { machine.setState(new Sold(machine)); } else { System.out.println("Machine don't have sufficient change, Please take refund"); } } } @Override public List refund() { machine.currentItem = null; machine.setState(new Idle(machine)); List change = machine.getChange(machine.balance); machine.balance = 0; return change; } }
VendingMachine.java
import java.util.ArrayList; import java.util.List; public class VendingMachine { private State state; Inventory itemInvertory = new Inventory(); Inventory coinInvertory = new Inventory(); Item currentItem; int balance; public VendingMachine(){ state = new SoldOut(this); initialize(); } public void insert(Coin c){ state.insert(c); } public List refund(){ return state.refund(); } public int choose(Item i){ return state.choose(i); } public Item dispense(){ return state.dispense(); } public void setState(State newState){ state = newState; } public List getChange(){ return state.getChange(); } private void initialize() { loadCoins(); loadItems(); this.state = new Idle(this); } private void loadCoins(){ for(Coin c: Coin.values()){ coinInvertory.add(c); coinInvertory.add(c); coinInvertory.add(c); coinInvertory.add(c); coinInvertory.add(c); } } private void loadItems(){ for(Item i: Item.values()){ itemInvertory.add(i); itemInvertory.add(i); itemInvertory.add(i); itemInvertory.add(i); itemInvertory.add(i); } } List getChange(int balance) { List change = new ArrayList(); while(balance != 0){ if(balance >= Coin.QUARTER.value() && coinInvertory.has(Coin.QUARTER)){ balance -= Coin.QUARTER.value(); change.add(Coin.QUARTER); coinInvertory.take(Coin.QUARTER); }else if(balance >= Coin.DIME.value() && coinInvertory.has(Coin.DIME) ) { balance -= Coin.DIME.value(); change.add(Coin.DIME); coinInvertory.take(Coin.DIME); }else if(balance >= Coin.NICKLE.value() && coinInvertory.has(Coin.NICKLE)){ balance -= Coin.NICKLE.value(); change.add(Coin.NICKLE); coinInvertory.take(Coin.NICKLE); }else if(balance >= Coin.PENNY.value() && coinInvertory.has(Coin.PENNY)) { balance -= Coin.PENNY.value(); change.add(Coin.PENNY); coinInvertory.take(Coin.PENNY); } if(coinInvertory.isEmpty() && balance >0){ throw new NotSufficientChangeException("Not Sufficient Change for this purchase"); } } return change; } boolean hasChange(int change) { try{ List coins = getChange(change); //returning coins back to inventory for(Coin c : coins){ coinInvertory.add(c); } }catch(NotSufficientChangeException ex){ return false; } return true; } }
VendingMachineTest.java
这是我们的测试程序,用于测试我们使用状态设计模式实现的 VendingMachine 代码。 这些测试将确认状态转换是否按预期工作。 下面的代码使用 JUnit 注释,因此我们的类路径中需要 JUnit 4.0 或 JUnit 5.0 框架 JAR 文件。 我进行了几次测试,以购买零钱和更多零钱的饮料,以查看我们的机器是否正常工作。
为了大家的练习,我还添加了一些空白测试方法,例如 buyMultipleDrinks()
、refund()
和 buyAllDrinks()
,我们可以实现这些方法来练习编写 JUnit 测试用例
这个状态设计模式的 UML 图也将帮助我们理解类结构和它们之间的关系。
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.*;
public class VendingMachineTest {
private VendingMachine machine = new VendingMachine();
public VendingMachineTest() {
System.out.println("JUnit Framework calls Constructor of test class before executing test methods");
}
@Test
public void buyDrinkWithExactAmount() {
int price = machine.choose(Item.COKE);
assertEquals(70, price);
assertEquals(Item.COKE, machine.currentItem);
machine.insert(Coin.QUARTER);
machine.insert(Coin.QUARTER);
machine.insert(Coin.DIME);
machine.insert(Coin.DIME);
assertEquals(70, machine.balance);
assertEquals(7, (int) machine.coinInvertory.getCount(Coin.DIME));
assertEquals(7, (int) machine.coinInvertory.getCount(Coin.QUARTER));
Item i = machine.dispense();
assertEquals(Item.COKE, i);
assertEquals(4, (int) machine.itemInvertory.getCount(i));
List change = machine.getChange();
assertTrue(change.isEmpty());
}
@Test
public void buyDrinkWithMoreAmount() {
int price = machine.choose(Item.SPRITE);
assertEquals(90, price);
assertEquals(Item.SPRITE, machine.currentItem);
machine.insert(Coin.QUARTER);
machine.insert(Coin.QUARTER);
machine.insert(Coin.QUARTER);
machine.insert(Coin.QUARTER);
assertEquals(100, machine.balance);
assertEquals(9, (int) machine.coinInvertory.getCount(Coin.QUARTER));
Item i = machine.dispense();
assertEquals(Item.SPRITE, i);
assertEquals(4, machine.itemInvertory.getCount(i));
assertEquals(5, machine.itemInvertory.getCount(Item.COKE));
List change = machine.getChange();
assertEquals(1, change.size());
assertEquals(Coin.DIME, change.get(0));
assertEquals(4, machine.coinInvertory.getCount(Coin.DIME));
}
@Test
public void buyMultipleDrinks() {
//TODO
}
@Test
public void refund() {
//TODO
}
@Test
public void buyAllDrinks() {
//TODO
}
}
这就是如何使用状态设计模式在 Java 中实现自动售货机的全部内容。 与我们之前的示例相比,我们可以看到在管理更改状态的逻辑和在不同状态下实现不同行为方面的明显优势。 我强烈建议大家使用 Java 中的状态设计模式来实现自动售货机或任何面向状态的问题。 它导致更简单和有效的解决方案。
相关文章
使用 Java 在 MongoDB 中生成 ObjectId
发布时间:2023/04/20 浏览次数:179 分类:MongoDB
-
本文将讨论 ObjectId 以及我们如何使用 Java 程序生成它。 为了使主题更简单,我们将看到一个带有解释的示例,以使主题更容易。
在 PHP 变量中存储 Div Id 并将其传递给 JavaScript
发布时间:2023/03/29 浏览次数:69 分类:PHP
-
本文教导将 div id 存储在 PHP 变量中并将其传递给 JavaScript 代码。
如何在 Java 中把日期转换为字符串
发布时间:2023/03/28 浏览次数:192 分类:Java
-
本篇文章介绍了如何在 Java 中把 java.util.Date 转换为字符串 String。