blob: b56fa1e186fbb94a48c74298c710057336b9ea96 [file] [log] [blame]
/*
* 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));
}
}