mlovic / code_exercise_1

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

I've implemented the rules basically as Procs that take items and their prices as arguments. The Proc is wrapped in a PricingRule class that exposes an apply_discount method which returns the amount to be discounted. Apart from that there isn't any real abstraction here. I considered a few things like separating the discount condition from the amount calculation, and initializing rules for a specific item, but I thought it was all too restrictive for the little information given in the exercise.

Right now the PriceRule abstraction is very flexible however (the price rules are stateless and free of dependencies), and can be easily extended and built upon. For example by subclassing PricingRule. No code changes required:

class BuyXGetOneFree < PricingRule
  def initialize(item, x)
    @discount_fn = lambda do |items, prices|
      (items[item] / (x + 1)) * prices[item]
    end
  end
end

VOUCHER_PROMO = BuyXGetOneFree.new "VOUCHER", 1
VOUCHER_PROMO.apply_discount items, prices

You just have to set the @discount_fn variable, which always holds a proc or a lambda.

Why inheritance over composition?

I could have, for example, made PricingRule a module and mixed it in, but I think that it's more semantic this way. Everything truly is a PricingRule (it satisfies the Liskov substitution principle), so there's no reason not to. It also allows for multiple layers of inheritance. You can subclass the subclass:

  class BuyXGetOneFreeWithMin < BuyXGetOneFree
    def initialize(item, x, min)
      bogof = super(item, x)
      @discount_fn = lambda do |items, prices|
        bogof.call if items.size > min # or something
      end
    end
  end

This looks like the beginning of an inheritance mess, but really it's just wrapping functions in functions.

Additionally, by making all rule objects instantiations of classes instead of classes that include modules, you can build them dynamically, from data pulled out of a database for example.

Why not just have a duck type?

I also could have just adopted the convention of giving price rules an apply_discount method and forgot about any code sharing at all. There isn't much code to share after all. I realize this is often the best, cleanest, most rubylike approach, but I think that in this case, inheritance makes the "type" more explicit, and also makes the public interface easier to change, as it's only in one place.

I made the table of products just be a global constant directly read by Checkout. This conforms to the example, although it would have been cleaner and easier to test to pass the products table to the Checkout initializer.

The rest of the code is pretty straight forward. There are specs too!

A few other considerations, which I didn't implement:

  • Not using floating point numbers for prices
  • Marking pricing rules as exclusive, so as to disallow coumpounding multiple discounts for the same item. This could be handled outside the Checkout class.

About


Languages

Language:Ruby 100.0%