Skip to content

Instantly share code, notes, and snippets.

@ivaravko
Created February 25, 2012 20:03
Show Gist options
  • Save ivaravko/1910358 to your computer and use it in GitHub Desktop.
Save ivaravko/1910358 to your computer and use it in GitHub Desktop.

Problem 1: Stacks and Bags

Our goal is to make it so that we have a stack equality operator that is both a stand-in replacement for bag's equality operator and does the right thing when dealing with a stack operation. Liskov suggests that you could define something like Bag#bag_equal?, which Stack must expose and implement to match Bag's behavior, and that you could add a second stack_equal? method for doing ordered comparisons. However, this feels like a kludge to me, and so I tried to take another angle, following a pattern found in Ruby's Numeric class (i.e. its integer? and float? methods):

require "set"

class Bag  
  def ==(other)
    [Set.new(data), limit] == [Set.new(other.send(:data)), other.send(:limit)]
  end

  # returns true if the collection is an ordered collection,
  # false otherwise. This defaults to returning false, but 
  # may be overridden by subtypes to return either true or false.
  def ordered?
    false
  end

  private
  
  attr_accessor :data, :limit
end

class Stack
  # other code similar to before

  def ordered?
    true
  end

  # use of send() is ugly here but I don't like making data public
  def ==(other)
    if other.ordered?
      [other.send(:data), other.send(:limit)] == [data, limit]
    else
      [Set[*other.send(:data)], other.send(:limit)] == [Set[*data], limit]
    end
  end
 
  private

  attr_accessor :data, :limit
end

The end result is actually just as ugly (implementation-wise) as what Liskov recommended, but leads to slightly nicer behavior for the end user. Comparing two bags, or a bag and a stack uses bag equality behavior, comparing two stacks use stack equality behavior. But the fundamental difference between these two operations makes me wonder if it'd be better to avoid attempting to make these two objects have a subtype relationship at all, and have a simple Stack#to_bag method that would make it easy to convert a stack into a Bag. Since the whole exercise is a bit contrived, I won't take that line of reasoning further, but I'd be curious to hear the solutions that our readers came up with, so please do share if you have ideas!

Problem 2: Squares and Rectangles

The problems with the LSP violations between squares and rectangles can be trivially solved by making the structures immutable rather than mutable, which is probably a better design anyway. The way I might implement this is shown below, but it's easy to imagine how you can actually use composition rather than mixins for introducing the "Rectangular" concept.

module Rectangular
  def area
    width * height
  end
end

class Rectangle
  include Rectangular

  def initialize(width, height)
    self.width  = width
    self.height = height
  end

  attr_reader :width, :height

  private

  attr_writer :width, :height
end

class Square 
  include Rectangular

  def initialize(size)
    self.size = size
  end

  attr_reader  :size

  alias_method :width,  :size
  alias_method :height, :size

  private

  attr_writer :size
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment