How to avoid a hierarchy in ruby classes?

Viewed 119

I am processing 2-dimensional shapes in ruby. I have a Point class with x and y attribute, and a Line class which has two end points. Later in the process chain, I often want to identify and process vertical lines slightly differently from horizontal lines, even though both can occur in the same set of Lines. I have mix-ins to do the processing, but often I find the method has something like

if l.vertical?
  l.do_something_with_y_coordinate
elsif l.horizontal?
  l.do_something_with_x_coordinate
end

I see this pattern quite frequently and I do not like the repetition. I could try to avoid it by identifying instances of Line that are vertical and creating a VerticalLine class, and a HorizontalLine class, but that feels more like .Net than ruby and does not reduce the duplication, just encapsulates it.

Another way might be to have a direction method that returns :x or :y and then a method to "do_something_with direction".

Does anybody else have any suggestions about how to refactor?

2 Answers

It's not "more like .Net", it's more like proper object-oriented encapsulation. If you learned that via .Net, that's fine, but it's a very general approach that dates back decades before .Net or Ruby ever existed.

There's three ways to tackle this problem:

  1. Use an if to split behaviour.
  2. Use subclasses to implement the specific behaviour.
  3. Generalize your code so it doesn't matter if it's vertical or horizontal, the same math is applied in both cases.

In the third case imagine having vector(s) or a matrix which describes the geometry you're manipulating. For example, [0, 1] could represent a vertical line going up, or [-1, 0] a line going to the left depending on your coordinate system. Each iteration you just add that to your base point. The very idea of horizontal or vertical melts away.

In the concrete case I see three suggestions:

1.) Define accessors which map to x and y depending on the two cases:

def s=(value)
  if horizontal
    x = value
  else 
    y = value
  end
end 

def t=(value)
  if horizontal
    y = value
  else 
    x = value
  end
end 

With this you can delegate to the "right" field dynamically.

2.) Convert your horizontal cases into vertical ones by rotating, then performing the operation and then rotating it back.

3.) Using another representation that doesn't use x and y such as polar coordinates where your differentiates vanishes.

Related