Showing posts with label oop. Show all posts
Showing posts with label oop. Show all posts

Monday, April 14, 2014

DRYer Ruby Class Definitions w/ Struct

Many web developers subscribe to a principle know as the DRY principle. It translates to Don't Repeat Yourself. I try my best to adhere to the DRY principle, but sometimes I repeat snippets of code here and there, especially if the footprint is small. Well, today, a coworker showed me a clever way to DRY up some of my "small footprint" repeated code. It's probably some age-old Ruby technique, but I just discovered it today, and I'm really excited about it! So, I'll share it.

A Myriad of Class Definitions

Let's say we need to implement a Pizza application for a friend. After much white-boarding, we finally decide on a code architecture and a series of fancy design patterns we're gonna use to make our Pizza site come to life! Inevitably, we are going to have several class definitions in our codebase:
class Pizza
  attr_accessor :cheese, :sauce, :toppings

  def initialize(cheese, sauce, toppings)
    @cheese = cheese
    @sauce = sauce
    @toppings = toppings
  end
  
  # ... more implementation
end
class Soda
  attr_accessor :type, :is_diet

  def initialize(type, is_diet)
    @type = type
    @is_diet = is_diet 
  end

  # ... yes, more implementation
end
class Topping
  attr_accessor :name, :cost

  def initialize(name, cost)
    @name = name
    @cost = cost
  end

  # ... you get it
end

The Boilerplate

It's pretty obvious there are repeated elements of code in each of our classes. It's small, but repetitive nonetheless! Any DRY refactors we can do to reduce the code-smell would be an improvement.

When I'm writing class definitions, I frequently find myself writing two elements:
  • getters/setters for public attributes
  • a constructor with parameterss for initial attribute values

The DRY way to get getters and setters is to use attr_accessor.
attr_accessor :cheese, :sauce, :toppings
To set initial attribute values upon construction we use an initialize method with parameters. This is basic Ruby function. Within the initialize method, we explicitly define which attribute gets which parameter.
def initialize(cheese, sauce, toppings)
  @cheese = cheese
  @sauce = sauce
  @toppings = toppings
end

Reduce the Repetition with Struct

We can get rid of BOTH of these snippets of code by inheriting from a Struct instance! Well, kind of. A Struct generates an instance of Class. The new Class instance will have predefined attributes, along with accessor methods for them. By inheriting from the generated class, we essentially get all of the goodies we want in a single line of code!

Our Pizza class from above now becomes:
class Pizza < Struct.new(:cheese, :sauce, :toppings)  
  # no more initialize, just our implementation!
end
Subsequently, our entire codebase (however tiny) becomes:
class Pizza < Struct.new(:cheese, :sauce, :toppings)  
end
class Soda < Struct.new(:type, :is_diet)
end
class Topping < Struct.new(:name, :cost)
end

Mind. Blown. Anyway, I hope this helps someone as much as it has helped me. Comments or questions? Let me know!

Saturday, December 22, 2012

Quick Review: Basic Observer Design Pattern in Ruby



Every so often, it's nice to give yourself a quick review of basic topics in software. Today, I wanted to review the observer design pattern and give a very simple implementation of it in Ruby. The observer pattern allows several observers to be notified when an observable object, or subject, changes its state. When the subject sends an update notification, the observers can act accordingly based on their type.

When to Observe

The observer pattern is useful for cases where an object is dependent on the state of another. The subject manages a list of zero or more observers made up of several types. Two common practical examples are:
  • graphical interface elements of an application will change based on the state of back-end data
  • a coupon subscription service will send notifications to several devices based on deals at different stores


Observable (Subject)

We'll start by implementing the Observable module for our subject. Remember, the subject is responsible for managing and notifying several observers when its state changes.
module Observable

  attr_accessor :state

  def attachObserver(o)
    @observers ||= []
    @observers << o if !@observers.include?(o)
  end

  def removeObserver(o)
    @observers.delete(o)
  end

  def state=(s)
    @state = s
    @observers.each do |o|
      o.update(self) # more on update() later!
    end
  end

end
Let's break down the interesting parts:
def attachObserver(o)
  @observers ||= []
  @observers << o if !@observers.include?(o)
end

def removeObserver(o)
  @observers.delete(o)
end
These methods are used to manage the observers for our subject. We don't want the same object in our list twice, so we only add an object if it isn't already observing our subject.
def state=(s)
  @state = s
  @observers.each do |o|
    o.update(self) # more on update() later!
  end
end
When the subject changes its state, it needs to notify any object that is observing it. We overwrite the default behavior for state=, which is a method created for us by attr_accessor. After reassigning the state value, we call update on all the managed observers.

Observer

Here is what our Observer module looks:
module Observer
  
  def update(o)
    raise 'Implement this!'
  end

end
We leave the functionality of update to the class including our module. This way, the Observer module behaves as an abstract class with an interface that needs to be implemented by the includer. This gives us the ability to include the Observer module into any class we want!

The Ant Colony

As a simple example, let's create an ant colony using our modules. First, we define our subject, the Queen Ant:
class QueenAnt
  include Observable
end
Next, let's define an observer:
class WorkerAnt
  include Observer

  def update(o)
    p "I am working hard. Queen has changed state to #{o.state}!"
  end
end
The WorkerAnt defines its own unique way of handling the state changes of the queen. Let's create some worker ant objects to see how this works:
queen = QueenAnt.new

worker1 = WorkerAnt.new
worker2 = WorkerAnt.new
worker3 = WorkerAnt.new

queen.attachObserver(worker1)
queen.attachObserver(worker2)

queen.state = 'sleeping'
# I am working hard. Queen has changed state to sleeping!" <-- worker1
# I am working hard. Queen has changed state to sleeping!" <-- worker2
When the queen changes state, the worker1 and worker2 instances get notified and react accordingly. Since worker3 was not attached, it does not get notified.

The queen can handle any type of observer, not just WorkerAnts. Let's create two additional observer classes:
class SoldierAnt
  include Observer

  def update(o)
    p "Reporting for duty! Queen has changed state to #{o.state}!"
  end
end

class BreederAnt
  include Observer

  def update(o)
    p "Need to look for a mate. Queen has changed state to #{o.state}!"
  end
end
Now, let's create a soldier and breeder instance to observe the queen:
queen = QueenAnt.new

soldier = SoldierAnt.new
breeder = BreederAnt.new

queen.attachObserver(soldier)
queen.attachObserver(breeder)

queen.state = 'sleeping'
# Reporting for duty! Queen has changed state to sleeping!
# Need to look for a mate. Queen has changed state to sleeping!
SoldierAnt and BreederAnt are different classes, but implement the same Observer interface. When the queen changes her state, the two instances (soldier and breeder) get notified and react accordingly.

There you have it! Please leave any questions, opinions, and/or corrections below in the comments.

Wednesday, December 19, 2012

Reopen and Modify Ruby Classes (Monkey Patching)

As programmers, we often reuse code other programmers have written. It makes no sense to duplicate code if there are already libraries and/or modules available to us. Most of the time, we don't care about the quality of the code as long as it behaves as we expect. After all, who wants to know what the sausage factory looks like?

What if we want to modify how the sausage is made? Well, because Ruby classes are open, we can add/modify methods in existing pieces of code. We simply reopen the class and define (or redefine) the methods. This is often known as monkey patching. I'm not going to preach about how great or evil this "technique" is, but if you're interested, check out this article by Jeff Atwood.

Classes We Cannot Touch

Let's say I am using a class written by a friend called FriendClass. We'll assume it has two methods I'm allowed to use:
  • my_greeting()
  • my_goodbye()

Here is the usage:
f = FriendClass.new

f.my_greeting("Fred")
# Hello, Fred.

f.my_goodbye
# Goodbye!

Reopen and Add a Method

Assume I don't have access to the implementation of the class. What if I want to add a new method to it? We just reopen the class:
class FriendClass

  def my_insult
    puts "You suck."
  end

end

f = FriendClass.new

f.my_insult
# You suck.
It might look like we've completely redefined the class, but Ruby knows that FriendClass has already been defined. It simply adds the new method to the existing definition. So, I can still use the methods originally defined by my friend as well:
f.my_insult
# You suck.

f.my_goodbye
# Goodbye!

Reopen and Modify a Method

Now, I want to change the boring greeting in FriendClass. Just like adding new methods, we can completely change the behavior of existing methods using the same technique:
f = FriendClass.new

f.my_greeting("Fred")
# Hello, Fred.

class FriendClass

  def my_greeting(name)
    puts "Hey there, #{name}! How's it going?"
  end

end

f.my_greeting("Fred")
# Hey there, Fred! How's it going?
Voila! The class now has the behavior I want without changing the actual implementation of FriendClass.

Reopen and Add to Any Class

This type of modification can be done to any class in Ruby. This includes Gems and core Ruby classes. Let's say I wanted to be able to (naively) sum elements in my Arrays:
[1,2,3,4].sum
# undefined method `sum' for [1, 2, 3, 4]:Array (NoMethodError)

class Array

  def sum
    sum = 0
    self.each do |e|
      sum += e
    end
    sum
  end

end

[1,2,3,4].sum
# 10

Monkey Patch a Ruby Class

If you want to modify the behavior of a method, but retain some of the existing functionality, there are several ways to do this. Each have pros and cons. The most popular way is to use alias:
f = FriendClass.new

f.my_greeting("Fred")
# Hello, Fred.

class FriendClass

  alias old_my_greeting my_greeting
  def my_greeting(name)
    puts "I used to say '#{old_my_greeting(name)}'. 
    puts "Now I say, what's up, #{name}?"
  end

end

f.my_greeting("Fred")
# I used to say 'Hello, Fred.'. 
# Now I say, what's up, Fred?
By using alias, we can safely redefine my_greeting and still have access to the original version through old_my_greeting.

Alternative Solutions for Gems

Occasionally, you will be tempted to monkey patch a buggy Gem. Before doing so, it is important to consider alternative solutions. My favorite alternative to monkey patching is forking the Gem on Github, making a change, and adding the url to the Gemfile.

Another solution is to directly modify the Gem code in your local environment. By using gemedit, you can quickly modify any Gem in your preferred editor. You can also use gem unpack to create a private copy of a Gem's contents in your current directory. Only do this if portability is not important for you project.

Please leave your corrections, suggestions, and opinions on monkey patching in the comments below!

Wednesday, August 8, 2012

Polymorphism in Ruby

In many object-oriented programming languages, there is support for polymorphism. This is when an object takes on multiple shapes or forms. You can read more about polymorphism here. When programmers talk about polymorphism, usually they are talking about inheritance polymorphism, which is the topic of this post.

Basic Polymorphism

In Ruby, inheritance polymorphism can be achieved doing something like the following:
class Bird
  def initialize(name)
    @name = name
  end

  def speak
    puts 'Tweet'
  end

  def fly
    puts 'Up up and away...'
  end
end
This is our base class, Bird. All other classes will inherit the methods in this class.
class Duck < Bird
  def speak
    puts "Quack I am #{@name}"
  end
end

class Penguin < Bird
  def speak
    puts "Squak I am #{@name}"
  end

  def fly
    puts 'Nope. I swim...'
  end
end
These are the classes that inherit from our base class. Ducks and Penguins are Birds. So, they will have basic methods speak() and fly(). The cool thing is we can override existing methods of the superclass from within the subclass to change the default behavior of a Bird. For example, Penguins don't tweet or fly. So, we override the base methods to better reflect the behavior of penguins.

So, now we can do something like this:
birds = [Bird.new('Tweetie'), Duck.new('Donald'), Penguin.new('Mumbo')]
birds.each do |b|
  b.fly
end

# Up up and away...
# Up up and away...
# Nope. I swim...

birds.each do |b|
  b.speak
end

# Tweet
# Quack I am Donald
# Squak I am Mumbo

The Ruby Way

In Ruby, traditional methods of polymorphism (seen above and in languages such as Java) are not widely accepted. A concept known as "duck typing" is preferred. You can read more about duck typing here.

Basically, we follow a simple rule: If it walks, swims, and quacks like a duck, I call that bird a duck

We can get the same type of "polymorphic" behavior by simply implementing each method individually in each class and including any common methods:
class Duck
  def speak
    puts "Quack I am #{@name}"
  end

  def fly
    puts 'Up up and away...'
  end
end

class Penguin
  def speak
    puts "Squak I am #{@name}"
  end

  def fly
    puts 'Nope. I swim...'
  end
end
Now, each type of Bird has its methods individually defined according to its own specific behavior. What about the shared initialize() constructor?
module Bird
  def initialize(name)
    @name = name
  end
end
We put any shared methods in a module. This way, we can include them in our classes:
class Duck
  include Bird
  ...
end

class Penguin
  include Bird
  ...
end

Do It Your Way

I have no strong opinion on whatever approach you decide to use. There are evangelists on both ends who live and die by either one. Solve your problem any way you please!

Resources:
Ruby polymorphism discussion: http://stackoverflow.com/questions/137661/how-do-you-do-polymorphism-in-ruby
The Ruby Way excerpt: http://books.google.com/books?id=ows9jTsyaaEC&pg=PA14
Ruby Learning: http://rubylearning.com/satishtalim/ruby_inheritance.html