Zurück zum Supermarkt. Wir implementieren den Code für ein Kassensystem, das die Preisgestaltung übernimmt. Zu implementieren sind solche Regeln wie “Äpfel kosten 50 Cent, drei Äpfel kosten $1,30.”
In einem normalen Supermarkt werden die Dinge über SKUs identifiziert. In unserem können wir Buchstaben des Alphabets verwenden (A, B, C, und so weiter). Unsere Waren werden individuell berechnet. Darüber hinaus werden einige Elemente rabattiert: kaufen Sie n davon, und diese werden Ihnen y Cent kosten. Beispielsweise kann Artikel “A” 50 Cent einzeln kosten, aber in dieser Woche haben wir ein besonderes Angebot: kaufen drei von A und zahlen Sie $1,30. In der Tat sehen in dieser Woche die Preise wie folgt aus:
Item | Unit Price | Special Price |
A | 50 | 3 for 130 |
B | 30 | 2 for 45 |
C | 20 | |
D | 15 |
Unsere Kasse übernimmt Elemente in beliebiger Reihenfolge, so dass, wenn wir ein B scannen dann ein A und ein anderes B, bekommen wir einen Gesamtpreis von 95 (wir erkennen, das wir einen Mengenrabatt auf 2xB geben müssen). Da der Preis sich häufig ändert, müssen wir in der Lage sein den Gesamtpreis bei jeder Transaktion neu zu berechnen.
Das Interface für Checkout kann wie folgt aussehen:
co = CheckOut.new(pricing_rules) co.scan(item) co.scan(item) : : price = co.total
Hier ist ein Unit-Test implementiert in Ruby. Die Hilfsmethode preis bekommt eine Liste von Artikel und ruft scan-Methode der Kasse für jedes Element der Liste auf, bevor sie schließlich wieder den Gesamtpreis ausgibt.
class TestPrice < Test::Unit::TestCase def price(goods) co = CheckOut.new(RULES) goods.split(//).each { |item| co.scan(item) } co.total end def test_totals assert_equal( 0, price("")) assert_equal( 50, price("A")) assert_equal( 80, price("AB")) assert_equal(115, price("CDBA")) assert_equal(100, price("AA")) assert_equal(130, price("AAA")) assert_equal(180, price("AAAA")) assert_equal(230, price("AAAAA")) assert_equal(260, price("AAAAAA")) assert_equal(160, price("AAAB")) assert_equal(175, price("AAABB")) assert_equal(190, price("AAABBD")) assert_equal(190, price("DABABA")) end def test_incremental co = CheckOut.new(RULES) assert_equal( 0, co.total) co.scan("A"); assert_equal( 50, co.total) co.scan("B"); assert_equal( 80, co.total) co.scan("A"); assert_equal(130, co.total) co.scan("A"); assert_equal(160, co.total) co.scan("B"); assert_equal(175, co.total) end end
Es gibt viele Wege zur Umsetzung dieser Art von Algorithmus; wenn Sie Zeit haben, experimentieren Sie mit mehreren.
Ziele der Kata
Zu einem gewissen Grad ist dies nur ein lustiges kleines Problem. Der Format der Preisregeln wird in der Aufgabe nicht beschrieben. Wie können diese aussehen, damit der Checkout ohne das Wissen über die Preisregeln-Strategie immer einen korrekten Gesamtpreis berechnen kann? Wie können wir das Design flexibel genug halten, so dass wir neue Preisregeln in der Zukunft hinzufügen können?