| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.functor.example.kata.one; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import org.apache.commons.functor.Function; |
| import org.apache.commons.functor.adapter.BinaryFunctionFunction; |
| import org.apache.commons.functor.core.Identity; |
| import org.apache.commons.functor.core.comparator.IsGreaterThan; |
| import org.apache.commons.functor.core.composite.Composite; |
| import org.apache.commons.functor.core.composite.ConditionalFunction; |
| import org.apache.commons.functor.core.composite.CompositeBinaryFunction; |
| import org.junit.Test; |
| |
| /** |
| * Dave Thomas's Kata One asks us to think about how one might |
| * implement pricing rules: |
| * |
| * "Some things in supermarkets have simple prices: this can of |
| * beans costs $0.65. Other things have more complex prices. |
| * For example: |
| * |
| * o three for a dollar (so what?s the price if I buy 4, or 5?) |
| * |
| * o $1.99/pound (so what does 4 ounces cost?) |
| * |
| * o buy two, get one free (so does the third item have a price?)" |
| * |
| * Functors provide one approach to this sort of problem, and in |
| * this example we'll demonstrate some simple cases. |
| * |
| * See http://pragprog.com/pragdave/Practices/Kata/KataOne.rdoc,v |
| * for more information on this Kata. |
| * |
| * @version $Revision$ $Date$ |
| */ |
| public class SupermarketPricingExample { |
| |
| // tests |
| //---------------------------------------------------------- |
| |
| /* |
| * The simplest form of pricing is simply a constant |
| * rate. In Dave's example, a can of beans costs $0.65, |
| * and n cans of beans cost n*0.65. |
| * |
| * This pricing rule simply multiplies the quantity by |
| * a constant, e.g.: |
| * ToMoney.from(Multiply.by(65)) |
| * |
| * This case is so common, we may want to introduce a |
| * special Product constructor to wrap up create the |
| * functors for us. |
| */ |
| @Test |
| public void testConstantPricePerUnit() throws Exception { |
| { |
| Product beans = new Product( |
| "Can of Beans", |
| "SKU-0001", |
| ToMoney.from(Multiply.by(65))); |
| |
| assertEquals(new Money(0*65),beans.getPrice(0)); |
| assertEquals(new Money(1*65),beans.getPrice(1)); |
| assertEquals(new Money(2*65),beans.getPrice(2)); |
| assertEquals(new Money(3*65),beans.getPrice(3)); |
| } |
| // or, using the speical constructor: |
| { |
| Product beans = new Product( |
| "Can of Beans", |
| "SKU-0001", |
| 65); |
| |
| assertEquals(new Money(0*65),beans.getPrice(0)); |
| assertEquals(new Money(1*65),beans.getPrice(1)); |
| assertEquals(new Money(2*65),beans.getPrice(2)); |
| assertEquals(new Money(3*65),beans.getPrice(3)); |
| } |
| } |
| |
| /* |
| * A slighly more complicated example is a bulk |
| * discount. For example, bananas may be |
| * $0.33 cents each, or 4 for a dollar ($1.00). |
| * |
| * This rule is underspecified by itself, there are |
| * at least two ways to interpret this pricing rule: |
| * |
| * a) the cost is $0.33 cents for 3 or fewer, $0.25 |
| * for 4 or more |
| * |
| * or |
| * |
| * b) the cost is $1.00 for every group of 4, $0.33 |
| * each for anything left over |
| * |
| * although I think in practice, "4 for a dollar" |
| * usually means the former and not the latter. |
| * |
| * We can implement either: |
| */ |
| @Test |
| public void testFourForADollar_A() throws Exception { |
| Product banana = new Product( |
| "Banana", |
| "SKU-0002", |
| ToMoney.from( |
| new ConditionalFunction<Integer, Number>( |
| IsGreaterThan.instance(new Integer(3)), |
| Multiply.by(25), |
| Multiply.by(33)))); |
| |
| assertEquals(new Money(0*33),banana.getPrice(0)); |
| assertEquals(new Money(1*33),banana.getPrice(1)); |
| assertEquals(new Money(2*33),banana.getPrice(2)); |
| assertEquals(new Money(3*33),banana.getPrice(3)); |
| assertEquals(new Money(4*25),banana.getPrice(4)); |
| assertEquals(new Money(5*25),banana.getPrice(5)); |
| assertEquals(new Money(6*25),banana.getPrice(6)); |
| assertEquals(new Money(7*25),banana.getPrice(7)); |
| assertEquals(new Money(8*25),banana.getPrice(8)); |
| } |
| |
| |
| @Test |
| public void testFourForADollar_B() throws Exception { |
| Product banana = new Product( |
| "Banana", |
| "SKU-0002", |
| ToMoney.from( |
| new BinaryFunctionFunction<Integer, Number>( |
| new CompositeBinaryFunction<Integer, Integer, Number>( |
| Add.instance(), |
| Composite.function( |
| Multiply.by(100), |
| Divide.by(4)), |
| Composite.function( |
| Multiply.by(33), |
| Mod.by(4)))))); |
| assertEquals(new Money(0*33+0*25),banana.getPrice(0)); |
| assertEquals(new Money(1*33+0*25),banana.getPrice(1)); |
| assertEquals(new Money(2*33+0*25),banana.getPrice(2)); |
| assertEquals(new Money(3*33+0*25),banana.getPrice(3)); |
| assertEquals(new Money(0*33+4*25),banana.getPrice(4)); |
| assertEquals(new Money(1*33+4*25),banana.getPrice(5)); |
| assertEquals(new Money(2*33+4*25),banana.getPrice(6)); |
| assertEquals(new Money(3*33+4*25),banana.getPrice(7)); |
| assertEquals(new Money(0*33+8*25),banana.getPrice(8)); |
| } |
| |
| |
| /* |
| * Another interesting pricing rule is |
| * something like "buy 2, get 1 free". |
| * |
| * This may be implemented using a formula |
| * like: |
| * costPerUnit * (quantity - quantity / 2) |
| * |
| * For example... |
| */ |
| @Test |
| public void testBuyTwoGetOneFree_1() throws Exception { |
| Product apple = new Product( |
| "Apple", |
| "SKU-0003", |
| ToMoney.from( |
| Composite.function(Multiply.by(40), |
| BinaryFunctionFunction.adapt(new CompositeBinaryFunction<Number, Number, Number>(Subtract.instance(), |
| new Identity<Number>(), |
| Divide.by(3)))))); |
| |
| assertEquals(new Money(0*40),apple.getPrice(0)); |
| assertEquals(new Money(1*40),apple.getPrice(1)); |
| assertEquals(new Money(2*40),apple.getPrice(2)); |
| assertEquals(new Money(2*40),apple.getPrice(3)); |
| assertEquals(new Money(3*40),apple.getPrice(4)); |
| assertEquals(new Money(4*40),apple.getPrice(5)); |
| assertEquals(new Money(4*40),apple.getPrice(6)); |
| assertEquals(new Money(5*40),apple.getPrice(7)); |
| assertEquals(new Money(6*40),apple.getPrice(8)); |
| assertEquals(new Money(6*40),apple.getPrice(9)); |
| assertEquals(new Money(7*40),apple.getPrice(10)); |
| } |
| |
| /* |
| * ...but our pricing rule is starting to get ugly, |
| * and we haven't even considered things |
| * something like "buy 3, get 2 free", etc. |
| * |
| * Perhaps a special Function instance is in |
| * order: |
| */ |
| |
| class BuyNGetMFree implements Function<Number, Number> { |
| public BuyNGetMFree(int n, int m, int costPerUnit) { |
| this.n = n; |
| this.m = m; |
| this.costPerUnit = costPerUnit; |
| } |
| |
| public Number evaluate(Number num) { |
| int quantity = num.intValue(); |
| int cost = 0; |
| |
| while(quantity >= n) { |
| // buy n |
| cost += n * costPerUnit; |
| quantity -= n; |
| // get m (or fewer) free |
| quantity -= Math.min(quantity,m); |
| } |
| // buy less than n |
| cost += quantity * costPerUnit; |
| |
| return new Integer(cost); |
| } |
| |
| private int n, m, costPerUnit; |
| } |
| |
| @Test |
| public void testBuyTwoGetOneFree_2() throws Exception { |
| Product apple = new Product( |
| "Apple", |
| "SKU-0003", |
| ToMoney.from(new BuyNGetMFree(2,1,40))); |
| |
| assertEquals(new Money(0*40),apple.getPrice(0)); |
| assertEquals(new Money(1*40),apple.getPrice(1)); |
| assertEquals(new Money(2*40),apple.getPrice(2)); |
| assertEquals(new Money(2*40),apple.getPrice(3)); |
| assertEquals(new Money(3*40),apple.getPrice(4)); |
| assertEquals(new Money(4*40),apple.getPrice(5)); |
| assertEquals(new Money(4*40),apple.getPrice(6)); |
| assertEquals(new Money(5*40),apple.getPrice(7)); |
| assertEquals(new Money(6*40),apple.getPrice(8)); |
| assertEquals(new Money(6*40),apple.getPrice(9)); |
| assertEquals(new Money(7*40),apple.getPrice(10)); |
| } |
| |
| @Test |
| public void testBuyThreeGetTwoFree() throws Exception { |
| Product apple = new Product( |
| "Apple", |
| "SKU-0003", |
| ToMoney.from(new BuyNGetMFree(3,2,40))); |
| |
| assertEquals(new Money(0*40),apple.getPrice(0)); |
| assertEquals(new Money(1*40),apple.getPrice(1)); |
| assertEquals(new Money(2*40),apple.getPrice(2)); |
| assertEquals(new Money(3*40),apple.getPrice(3)); |
| assertEquals(new Money(3*40),apple.getPrice(4)); |
| assertEquals(new Money(3*40),apple.getPrice(5)); |
| assertEquals(new Money(4*40),apple.getPrice(6)); |
| assertEquals(new Money(5*40),apple.getPrice(7)); |
| assertEquals(new Money(6*40),apple.getPrice(8)); |
| assertEquals(new Money(6*40),apple.getPrice(9)); |
| assertEquals(new Money(6*40),apple.getPrice(10)); |
| assertEquals(new Money(7*40),apple.getPrice(11)); |
| } |
| |
| @Test |
| public void testBuyTwoGetFiveFree() throws Exception { |
| Product apple = new Product( |
| "Apple", |
| "SKU-0003", |
| ToMoney.from(new BuyNGetMFree(2,5,40))); |
| |
| assertEquals(new Money(0*40),apple.getPrice(0)); |
| assertEquals(new Money(1*40),apple.getPrice(1)); |
| assertEquals(new Money(2*40),apple.getPrice(2)); |
| assertEquals(new Money(2*40),apple.getPrice(3)); |
| assertEquals(new Money(2*40),apple.getPrice(4)); |
| assertEquals(new Money(2*40),apple.getPrice(5)); |
| assertEquals(new Money(2*40),apple.getPrice(6)); |
| assertEquals(new Money(2*40),apple.getPrice(7)); |
| assertEquals(new Money(3*40),apple.getPrice(8)); |
| assertEquals(new Money(4*40),apple.getPrice(9)); |
| assertEquals(new Money(4*40),apple.getPrice(10)); |
| assertEquals(new Money(4*40),apple.getPrice(11)); |
| assertEquals(new Money(4*40),apple.getPrice(12)); |
| assertEquals(new Money(4*40),apple.getPrice(13)); |
| assertEquals(new Money(4*40),apple.getPrice(14)); |
| assertEquals(new Money(5*40),apple.getPrice(15)); |
| } |
| } |