Snack只有三种类型,不允许出现其它类型:

    1. package com.lugew.springbootddd;
    2. import lombok.Getter;
    3. import lombok.Setter;
    4. /**
    5. * @author 夏露桂
    6. * @since 2021/6/16 10:45
    7. */
    8. @Getter
    9. @Setter
    10. public class Snack extends AggregateRoot {
    11. public static Snack None = new Snack(0, "None");
    12. public static Snack Chocolate = new Snack(1, "Chocolate");
    13. public static Snack Soda = new Snack(2, "Soda");
    14. public static Snack Gum = new Snack(3, "Gum");
    15. private String name;
    16. private Snack(long id, String name) {
    17. this.id = id;
    18. this.name = name;
    19. }
    20. public SnackDto convertToSnackDto() {
    21. SnackDto snackDto = new SnackDto();
    22. snackDto.setId(id);
    23. snackDto.setName(name);
    24. return snackDto;
    25. }
    26. }

    随后将测试中的new Snack(“Some snack”) 替换为Chocolate。

    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.*;
    6. import static org.junit.jupiter.api.Assertions.assertEquals;
    7. import static org.junit.jupiter.api.Assertions.assertThrows;
    8. /**
    9. * @author 夏露桂
    10. * @since 2021/6/9 18:12
    11. */
    12. public class SnackMachineTest {
    13. @Test
    14. public void return_money_empties_money_in_transaction() {
    15. SnackMachine snackMachine = new SnackMachine();
    16. snackMachine.insertMoney(Dollar);
    17. snackMachine.returnMoney();
    18. assertEquals(snackMachine.getMoneyInTransaction(), 0, 0);
    19. }
    20. @Test
    21. public void inserted_money_goes_to_money_in_transaction() {
    22. SnackMachine snackMachine = new SnackMachine();
    23. snackMachine.insertMoney(Cent);
    24. snackMachine.insertMoney(Dollar);
    25. assertEquals(snackMachine.getMoneyInTransaction(), 1.01f, 0);
    26. }
    27. @Test
    28. public void cannot_insert_more_than_one_coin_or_note_at_a_time() {
    29. SnackMachine snackMachine = new SnackMachine();
    30. Money twoCent = Money.add(Cent, Cent);
    31. assertThrows(IllegalStateException.class, () -> {
    32. snackMachine.insertMoney(twoCent);
    33. });
    34. }
    35. @Test
    36. public void buySnack_trades_inserted_money_for_a_snack() {
    37. SnackMachine snackMachine = new SnackMachine();
    38. snackMachine.loadSnacks(1, new SnackPile(Snack.Chocolate, 10, 1));
    39. snackMachine.insertMoney(Dollar);
    40. snackMachine.buySnack(1);
    41. assertEquals(snackMachine.getMoneyInTransaction(), Money.None.getAmount());
    42. assertEquals(snackMachine.getMoneyInside().getAmount(), 1, 0.5);
    43. assertEquals(snackMachine.getSnackPile(1).getQuantity(), 9);
    44. }
    45. @Test
    46. public void cannot_make_purchase_when_there_is_no_snacks() {
    47. SnackMachine snackMachine = new SnackMachine();
    48. assertThrows(IllegalStateException.class, () -> {
    49. snackMachine.buySnack(1);
    50. });
    51. }
    52. @Test
    53. public void cannot_make_purchase_if_not_enough_money_inserted() {
    54. SnackMachine snackMachine = new SnackMachine();
    55. snackMachine.loadSnacks(1, new SnackPile(Snack.Chocolate, 1, 2));
    56. snackMachine.insertMoney(Dollar);
    57. assertThrows(IllegalStateException.class, () -> {
    58. snackMachine.buySnack(1);
    59. });
    60. }
    61. @Test
    62. public void snack_machine_returns_money_with_highest_denomination_first() {
    63. SnackMachine snackMachine = new SnackMachine();
    64. snackMachine.loadMoney(Dollar);
    65. snackMachine.insertMoney(Quarter);
    66. snackMachine.insertMoney(Quarter);
    67. snackMachine.insertMoney(Quarter);
    68. snackMachine.insertMoney(Quarter);
    69. snackMachine.returnMoney();
    70. assertEquals(snackMachine.getMoneyInside().getQuarterCount(), 4);
    71. assertEquals(snackMachine.getMoneyInside().getOneDollarCount(), 0);
    72. }
    73. @Test
    74. public void after_purchase_change_is_returned() {
    75. SnackMachine snackMachine = new SnackMachine();
    76. snackMachine.loadSnacks(1, new SnackPile(Snack.Chocolate, 1,
    77. 0.5f));
    78. snackMachine.loadMoney(
    79. new Money(0, 10, 0, 0, 0, 0)
    80. );
    81. snackMachine.insertMoney(Dollar);
    82. snackMachine.buySnack(1);
    83. assertEquals(snackMachine.getMoneyInside().getAmount(), 1.5, 0);
    84. assertEquals(snackMachine.getMoneyInTransaction(), 0, 0);
    85. }
    86. @Test
    87. public void cannot_buy_snack_if_not_enough_change() {
    88. SnackMachine snackMachine = new SnackMachine();
    89. snackMachine.loadSnacks(1, new SnackPile(Snack.Chocolate, 1, 0.5f));
    90. snackMachine.insertMoney(Dollar);
    91. assertThrows(IllegalStateException.class, () -> {
    92. snackMachine.buySnack(1);
    93. });
    94. }
    95. }

    此外Slot构造器也可以优化:

    1. public class Slot extends Entity {
    2. public Slot(SnackMachine snackMachine, int position) {
    3. this.snackMachine = snackMachine;
    4. this.position = position;
    5. this.snackPile = new SnackPile(Snack.None, 0, 0);
    6. }
    7. }

    再次重构:

    1. public class SnackPile extends ValueObject<SnackPile> {
    2. public static SnackPile Empty = new SnackPile(Snack.None, 0, 0f);
    3. }

    最终结果:

    1. package com.lugew.springbootddd;
    2. import com.lugew.springbootddd.snackmachine.SnackMachine;
    3. import lombok.Getter;
    4. import lombok.Setter;
    5. /**
    6. * @author 夏露桂
    7. * @since 2021/6/16 10:47
    8. */
    9. @Getter
    10. @Setter
    11. public class Slot extends Entity {
    12. private SnackPile snackPile;
    13. private SnackMachine snackMachine;
    14. private int position;
    15. public Slot() {
    16. }
    17. public Slot(SnackMachine snackMachine, int position) {
    18. this.snackMachine = snackMachine;
    19. this.position = position;
    20. this.snackPile = SnackPile.Empty;
    21. }
    22. public SlotDto convertToSlotDto() {
    23. SlotDto slotDto = new SlotDto();
    24. slotDto.setId(id);
    25. slotDto.setPosition(position);
    26. slotDto.setPrice(snackPile.getPrice());
    27. slotDto.setQuantity(snackPile.getQuantity());
    28. slotDto.setSnackDto(snackPile.getSnack().convertToSnackDto());
    29. return slotDto;
    30. }
    31. public SnackPile getSnackPile() {
    32. return snackPile;
    33. }
    34. public void setSnackPile(SnackPile snackPile) {
    35. this.snackPile = snackPile;
    36. }
    37. public SnackMachine getSnackMachine() {
    38. return snackMachine;
    39. }
    40. public void setSnackMachine(SnackMachine snackMachine) {
    41. this.snackMachine = snackMachine;
    42. }
    43. public int getPosition() {
    44. return position;
    45. }
    46. public void setPosition(int position) {
    47. this.position = position;
    48. }
    49. }

    👆持续迭代在DDD中十分常见。

    Snack类型的校验可在集成测试中验证。