Saturday, December 26, 2009

Ruby and Simple Dynamic Programming II

In my last post, we went over using method_missing in Ruby to do some simple dynamic programming. In this post we'll take a look at using the Singleton Class (not the Singleton pattern) to do a bit more dynamic programming. Most of this is a rehash of a couple of great posts by Ola Bini here and Peter Jones here.

So, what is the Singleton Class? A Singleton Class is where methods for an individual object are stored. What's this mean? Let's just look at some code.

# Create a class, Foo, that has a single method bar. We'll also
# add a method_missing so that we can see what's called that
class Foo
def bar
puts "Called bar"
end

# Put this in so we can see what gets called in foo2 below that's not available.
def method_missing(name, *args, &block)
puts "You tried to call #{name} with #{args.inspect}. There is no method with that name."
end
end


# Create a new Foo
foo = Foo.new

# Call the method that does exist.
foo.bar

# Add a new method to foo. This method, baz, will be added to
# foo's singleton class.
def foo.baz
puts "Called baz"
end

# Call the method we just added.
foo.baz

# Another way to add a new method to foo. This method, baz, will be added to
# foo's singleton class.
class << foo
def qux
puts "Called qux"
end
end

# Call the method we just added.
foo.qux

# Show that we don't have either baz or qux for any other Foo objects.
foo2 = Foo.new
foo2.bar # Should be there
foo2.baz # Should not be there
foo2.qux # Should not be there

# Add two class methods to Foo.
class Foo
class << self
def quux
puts "Called quux"
end
end

def self.quuux
puts "Called quuux"
end
end

foo3 = Foo.new
foo3.quux
foo3.quuux

Foo.quux
Foo.quuux



First we define a class Foo (here we're going to use metasyntactic variables, a phrase I just learned here). Anyway, class Foo has two methods, bar and method_missing (see last post). bar just prints out the fact that it was called and method_missing shows any methods called that aren't available. Nothing too awfully interesting here, we've seen things like this a million times. Next, we'll just create a new Foo called foo and then call the bar method on it. After that, we're going to do something a bit new, we're going to add a new method, baz, to foo (note that we're adding it to the object foo and not the class Foo. We'll call foo.baz to show that it works. Following, we're going to add a method qux using a different syntax, but it will do exactly the same thing as the last addition. Then we're going to call qux on foo to show that it works. Where were these methods added? Well, you can see from the next few statements, that they weren't added to all Foos. The fact is they were added to foo's Singleton Class. The Singleton Class sits between the object foo and its class Foo (see Jones' post to see this represented graphically).

Since a class has a class Class (OK, that may have made very little sense), it makes sense that the class Foo itself might have a Singleton Class and if you guessed that, then you guessed correctly. In the next section, we add a couple of methods using two different techniques to Foo (notice that we just reopen it), as class methods. In the same way as happened above, these methods are added to the Foo Singleton Class. We create a new foo3 variable and show that we can't call these new methods using an instance of foo, then we call them using Foo.

OK, so all of this is very interesting and ... so what? What can we do with this. Well, one use for the Singleton Class is for mocking (once again shown in Jones' post).

# Create a class, User, that has a single method get_buy_power. In
# real life a user would have many, many more methods than this.
class User
def get_buy_power
# Normally, this would be a complex call to
# the database, but here we'll just return
# a random number * 10000.0. This should give
# us a number between 0 and 10000.
rand * 10000.0
end
end

# A "normal" user.
user_normal = User.new
puts "user_normal buy power = #{user_normal.get_buy_power}"


# Create a new User
user_small_bp = User.new

# Change the user_small_bp's get_buy_power method to return a small value. This will be
# added to the user_small_bp's singleton class.
class << user_small_bp
def get_buy_power
20.0
end
end

# Print out the buy power for the user_small_bp
puts "user_small_bp buy power = #{user_small_bp.get_buy_power}"


# Create a new User
user_large_bp = User.new

# Change the user's get_buy_power method to return a large value. This will be
# added to the user's singleton class.
class << user_large_bp
def get_buy_power
200000.0
end
end

# Print out the buy power for the user_large_bp
puts "user_large_bp buy power = #{user_large_bp.get_buy_power}"


# We can now use user_small_bp and user_large_bp to run tests on buying stocks
# without enough buy power or with quite a bit of buy power.


# Create a simple OrderManager that will place orders and send orders. The place_order
# method will check a user's buy power before sending an order through. If the buy power isn't
# large enough, it will simply print a message, otherwise it will send the order and it
# will also print a message.
class OrderManager
def place_order(user, shares, stock, price)
if user.get_buy_power > price*shares
send_order(stock, price, shares)
else
puts "Not engough buy power for #{shares} of #{stock} at #{price}"
end
end

private

def send_order(stock, price, shares)
puts "Order sent for #{shares} of #{stock} at #{price}"
end
end

# Create a simple order manager.
order_manager = OrderManager.new

# Place an order for 200 shares of apple at $210.0 with user_small_bp. This should fail.
order_manager.place_order(user_small_bp, 200, "AAPL", 210.0)

# Place an order for 200 shares of apple at $210.0 with user_large_bp. This should succeed.
order_manager.place_order(user_large_bp, 200, "AAPL", 210.0)



Take a look at the code above. In the world that I live in (stock trading software), users have a certain amount of buy power that they can use to purchase stocks. Normally, this value would come out of the database and would be incremented (selling a stock) and decremented I(buying a stock) with each trade (OK, this sentence is incredibly simplistic, but will do for now). We're going to use Singleton Class to change our get_buy_power method (rather than adding as we did earlier). In one case, we'll give the user very little buy power and in the other, quite a bit more. After that, we'll create simple order manager that will allow us to place an order for a user that will check their buy power before sending it.

So there you have it, the Singleton Class and its usage. As always, let me know if you have any questions or comments.

No comments:

Post a Comment