From 08e13dcf431717ac2a6475cc61f831c82af58266 Mon Sep 17 00:00:00 2001 From: Georgi Kazakov Date: Wed, 14 May 2025 11:20:54 +0300 Subject: [PATCH] [] Gilded Rose Refactoring Kata , including "Conjured" case and tests for all the cases --- Java/pom.xml | 20 ++- .../main/java/com/gildedrose/GildedRose.java | 98 +++++++------- Java/src/main/java/com/gildedrose/Item.java | 23 ++-- .../java/com/gildedrose/enums/BorderDays.java | 18 +++ .../com/gildedrose/enums/BorderQualities.java | 15 +++ .../java/com/gildedrose/enums/ItemName.java | 27 ++++ .../service/AgedBrieQualityUpdater.java | 32 +++++ .../BackstagePassesQualityUpdater.java | 44 ++++++ .../service/ConjuredQualityUpdater.java | 24 ++++ .../service/GeneralQualityUpdater.java | 28 ++++ .../gildedrose/service/QualityUpdater.java | 11 ++ .../java/com/gildedrose/GildedRoseTest.java | 125 +++++++++++++++++- ...ttestFixture.java => TextTestFixture.java} | 24 ++-- 13 files changed, 412 insertions(+), 77 deletions(-) create mode 100644 Java/src/main/java/com/gildedrose/enums/BorderDays.java create mode 100644 Java/src/main/java/com/gildedrose/enums/BorderQualities.java create mode 100644 Java/src/main/java/com/gildedrose/enums/ItemName.java create mode 100644 Java/src/main/java/com/gildedrose/service/AgedBrieQualityUpdater.java create mode 100644 Java/src/main/java/com/gildedrose/service/BackstagePassesQualityUpdater.java create mode 100644 Java/src/main/java/com/gildedrose/service/ConjuredQualityUpdater.java create mode 100644 Java/src/main/java/com/gildedrose/service/GeneralQualityUpdater.java create mode 100644 Java/src/main/java/com/gildedrose/service/QualityUpdater.java rename Java/src/test/java/com/gildedrose/{TexttestFixture.java => TextTestFixture.java} (71%) diff --git a/Java/pom.xml b/Java/pom.xml index 3bd6aff8..1c55a873 100644 --- a/Java/pom.xml +++ b/Java/pom.xml @@ -10,10 +10,12 @@ 0.0.1-SNAPSHOT - 1.8 + 1.9 5.8.2 3.1 3.0.0-M4 + 1.18.38 + 2.0.17 UTF-8 @@ -24,6 +26,22 @@ ${junit.jupiter.version} test + + org.projectlombok + lombok + ${lombok.version} + true + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + diff --git a/Java/src/main/java/com/gildedrose/GildedRose.java b/Java/src/main/java/com/gildedrose/GildedRose.java index 87a3b926..fc3a5eb6 100644 --- a/Java/src/main/java/com/gildedrose/GildedRose.java +++ b/Java/src/main/java/com/gildedrose/GildedRose.java @@ -1,61 +1,61 @@ package com.gildedrose; -class GildedRose { - Item[] items; +import com.gildedrose.enums.ItemName; +import com.gildedrose.service.AgedBrieQualityUpdater; +import com.gildedrose.service.BackstagePassesQualityUpdater; +import com.gildedrose.service.ConjuredQualityUpdater; +import com.gildedrose.service.GeneralQualityUpdater; +import com.gildedrose.service.QualityUpdater; +import lombok.Data; - public GildedRose(Item[] items) { - this.items = items; - } +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.gildedrose.enums.ItemName.AGED_BRIE; +import static com.gildedrose.enums.ItemName.BACKSTAGE_PASSES; +import static com.gildedrose.enums.ItemName.CONJURED; +import static com.gildedrose.enums.ItemName.GENERAL; + +@Data +public class GildedRose { + + private static final Map qualityUpdaters = + Map.of(AGED_BRIE, new AgedBrieQualityUpdater(), + BACKSTAGE_PASSES, new BackstagePassesQualityUpdater(), + CONJURED, new ConjuredQualityUpdater(), + GENERAL, new GeneralQualityUpdater()); + + private final List items; public void updateQuality() { - for (int i = 0; i < items.length; i++) { - if (!items[i].name.equals("Aged Brie") - && !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { - if (items[i].quality > 0) { - if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { - items[i].quality = items[i].quality - 1; - } - } - } else { - if (items[i].quality < 50) { - items[i].quality = items[i].quality + 1; + Set itemNames = items.stream().map(item -> ItemName.resolve(item.getName())).collect(Collectors.toSet()); - if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { - if (items[i].sellIn < 11) { - if (items[i].quality < 50) { - items[i].quality = items[i].quality + 1; - } - } + for (ItemName itemName : itemNames) { + QualityUpdater qualityUpdater = null; - if (items[i].sellIn < 6) { - if (items[i].quality < 50) { - items[i].quality = items[i].quality + 1; - } - } - } - } + switch (itemName) { + case AGED_BRIE: + qualityUpdater = qualityUpdaters.get(AGED_BRIE); + break; + case BACKSTAGE_PASSES: + qualityUpdater = qualityUpdaters.get(BACKSTAGE_PASSES); + break; + case SULFURAS_HAND_RANGAROS: + // nothing has to be done in that case - this is immutable object + break; + case CONJURED: + qualityUpdater = qualityUpdaters.get(CONJURED); + break; + default: + qualityUpdater = qualityUpdaters.get(GENERAL); } - if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { - items[i].sellIn = items[i].sellIn - 1; - } - - if (items[i].sellIn < 0) { - if (!items[i].name.equals("Aged Brie")) { - if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { - if (items[i].quality > 0) { - if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { - items[i].quality = items[i].quality - 1; - } - } - } else { - items[i].quality = items[i].quality - items[i].quality; - } - } else { - if (items[i].quality < 50) { - items[i].quality = items[i].quality + 1; - } - } + if (qualityUpdater != null) { + List properItems = + items.stream().filter(item -> itemName.equals(ItemName.resolve(item.getName()))).collect(Collectors.toList()); + qualityUpdater.updateQuality(properItems); } } } diff --git a/Java/src/main/java/com/gildedrose/Item.java b/Java/src/main/java/com/gildedrose/Item.java index 465729ec..5aa1b301 100644 --- a/Java/src/main/java/com/gildedrose/Item.java +++ b/Java/src/main/java/com/gildedrose/Item.java @@ -1,21 +1,14 @@ package com.gildedrose; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor public class Item { - public String name; + private String name; + private int sellIn; + private int quality; - public int sellIn; - - public int quality; - - public Item(String name, int sellIn, int quality) { - this.name = name; - this.sellIn = sellIn; - this.quality = quality; - } - - @Override - public String toString() { - return this.name + ", " + this.sellIn + ", " + this.quality; - } } diff --git a/Java/src/main/java/com/gildedrose/enums/BorderDays.java b/Java/src/main/java/com/gildedrose/enums/BorderDays.java new file mode 100644 index 00000000..27a780a6 --- /dev/null +++ b/Java/src/main/java/com/gildedrose/enums/BorderDays.java @@ -0,0 +1,18 @@ +package com.gildedrose.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum BorderDays { + + INCREASE_QUALITY_BY_TWO_WHEN_LESS_THEN_OR_EQUAL_TEN_DAYS(10), + INCREASE_QUALITY_BY_THREE_WHEN_LESS_THEN_OR_EQUAL_FIVE_DAYS(5), + INCREASE_BY_TWO_DAYS(2), + INCREASE_BY_THREE_DAYS(3), + DATE_HAS_PASSED(0); + + private final int days; + +} diff --git a/Java/src/main/java/com/gildedrose/enums/BorderQualities.java b/Java/src/main/java/com/gildedrose/enums/BorderQualities.java new file mode 100644 index 00000000..499bdd6a --- /dev/null +++ b/Java/src/main/java/com/gildedrose/enums/BorderQualities.java @@ -0,0 +1,15 @@ +package com.gildedrose.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum BorderQualities { + + MIN_QUALITY(0), + MAX_QUALITY(50); + + private final int quality; + +} diff --git a/Java/src/main/java/com/gildedrose/enums/ItemName.java b/Java/src/main/java/com/gildedrose/enums/ItemName.java new file mode 100644 index 00000000..874c9f32 --- /dev/null +++ b/Java/src/main/java/com/gildedrose/enums/ItemName.java @@ -0,0 +1,27 @@ +package com.gildedrose.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Set; + +@Getter +@RequiredArgsConstructor +public enum ItemName { + + BACKSTAGE_PASSES("Backstage passes to a TAFKAL80ETC concert"), + SULFURAS_HAND_RANGAROS("Sulfuras, Hand of Ragnaros"), + AGED_BRIE("Aged Brie"), + GENERAL("General item"), + CONJURED("Conjured Mana Cake"); + + private final String name; + + public static ItemName resolve(String name) { + return Set.of(ItemName.values()).stream() + .filter(itemName -> name.equalsIgnoreCase(itemName.getName())) + .findFirst() + .orElse(GENERAL); + } + +} diff --git a/Java/src/main/java/com/gildedrose/service/AgedBrieQualityUpdater.java b/Java/src/main/java/com/gildedrose/service/AgedBrieQualityUpdater.java new file mode 100644 index 00000000..acbc2f93 --- /dev/null +++ b/Java/src/main/java/com/gildedrose/service/AgedBrieQualityUpdater.java @@ -0,0 +1,32 @@ +package com.gildedrose.service; + +import com.gildedrose.Item; +import lombok.NoArgsConstructor; + +import java.util.List; + +import static com.gildedrose.enums.BorderQualities.MAX_QUALITY; +import static com.gildedrose.enums.BorderQualities.MIN_QUALITY; + +@NoArgsConstructor +public class AgedBrieQualityUpdater implements QualityUpdater { + + @Override + public void updateQuality(List items) { + for (Item item : items) { + item.setSellIn(item.getSellIn() - 1); + // quantity actually always increases as the item becomes older + if (item.getQuality() < MAX_QUALITY.getQuality() && item.getQuality() >= MIN_QUALITY.getQuality()) { + // Once the sell by date has passed, quality degrades twice as fast - should this have to happen, because it is logical contradiction ??? + /*if (item.getSellIn() < DATE_HAS_PASSED.getDays()) { + item.setQuality(item.getQuality() / 2); + } else {*/ + if (item.getSellIn() < 0) { + item.setQuality(item.getQuality() + 2); + } else { + item.setQuality(item.getQuality() + 1); + } + } + } + } +} diff --git a/Java/src/main/java/com/gildedrose/service/BackstagePassesQualityUpdater.java b/Java/src/main/java/com/gildedrose/service/BackstagePassesQualityUpdater.java new file mode 100644 index 00000000..7103c670 --- /dev/null +++ b/Java/src/main/java/com/gildedrose/service/BackstagePassesQualityUpdater.java @@ -0,0 +1,44 @@ +package com.gildedrose.service; + +import com.gildedrose.Item; +import lombok.NoArgsConstructor; + +import java.util.List; + +import static com.gildedrose.enums.BorderDays.DATE_HAS_PASSED; +import static com.gildedrose.enums.BorderDays.INCREASE_BY_THREE_DAYS; +import static com.gildedrose.enums.BorderDays.INCREASE_BY_TWO_DAYS; +import static com.gildedrose.enums.BorderDays.INCREASE_QUALITY_BY_THREE_WHEN_LESS_THEN_OR_EQUAL_FIVE_DAYS; +import static com.gildedrose.enums.BorderDays.INCREASE_QUALITY_BY_TWO_WHEN_LESS_THEN_OR_EQUAL_TEN_DAYS; +import static com.gildedrose.enums.BorderQualities.MAX_QUALITY; +import static com.gildedrose.enums.BorderQualities.MIN_QUALITY; + +@NoArgsConstructor +public class BackstagePassesQualityUpdater implements QualityUpdater { + + @Override + public void updateQuality(List items) { + for (Item item : items) { + item.setSellIn(item.getSellIn() - 1); + + if (item.getSellIn() < DATE_HAS_PASSED.getDays()) { + item.setQuality(0); + } + + // quantity actually always increases as the item becomes older (if sellIn < 0 decreases twice by general condition!) + if (item.getQuality() < MAX_QUALITY.getQuality() && item.getQuality() > MIN_QUALITY.getQuality()) { + // Once the sell by date has passed, quality degrades twice as fast - - should this have to happen, because it is logical contradiction ??? + if (item.getSellIn() >= INCREASE_QUALITY_BY_THREE_WHEN_LESS_THEN_OR_EQUAL_FIVE_DAYS.getDays() && + item.getSellIn() < INCREASE_QUALITY_BY_TWO_WHEN_LESS_THEN_OR_EQUAL_TEN_DAYS.getDays() && + item.getQuality() + INCREASE_BY_TWO_DAYS.getDays() <= MAX_QUALITY.getQuality()) { + item.setQuality(item.getQuality() + INCREASE_BY_TWO_DAYS.getDays()); + } else if (item.getSellIn() < INCREASE_QUALITY_BY_THREE_WHEN_LESS_THEN_OR_EQUAL_FIVE_DAYS.getDays() && + item.getQuality() + INCREASE_BY_THREE_DAYS.getDays() <= MAX_QUALITY.getQuality()) { + item.setQuality(item.getQuality() + INCREASE_BY_THREE_DAYS.getDays()); + } else { + item.setQuality(item.getQuality() + 1); + } + } + } + } +} diff --git a/Java/src/main/java/com/gildedrose/service/ConjuredQualityUpdater.java b/Java/src/main/java/com/gildedrose/service/ConjuredQualityUpdater.java new file mode 100644 index 00000000..26c82d44 --- /dev/null +++ b/Java/src/main/java/com/gildedrose/service/ConjuredQualityUpdater.java @@ -0,0 +1,24 @@ +package com.gildedrose.service; + +import com.gildedrose.Item; +import lombok.NoArgsConstructor; + +import java.util.List; + +import static com.gildedrose.enums.BorderQualities.MAX_QUALITY; +import static com.gildedrose.enums.BorderQualities.MIN_QUALITY; + +@NoArgsConstructor +public class ConjuredQualityUpdater implements QualityUpdater { + @Override + public void updateQuality(List items) { + for (Item item : items) { + item.setSellIn(item.getSellIn() - 1); + if (item.getQuality() < MAX_QUALITY.getQuality() && + item.getQuality() > MIN_QUALITY.getQuality() && + item.getQuality() - 2 >= MIN_QUALITY.getQuality()) { + item.setQuality(item.getQuality() - 2); + } + } + } +} diff --git a/Java/src/main/java/com/gildedrose/service/GeneralQualityUpdater.java b/Java/src/main/java/com/gildedrose/service/GeneralQualityUpdater.java new file mode 100644 index 00000000..462e2baf --- /dev/null +++ b/Java/src/main/java/com/gildedrose/service/GeneralQualityUpdater.java @@ -0,0 +1,28 @@ +package com.gildedrose.service; + +import com.gildedrose.Item; +import lombok.NoArgsConstructor; + +import java.util.List; + +import static com.gildedrose.enums.BorderDays.DATE_HAS_PASSED; +import static com.gildedrose.enums.BorderQualities.MAX_QUALITY; +import static com.gildedrose.enums.BorderQualities.MIN_QUALITY; + +@NoArgsConstructor +public class GeneralQualityUpdater implements QualityUpdater { + + @Override + public void updateQuality(List items) { + for (Item item : items) { + item.setSellIn(item.getSellIn() - 1); + // at the end of each day decrease quantity and sellIn (quality > 0 and quantity <= 50 always) + if (item.getSellIn() < DATE_HAS_PASSED.getDays() && + item.getQuality() - 2 >= MIN_QUALITY.getQuality()) { + item.setQuality(item.getQuality() - 2); + } else if (item.getQuality() < MAX_QUALITY.getQuality() && item.getQuality() > MIN_QUALITY.getQuality()) { + item.setQuality(item.getQuality() - 1); + } + } + } +} diff --git a/Java/src/main/java/com/gildedrose/service/QualityUpdater.java b/Java/src/main/java/com/gildedrose/service/QualityUpdater.java new file mode 100644 index 00000000..52b4875a --- /dev/null +++ b/Java/src/main/java/com/gildedrose/service/QualityUpdater.java @@ -0,0 +1,11 @@ +package com.gildedrose.service; + +import com.gildedrose.Item; + +import java.util.List; + +public interface QualityUpdater { + + void updateQuality(List items); + +} diff --git a/Java/src/test/java/com/gildedrose/GildedRoseTest.java b/Java/src/test/java/com/gildedrose/GildedRoseTest.java index 8ae29eec..e520293b 100644 --- a/Java/src/test/java/com/gildedrose/GildedRoseTest.java +++ b/Java/src/test/java/com/gildedrose/GildedRoseTest.java @@ -1,6 +1,12 @@ package com.gildedrose; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,10 +14,125 @@ class GildedRoseTest { @Test void foo() { - Item[] items = new Item[] { new Item("foo", 0, 0) }; + List items = List.of(new Item("foo", 0, 0)); GildedRose app = new GildedRose(items); app.updateQuality(); - assertEquals("fixme", app.items[0].name); + assertEquals("foo", app.getItems().get(0).getName()); + } + + @ParameterizedTest + @MethodSource("agedBrieQualities") + void testAgedBrieQualityUpdater(String name, int sellIn, int quality) { + List items = List.of(new Item(name, sellIn, quality)); + GildedRose app = new GildedRose(items); + for (int i = 0; i <= sellIn; i++) { + app.updateQuality(); + } + for (Item item : items) { + assertEquals(name, item.getName()); + assertEquals(quality + sellIn + 2, item.getQuality()); + } + } + + private static Stream agedBrieQualities() { + return Stream.of( + Arguments.of("Aged Brie", 2, 0), + Arguments.of("Aged Brie", 5, 10)); + } + + @ParameterizedTest + @MethodSource("backstagePassesQualities") + void testBackstagePassesQualityUpdater(String name, int sellIn, int quality) { + List items = List.of(new Item(name, sellIn, quality)); + GildedRose app = new GildedRose(items); + + for (int i = 0; i < sellIn; i++) { + app.updateQuality(); + } + + for (Item item : items) { + assertEquals(name, item.getName()); + assertEquals(50 - item.getSellIn() * 5, item.getQuality()); + } + } + + private static Stream backstagePassesQualities() { + return Stream.of( + Arguments.of("Backstage passes to a TAFKAL80ETC concert", 15, 20), + Arguments.of("Backstage passes to a TAFKAL80ETC concert", 10, 49), + Arguments.of("Backstage passes to a TAFKAL80ETC concert", 5, 49)); + } + + @ParameterizedTest + @MethodSource("sulfurasQualities") + void testSulfurasQualityUpdater(String name, int sellIn, int quality) { + List items = List.of(new Item(name, sellIn, quality)); + GildedRose app = new GildedRose(items); + + for (int i = 0; i < sellIn; i++) { + app.updateQuality(); + } + + for (Item item : items) { + assertEquals(name, item.getName()); + assertEquals(80, item.getQuality()); + } + } + + private static Stream sulfurasQualities() { + return Stream.of( + Arguments.of("Sulfuras, Hand of Ragnaros", 0, 80), + Arguments.of("Sulfuras, Hand of Ragnaros", -1, 80), + Arguments.of("Sulfuras, Hand of Ragnaros", 5, 80)); + } + + @ParameterizedTest + @MethodSource("conjuredQualities") + void testConjuredQualityUpdater(String name, int sellIn, int quality) { + List items = List.of(new Item(name, sellIn, quality)); + GildedRose app = new GildedRose(items); + + for (int i = 0; i < sellIn; i++) { + app.updateQuality(); + } + + for (Item item : items) { + assertEquals(name, item.getName()); + assertEquals(Math.max(quality - sellIn * 2, 0), item.getQuality()); + } + } + + private static Stream conjuredQualities() { + return Stream.of( + Arguments.of("Conjured Mana Cake", 3, 6), + Arguments.of("Conjured Mana Cake", 2, 5), + Arguments.of("Conjured Mana Cake", 7, 10)); + } + + @ParameterizedTest + @MethodSource("generalQualities") + void testGeneralQualityUpdater(String name, int sellIn, int quality) { + List items = List.of(new Item(name, sellIn, quality)); + GildedRose app = new GildedRose(items); + + for (int i = 0; i < sellIn; i++) { + app.updateQuality(); + } + + for (Item item : items) { + assertEquals(name, item.getName()); + if (item.getSellIn() < 0) { + assertEquals(quality - sellIn * 2, item.getQuality()); + } else { + assertEquals(quality - sellIn, item.getQuality()); + } + } + } + + private static Stream generalQualities() { + return Stream.of( + Arguments.of("+5 Dexterity Vest", 10, 20), + Arguments.of("Elixir of the Mongoose", 5, 7)); } } diff --git a/Java/src/test/java/com/gildedrose/TexttestFixture.java b/Java/src/test/java/com/gildedrose/TextTestFixture.java similarity index 71% rename from Java/src/test/java/com/gildedrose/TexttestFixture.java rename to Java/src/test/java/com/gildedrose/TextTestFixture.java index d059c88f..5e79b2a1 100644 --- a/Java/src/test/java/com/gildedrose/TexttestFixture.java +++ b/Java/src/test/java/com/gildedrose/TextTestFixture.java @@ -1,10 +1,15 @@ package com.gildedrose; -public class TexttestFixture { - public static void main(String[] args) { - System.out.println("OMGHAI!"); +import lombok.extern.slf4j.Slf4j; - Item[] items = new Item[] { +import java.util.List; + +@Slf4j +public class TextTestFixture { + public static void main(String[] args) { + log.info("OMGHAI!"); + + List items = List.of( new Item("+5 Dexterity Vest", 10, 20), // new Item("Aged Brie", 2, 0), // new Item("Elixir of the Mongoose", 5, 7), // @@ -14,22 +19,21 @@ public class TexttestFixture { new Item("Backstage passes to a TAFKAL80ETC concert", 10, 49), new Item("Backstage passes to a TAFKAL80ETC concert", 5, 49), // this conjured item does not work properly yet - new Item("Conjured Mana Cake", 3, 6) }; + new Item("Conjured Mana Cake", 3, 6)); GildedRose app = new GildedRose(items); - int days = 2; + int days = 12; if (args.length > 0) { days = Integer.parseInt(args[0]) + 1; } for (int i = 0; i < days; i++) { - System.out.println("-------- day " + i + " --------"); - System.out.println("name, sellIn, quality"); + log.info("-------- day {} --------", i); + log.info("name, sellIn, quality"); for (Item item : items) { - System.out.println(item); + log.info(item.toString()); } - System.out.println(); app.updateQuality(); } }