Discovering self-evaluating objects. (pt. 1 of 2)
Notice: This is part one of a two-part post. Read the second post when you’re done with this one.
Have you ever written a program that sells products? Did you have to write the pricing rules for those products? Was it difficult? Time-consuming? Constantly changing? Screwing up your code and frustrating you to no end? Did people dip their grimy hands into your beautiful algorithm by asking you to make (gasp!) exceptions for this or that product? And don’t forget that special affiliate that everyone loves! He gets an extra percentage on top of everything else.
Stop. Breathe. Calm down. Everything will be okay. Object thinking to the rescue. Introducing the self-evaluating rule.
David West, author of Object Thinking, describes a self-evaluating rule thusly:
…think of a self-evaluating rule as an expression in the form of an ordered sequence of operators, variables, and constants. When the rule is asked to evaluate itself, each of its variables is asked to instantiate itself (become a known value), after which the rule resolves itself (applies the operators in the expression) to a final value — a Boolean true or false, for example.
Let’s take a stab at that, shall we? (You can paste the whole thing into IRB if you want to follow along.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class SelfEvaluatingRule attr_accessor :price, :tax def evaluate (@price * @tax) + @price end end rule = SelfEvaluatingRule.new rule.price = 100 rule.tax = 0.05 rule.evaluate # 105.0 |
Interesting. Okay, not really. It might resemble something you’ve written before, but instead of calling the evaluate method, you called price (or something similar).
Let’s think for a moment… What we have is a collection of variables, and a hard-coded formula that puts those variables to use. Right now it’s pretty crappy code. How can we make it better?
- Enter variable names and values dynamically
- Enter arbitrary rules
- Evaluate changes to our variables and rules without rewriting code
Those sound like solid goals. We’re going to take another stab at it, but before we do, I’d like to show you something that might be new to you…
First, fire up IRB.
Now, type the following, then hit enter:
1 | eval "1 + 1" |
What did you get? 2? Pretty cool, eh? This simple trick forms the basis of everything else we’re going to do.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class SelfEvaluatingRule attr_accessor :price, :tax def evaluate(rule) eval(rule.gsub('price', @price.to_s).gsub('tax', @tax.to_s)) end end rule = SelfEvaluatingRule.new rule.price = 100 rule.tax = 0.05 rule.evaluate( |