下一个需求不像想象中那么简单,当用户投4个0.25元和1元,购买1美元的零食,我们应该接受哪种面额的货币?这种情况是没有料想到的,这时可以和领域专家讨论。在这里,售货机有限接收低面值的货币,返回高面值的1元。

    1. package com.lugew.springbootddd.snackmachine;
    2. import com.lugew.springbootddd.ValueObject;
    3. import lombok.Getter;
    4. /**
    5. * @author 夏露桂
    6. * @since 2021/6/7 11:59
    7. */
    8. @Getter
    9. public class Money extends ValueObject<Money> {
    10. public static Money None = new Money(0, 0, 0, 0, 0, 0);
    11. public static Money Cent = new Money(1, 0, 0, 0, 0, 0);
    12. public static Money TenCent = new Money(0, 1, 0, 0, 0, 0);
    13. public static Money Quarter = new Money(0, 0, 1, 0, 0, 0);
    14. public static Money Dollar = new Money(0, 0, 0, 1, 0, 0);
    15. public static Money FiveDollar = new Money(0, 0, 0, 0, 1, 0);
    16. public static Money TwentyDollar = new Money(0, 0, 0, 0, 0, 1);
    17. private final int oneCentCount;
    18. private final int tenCentCount;
    19. private final int quarterCount;
    20. private final int oneDollarCount;
    21. private final int fiveDollarCount;
    22. private final int twentyDollarCount;
    23. private float amount;
    24. public float getAmount() {
    25. return oneCentCount * 0.01f + tenCentCount * 0.10f + quarterCount * 0.25f +
    26. oneDollarCount * 1f
    27. + fiveDollarCount * 5f + twentyDollarCount * 20f;
    28. }
    29. public Money(int oneCentCount, int tenCentCount, int quarterCount, int
    30. oneDollarCount, int fiveDollarCount, int twentyDollarCount) {
    31. if (oneCentCount < 0)
    32. throw new IllegalStateException();
    33. if (tenCentCount < 0)
    34. throw new IllegalStateException();
    35. if (quarterCount < 0)
    36. throw new IllegalStateException();
    37. if (oneDollarCount < 0)
    38. throw new IllegalStateException();
    39. if (fiveDollarCount < 0)
    40. throw new IllegalStateException();
    41. if (twentyDollarCount < 0)
    42. throw new IllegalStateException();
    43. this.oneCentCount = oneCentCount;
    44. this.tenCentCount = tenCentCount;
    45. this.quarterCount = quarterCount;
    46. this.oneDollarCount = oneDollarCount;
    47. this.fiveDollarCount = fiveDollarCount;
    48. this.twentyDollarCount = twentyDollarCount;
    49. }
    50. public Money(float amount, int oneCentCount, int tenCentCount, int quarterCount, int
    51. oneDollarCount, int fiveDollarCount, int twentyDollarCount) {
    52. this(oneCentCount, tenCentCount, quarterCount, oneDollarCount, fiveDollarCount, twentyDollarCount);
    53. if (amount < 0) {
    54. throw new IllegalStateException();
    55. }
    56. this.amount = amount;
    57. }
    58. public Money substract(Money other) {
    59. return new Money(
    60. oneCentCount - other.oneCentCount,
    61. tenCentCount - other.tenCentCount,
    62. quarterCount - other.quarterCount,
    63. oneDollarCount - other.oneDollarCount,
    64. fiveDollarCount - other.fiveDollarCount,
    65. twentyDollarCount - other.twentyDollarCount);
    66. }
    67. public static Money add(Money money1, Money money2) {
    68. return new Money(
    69. money1.oneCentCount + money2.oneCentCount,
    70. money1.tenCentCount + money2.tenCentCount,
    71. money1.quarterCount + money2.quarterCount,
    72. money1.oneDollarCount + money2.oneDollarCount,
    73. money1.fiveDollarCount + money2.fiveDollarCount,
    74. money1.twentyDollarCount + money2.twentyDollarCount);
    75. }
    76. public Money add(Money other) {
    77. return new Money(
    78. other.oneCentCount + this.oneCentCount,
    79. other.tenCentCount + this.tenCentCount,
    80. other.quarterCount + this.quarterCount,
    81. other.oneDollarCount + this.oneDollarCount,
    82. other.fiveDollarCount + this.fiveDollarCount,
    83. other.twentyDollarCount + this.twentyDollarCount);
    84. }
    85. @Override
    86. protected boolean equalsCore(Money other) {
    87. return oneCentCount == other.oneCentCount
    88. && tenCentCount == other.tenCentCount
    89. && quarterCount == other.quarterCount
    90. && oneDollarCount == other.oneDollarCount
    91. && fiveDollarCount == other.fiveDollarCount
    92. && twentyDollarCount == other.twentyDollarCount;
    93. }
    94. @Override
    95. protected int getHashCodeCore() {
    96. int hashCode = oneCentCount;
    97. hashCode = (hashCode * 397) ^ tenCentCount;
    98. hashCode = (hashCode * 397) ^ quarterCount;
    99. hashCode = (hashCode * 397) ^ oneDollarCount;
    100. hashCode = (hashCode * 397) ^ fiveDollarCount;
    101. hashCode = (hashCode * 397) ^ twentyDollarCount;
    102. return hashCode;
    103. }
    104. @Override
    105. public String toString() {
    106. if (getAmount() < 1)
    107. return "¢" + getAmount() * 100;
    108. return "$" + getAmount();
    109. }
    110. }
    1. package com.lugew.springbootddd.snackmachine;
    2. import com.lugew.springbootddd.AggregateRoot;
    3. import com.lugew.springbootddd.Slot;
    4. import com.lugew.springbootddd.SnackMachineDto;
    5. import com.lugew.springbootddd.SnackPile;
    6. import lombok.Getter;
    7. import lombok.Setter;
    8. import java.util.ArrayList;
    9. import java.util.Arrays;
    10. import java.util.List;
    11. import static com.lugew.springbootddd.snackmachine.Money.None;
    12. public final class SnackMachine extends AggregateRoot {
    13. @Getter
    14. @Setter
    15. private Money moneyInside;
    16. @Getter
    17. @Setter
    18. private float moneyInTransaction;
    19. private List<Slot> slots;
    20. public SnackMachine() {
    21. moneyInside = None;
    22. moneyInTransaction = 0;
    23. slots = new ArrayList<>();
    24. slots.add(new Slot(this, 1));
    25. slots.add(new Slot(this, 2));
    26. slots.add(new Slot(this, 3));
    27. }
    28. public Slot getSlot(int position) {
    29. return slots.stream().filter(x -> x.getPosition() ==
    30. position).findAny().orElse(null);
    31. }
    32. public SnackPile getSnackPile(int position) {
    33. return getSlot(position).getSnackPile();
    34. }
    35. public void insertMoney(Money money) {
    36. Money[] coinsAndNotes = {Money.Cent, Money.TenCent, Money.Quarter,
    37. Money.Dollar, Money.FiveDollar,
    38. Money.TwentyDollar};
    39. if (!Arrays.asList(coinsAndNotes).contains(money))
    40. throw new IllegalStateException();
    41. moneyInTransaction = moneyInTransaction + money.getAmount();
    42. moneyInside = moneyInside.add(money);
    43. }
    44. public void returnMoney() {
    45. moneyInTransaction = 0;
    46. }
    47. public void buySnack(int position) {
    48. Slot slot = getSlot(position);
    49. if (slot.getSnackPile().getPrice() > moneyInTransaction) {
    50. throw new IllegalStateException();
    51. }
    52. slot.setSnackPile(slot.getSnackPile().subtractOne());
    53. moneyInTransaction = 0;
    54. }
    55. public SnackMachineDto convertToSnackMachineDto() {
    56. SnackMachineDto snackMachineDto = new SnackMachineDto();
    57. snackMachineDto.setId(id);
    58. snackMachineDto.setMoneyInTransaction(moneyInTransaction);
    59. snackMachineDto.setOneCentCount(moneyInside.getOneCentCount());
    60. snackMachineDto.setTenCentCount(moneyInside.getTenCentCount());
    61. snackMachineDto.setQuarterCount(moneyInside.getQuarterCount());
    62. snackMachineDto.setOneDollarCount(moneyInside.getOneDollarCount());
    63. snackMachineDto.setFiveDollarCount(moneyInside.getFiveDollarCount());
    64. snackMachineDto.setTwentyDollarCount(moneyInside.getTwentyDollarCount());
    65. return snackMachineDto;
    66. }
    67. public void loadSnacks(int position, SnackPile snackPile) {
    68. Slot slot = slots.stream().filter(x -> x.getPosition() ==
    69. position).findAny().orElse(null);
    70. if (slot != null) {
    71. slot.setSnackPile(snackPile);
    72. }
    73. }
    74. }
    1. package com.lugew.springbootddd;
    2. import com.lugew.springbootddd.snackmachine.SnackMachine;
    3. import lombok.RequiredArgsConstructor;
    4. import org.springframework.web.bind.annotation.*;
    5. import static com.lugew.springbootddd.snackmachine.Money.*;
    6. /**
    7. * @author 夏露桂
    8. * @since 2021/6/10 11:55
    9. */
    10. @RestController
    11. @RequestMapping("snackmachines")
    12. @RequiredArgsConstructor
    13. public class SnackMachineController {
    14. private final SnackMachineRepository snackMachineRepository;
    15. @GetMapping("/{id}")
    16. @ResponseBody
    17. public SnackMachineDto getSnackMachine(@PathVariable("id") long id) {
    18. return snackMachineRepository.findById(id).orElse(null);
    19. }
    20. @PutMapping("/{id}/{slotNumber}")
    21. public void buySnack(@PathVariable("id") long id, @PathVariable("slotNumber")
    22. int slotNumber) {
    23. SnackMachineDto snackMachineDto =
    24. snackMachineRepository.findById(id).orElse(null);
    25. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    26. snackMachine.buySnack(slotNumber);
    27. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    28. }
    29. @PutMapping("/{id}/moneyInTransaction/{coinOrNote}")
    30. public void insertCoinOrNote(@PathVariable("id") long id, @PathVariable("coinOrNote") String coinOrNote) {
    31. SnackMachineDto snackMachineDto =
    32. snackMachineRepository.findById(id).orElse(null);
    33. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    34. if (coinOrNote.equalsIgnoreCase("Cent")) snackMachine.insertMoney(Cent);
    35. else if (coinOrNote.equalsIgnoreCase("TenCent"))
    36. snackMachine.insertMoney(TenCent);
    37. else if (coinOrNote.equalsIgnoreCase("Quarter"))
    38. snackMachine.insertMoney(Quarter);
    39. else if (coinOrNote.equalsIgnoreCase("Dollar"))
    40. snackMachine.insertMoney(Dollar);
    41. else if (coinOrNote.equalsIgnoreCase("FiveDollar"))
    42. snackMachine.insertMoney(FiveDollar);
    43. else if (coinOrNote.equalsIgnoreCase("TwentyDollar"))
    44. snackMachine.insertMoney(TwentyDollar);
    45. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    46. }
    47. @PutMapping("/{id}/moneyInTransaction")
    48. public void returnMoney(@PathVariable("id") long id) {
    49. SnackMachineDto snackMachineDto =
    50. snackMachineRepository.findById(id).orElse(null);
    51. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    52. snackMachine.returnMoney();
    53. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    54. }
    55. /* public Money getWholeMoney(SnackMachine snackMachine) {
    56. return Money.add(snackMachine.getMoneyInside(), snackMachine.getMoneyInTransaction());
    57. }*/
    58. }
    1. package com.lugew.springbootddd;
    2. import com.lugew.springbootddd.snackmachine.Money;
    3. import com.lugew.springbootddd.snackmachine.SnackMachine;
    4. import lombok.Getter;
    5. import lombok.Setter;
    6. import javax.persistence.Entity;
    7. import javax.persistence.GeneratedValue;
    8. import javax.persistence.Id;
    9. /**
    10. * @author 夏露桂
    11. * @since 2021/6/10 12:00
    12. */
    13. @Getter
    14. @Setter
    15. @Entity
    16. public class SnackMachineDto {
    17. @Id
    18. @GeneratedValue
    19. private long id;
    20. private int oneCentCount;
    21. private int tenCentCount;
    22. private int quarterCount;
    23. private int oneDollarCount;
    24. private int fiveDollarCount;
    25. private int twentyDollarCount;
    26. private float moneyInTransaction;
    27. public SnackMachine convertToSnackMachine() {
    28. SnackMachine snackMachine = new SnackMachine();
    29. snackMachine.setId(id);
    30. snackMachine.setMoneyInTransaction(moneyInTransaction);
    31. snackMachine.setMoneyInside(new
    32. Money(oneCentCount, tenCentCount, quarterCount, oneDollarCount, fiveDollarCount, twentyDollarCount));
    33. return snackMachine;
    34. }
    35. }
    1. package com.lugew.springbootddd.snackmachine;
    2. import com.lugew.springbootddd.Snack;
    3. import com.lugew.springbootddd.SnackPile;
    4. import org.junit.jupiter.api.Test;
    5. import static com.lugew.springbootddd.snackmachine.Money.Cent;
    6. import static com.lugew.springbootddd.snackmachine.Money.Dollar;
    7. import static org.junit.jupiter.api.Assertions.assertEquals;
    8. import static org.junit.jupiter.api.Assertions.assertThrows;
    9. /**
    10. * @author 夏露桂
    11. * @since 2021/6/9 18:12
    12. */
    13. public class SnackMachineTest {
    14. @Test
    15. public void return_money_empties_money_in_transaction() {
    16. SnackMachine snackMachine = new SnackMachine();
    17. snackMachine.insertMoney(Dollar);
    18. snackMachine.returnMoney();
    19. assertEquals(snackMachine.getMoneyInTransaction(), 0, 0);
    20. }
    21. @Test
    22. public void inserted_money_goes_to_money_in_transaction() {
    23. SnackMachine snackMachine = new SnackMachine();
    24. snackMachine.insertMoney(Cent);
    25. snackMachine.insertMoney(Dollar);
    26. assertEquals(snackMachine.getMoneyInTransaction(), 1.01f, 0);
    27. }
    28. @Test
    29. public void cannot_insert_more_than_one_coin_or_note_at_a_time() {
    30. SnackMachine snackMachine = new SnackMachine();
    31. Money twoCent = Money.add(Cent, Cent);
    32. assertThrows(IllegalStateException.class, () -> {
    33. snackMachine.insertMoney(twoCent);
    34. });
    35. }
    36. @Test
    37. public void buySnack_trades_inserted_money_for_a_snack() {
    38. SnackMachine snackMachine = new SnackMachine();
    39. snackMachine.loadSnacks(1, new SnackPile(new Snack("Some snack"), 10, 1));
    40. snackMachine.insertMoney(Dollar);
    41. snackMachine.buySnack(1);
    42. assertEquals(snackMachine.getMoneyInTransaction(), Money.None.getAmount());
    43. assertEquals(snackMachine.getMoneyInside().getAmount(), 1, 0.5);
    44. assertEquals(snackMachine.getSnackPile(1).getQuantity(), 9);
    45. }
    46. @Test
    47. public void cannot_make_purchase_when_there_is_no_snacks() {
    48. SnackMachine snackMachine = new SnackMachine();
    49. assertThrows(IllegalStateException.class, () -> {
    50. snackMachine.buySnack(1);
    51. });
    52. }
    53. @Test
    54. public void cannot_make_purchase_if_not_enough_money_inserted() {
    55. SnackMachine snackMachine = new SnackMachine();
    56. snackMachine.loadSnacks(1, new SnackPile(new Snack("Some snack"), 1, 2));
    57. snackMachine.insertMoney(Dollar);
    58. assertThrows(IllegalStateException.class, () -> {
    59. snackMachine.buySnack(1);
    60. });
    61. }
    62. /* @Test
    63. public void snack_machine_returns_money_with_highest_denomination_first() {
    64. SnackMachine snackMachine = new SnackMachine();
    65. snackMachine.loadMoney(Dollar);
    66. snackMachine.insertMoney(Quarter);
    67. snackMachine.insertMoney(Quarter);
    68. snackMachine.insertMoney(Quarter);
    69. snackMachine.insertMoney(Quarter);
    70. snackMachine.returnMoney();
    71. assertEquals(snackMachine.getMoneyInside().getQuarterCount(), 4);
    72. assertEquals(snackMachine.getMoneyInside().getOneDollarCount(), 0);
    73. }*/
    74. }

    我们将moneyInTransaction重构为float类型,因为moneyInTransaction和找零的数额可能是不同的(找零优先高面额)。在此重构了和此字段有关的所有类。还需要重构Controller和SnackMachine:

    1. package com.lugew.springbootddd;
    2. import com.lugew.springbootddd.snackmachine.SnackMachine;
    3. import lombok.RequiredArgsConstructor;
    4. import org.springframework.web.bind.annotation.*;
    5. import static com.lugew.springbootddd.snackmachine.Money.*;
    6. /**
    7. * @author 夏露桂
    8. * @since 2021/6/10 11:55
    9. */
    10. @RestController
    11. @RequestMapping("snackmachines")
    12. @RequiredArgsConstructor
    13. public class SnackMachineController {
    14. private final SnackMachineRepository snackMachineRepository;
    15. @GetMapping("/{id}")
    16. @ResponseBody
    17. public SnackMachineDto getSnackMachine(@PathVariable("id") long id) {
    18. return snackMachineRepository.findById(id).orElse(null);
    19. }
    20. @PutMapping("/{id}/moneyInTransaction/{coinOrNote}")
    21. public void insertCoinOrNote(@PathVariable("id") long id, @PathVariable("coinOrNote") String coinOrNote) {
    22. SnackMachineDto snackMachineDto =
    23. snackMachineRepository.findById(id).orElse(null);
    24. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    25. if (coinOrNote.equalsIgnoreCase("Cent")) snackMachine.insertMoney(Cent);
    26. else if (coinOrNote.equalsIgnoreCase("TenCent"))
    27. snackMachine.insertMoney(TenCent);
    28. else if (coinOrNote.equalsIgnoreCase("Quarter"))
    29. snackMachine.insertMoney(Quarter);
    30. else if (coinOrNote.equalsIgnoreCase("Dollar"))
    31. snackMachine.insertMoney(Dollar);
    32. else if (coinOrNote.equalsIgnoreCase("FiveDollar"))
    33. snackMachine.insertMoney(FiveDollar);
    34. else if (coinOrNote.equalsIgnoreCase("TwentyDollar"))
    35. snackMachine.insertMoney(TwentyDollar);
    36. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    37. }
    38. @PutMapping("/{id}/moneyInTransaction")
    39. public void returnMoney(@PathVariable("id") long id) {
    40. SnackMachineDto snackMachineDto =
    41. snackMachineRepository.findById(id).orElse(null);
    42. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    43. snackMachine.returnMoney();
    44. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    45. }
    46. @PutMapping("/{id}/{slotNumber}")
    47. public void buySnack(@PathVariable("id") long id, @PathVariable("slotNumber")
    48. int slotNumber) {
    49. SnackMachineDto snackMachineDto =
    50. snackMachineRepository.findById(id).orElse(null);
    51. SnackMachine snackMachine = snackMachineDto.convertToSnackMachine();
    52. snackMachine.buySnack(slotNumber);
    53. snackMachineRepository.save(snackMachine.convertToSnackMachineDto());
    54. }
    55. }
    1. package com.lugew.springbootddd.snackmachine;
    2. import com.lugew.springbootddd.*;
    3. import lombok.Getter;
    4. import lombok.Setter;
    5. import java.util.ArrayList;
    6. import java.util.Arrays;
    7. import java.util.List;
    8. import static com.lugew.springbootddd.snackmachine.Money.None;
    9. public final class SnackMachine extends AggregateRoot {
    10. @Getter
    11. @Setter
    12. private Money moneyInside;
    13. @Getter
    14. @Setter
    15. private float moneyInTransaction;
    16. @Setter
    17. private List<Slot> slots;
    18. public SnackMachine() {
    19. moneyInside = None;
    20. moneyInTransaction = 0;
    21. slots = new ArrayList<>();
    22. slots.add(new Slot(this, 1));
    23. slots.add(new Slot(this, 2));
    24. slots.add(new Slot(this, 3));
    25. }
    26. public Slot getSlot(int position) {
    27. return slots.stream().filter(x -> x.getPosition() ==
    28. position).findAny().orElse(null);
    29. }
    30. public SnackPile getSnackPile(int position) {
    31. return getSlot(position).getSnackPile();
    32. }
    33. public void insertMoney(Money money) {
    34. Money[] coinsAndNotes = {Money.Cent, Money.TenCent, Money.Quarter,
    35. Money.Dollar, Money.FiveDollar,
    36. Money.TwentyDollar};
    37. if (!Arrays.asList(coinsAndNotes).contains(money))
    38. throw new IllegalStateException();
    39. moneyInTransaction = moneyInTransaction + money.getAmount();
    40. moneyInside = moneyInside.add(money);
    41. }
    42. public void returnMoney() {
    43. moneyInTransaction = 0;
    44. }
    45. public void buySnack(int position) {
    46. Slot slot = getSlot(position);
    47. if (slot.getSnackPile().getPrice() > moneyInTransaction) {
    48. throw new IllegalStateException();
    49. }
    50. slot.setSnackPile(slot.getSnackPile().subtractOne());
    51. moneyInTransaction = 0;
    52. }
    53. public SnackMachineDto convertToSnackMachineDto() {
    54. SnackMachineDto snackMachineDto = new SnackMachineDto();
    55. snackMachineDto.setId(id);
    56. snackMachineDto.setMoneyInTransaction(moneyInTransaction);
    57. List<SlotDto> slotDtoList = new ArrayList<>();
    58. for (Slot slot : slots) slotDtoList.add(slot.convertToSlotDto());
    59. snackMachineDto.setSlotDtoList(slotDtoList);
    60. snackMachineDto.setOneCentCount(moneyInside.getOneCentCount());
    61. snackMachineDto.setTenCentCount(moneyInside.getTenCentCount());
    62. snackMachineDto.setQuarterCount(moneyInside.getQuarterCount());
    63. snackMachineDto.setOneDollarCount(moneyInside.getOneDollarCount());
    64. snackMachineDto.setFiveDollarCount(moneyInside.getFiveDollarCount());
    65. snackMachineDto.setTwentyDollarCount(moneyInside.getTwentyDollarCount());
    66. return snackMachineDto;
    67. }
    68. public void loadSnacks(int position, SnackPile snackPile) {
    69. Slot slot = slots.stream().filter(x -> x.getPosition() ==
    70. position).findAny().orElse(null);
    71. if (slot != null) {
    72. slot.setSnackPile(snackPile);
    73. }
    74. }
    75. public void loadMoney(Money money) {
    76. moneyInside = moneyInside.add(money);
    77. }
    78. }
    1. package com.lugew.springbootddd;
    2. import com.lugew.springbootddd.snackmachine.Money;
    3. import com.lugew.springbootddd.snackmachine.SnackMachine;
    4. import lombok.Getter;
    5. import lombok.Setter;
    6. import javax.persistence.Entity;
    7. import javax.persistence.*;
    8. import java.util.ArrayList;
    9. import java.util.List;
    10. /**
    11. * @author 夏露桂
    12. * @since 2021/6/10 12:00
    13. */
    14. @Getter
    15. @Setter
    16. @Entity
    17. public class SnackMachineDto {
    18. @Id
    19. @GeneratedValue
    20. private long id;
    21. private int oneCentCount;
    22. private int tenCentCount;
    23. private int quarterCount;
    24. private int oneDollarCount;
    25. private int fiveDollarCount;
    26. private int twentyDollarCount;
    27. private float moneyInTransaction;
    28. @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    29. @JoinColumn(name = "snackMachineId")
    30. private List<SlotDto> slotDtoList;
    31. public SnackMachine convertToSnackMachine() {
    32. SnackMachine snackMachine = new SnackMachine();
    33. snackMachine.setId(id);
    34. snackMachine.setMoneyInTransaction(moneyInTransaction);
    35. snackMachine.setMoneyInside(new
    36. Money(oneCentCount, tenCentCount, quarterCount,
    37. oneDollarCount, fiveDollarCount, twentyDollarCount));
    38. List<Slot> slotList = new ArrayList<>();
    39. for (SlotDto slotDto : slotDtoList) {
    40. slotList.add(slotDto.convertToSlot());
    41. }
    42. snackMachine.setSlots(slotList);
    43. return snackMachine;
    44. }
    45. }
    1. @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    2. @JoinColumn(name = "snackMachineId")
    3. private List<SlotDto> slotDtoList;

    一对多关系会改变数据库,Springboot已帮我们实现。

    1. @Test
    2. public void snack_machine_returns_money_with_highest_denomination_first() {
    3. SnackMachine snackMachine = new SnackMachine();
    4. snackMachine.loadMoney(Dollar);
    5. snackMachine.insertMoney(Quarter);
    6. snackMachine.insertMoney(Quarter);
    7. snackMachine.insertMoney(Quarter);
    8. snackMachine.insertMoney(Quarter);
    9. snackMachine.returnMoney();
    10. assertEquals(snackMachine.getMoneyInside().getQuarterCount(), 4);
    11. assertEquals(snackMachine.getMoneyInside().getOneDollarCount(), 0);
    12. }

    显然测试是失败的:
    image.png
    这是我们需要重构returnMoney方法。我们给Money类引入allocate累加方法:

    1. public void returnMoney() {
    2. Money moneyToReturn = moneyInside.allocate(moneyInTransaction);
    3. moneyInside = moneyInside.substract(moneyToReturn);
    4. moneyInTransaction = 0;
    5. }
    1. public boolean canAllocate(float amount) {
    2. Money money = allocateCore(amount);
    3. return money.amount == amount;
    4. }
    5. public Money allocate(float amount) {
    6. if (!canAllocate(amount))
    7. throw new IllegalStateException();
    8. return allocateCore(amount);
    9. }
    10. private Money allocateCore(float amount) {
    11. int twentyDollarCount = Math.min((int) (amount / 20), this.twentyDollarCount);
    12. amount = amount - twentyDollarCount * 20;
    13. int fiveDollarCount = Math.min((int) (amount / 5), this.fiveDollarCount);
    14. amount = amount - fiveDollarCount * 5;
    15. int oneDollarCount = Math.min((int) amount, this.oneDollarCount);
    16. amount = amount - oneDollarCount;
    17. int quarterCount = Math.min((int) (amount / 0.25f), this.quarterCount);
    18. amount = amount - quarterCount * 0.25f;
    19. int tenCentCount = Math.min((int) (amount / 0.1f), this.tenCentCount);
    20. amount = amount - tenCentCount * 0.1f;
    21. int oneCentCount = Math.min((int) (amount / 0.01f), this.oneCentCount);
    22. return new Money(
    23. oneCentCount,
    24. tenCentCount,
    25. quarterCount,
    26. oneDollarCount,
    27. fiveDollarCount,
    28. twentyDollarCount
    29. );
    30. }

    另一个是找零功能:

    1. @Test
    2. public void after_purchase_change_is_returned() {
    3. SnackMachine snackMachine = new SnackMachine();
    4. snackMachine.loadSnacks(1, new SnackPile(new Snack("Some snack"), 1,
    5. 0.5f));
    6. snackMachine.loadMoney(
    7. new Money(0, 10, 0, 0, 0, 0)
    8. );
    9. snackMachine.insertMoney(Dollar);
    10. snackMachine.buySnack(1);
    11. assertEquals(snackMachine.getMoneyInside().getAmount(), 1.5, 0);
    12. assertEquals(snackMachine.getMoneyInTransaction(), 0, 0);
    13. }

    image.png
    最后是无法找零禁止购买:

    1. @Test
    2. public void cannot_buy_snack_if_not_enough_change() {
    3. SnackMachine snackMachine = new SnackMachine();
    4. snackMachine.loadSnacks(1, new SnackPile(new Snack("Some snack"), 1, 0.5f));
    5. snackMachine.insertMoney(Dollar);
    6. assertThrows(IllegalStateException.class, () -> {
    7. snackMachine.buySnack(1);
    8. });
    9. }

    image.png