Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I defined the class Rectangle:

class Rectangle
  attr_reader :b, :h
  def initialize(b, h)
    @b = b
    @h = h
  end
  def area
    @b*@h
  end
  def to_s
    "Rectangle #{@b}x{@h}"
  end
end

and its subclass Square:

class Square < Rectangle

  attr_reader :s
  def initialize(s)
    @s = s
    super(@s, @s)
  end
  def to_s
    "Square of side #{@s}"
  end
end

Now, let's say I defined a Rectangle object r = Rectangle.new(5, 5), but actually it should be a Square because I'd like r.to_s to return "Square of side 5".

I can define a to_square method in the Rectangle class that returns a Square object equivalent to r, but, is it possible to write a to_square! method that would actually change the r class to Square without returning another object, so that r.class would now return Square instead of Rectangle?

And, what if I'd like:

Square(r)

to return the Square equivalent object of r, just like:

Integer("3")

which returns the Integer 3?

Is that possible? And, if so, how?

share|improve this question

closed as off topic by svick, palacsint, Jeff Vanzella, mseancole, Glenn Rogers Oct 23 '12 at 20:55

Questions on Code Review Stack Exchange are expected to relate to code review request within the scope defined in the FAQ. Consider editing the question or leaving comments for improvement if you believe the question can be reworded to fit within the scope. Read more about closed questions here.

2 Answers

up vote 1 down vote accepted

First off, why bother with a separate Square class?

Personally, I'd make a Rectangle class' to_square! method simply set both width and height to Math.sqrt(area). The result is still a Rectangle, just a square one.

The Rectangle's initializer method could also be written with an optional second argument; If there's only one argument, use that for both width and height, and you have a square.

Furthermore, I'd add a square? method. Checking obj.square? is, in my view, preferable to checking obj.class == Square. You could also use square? in to_s to return one string or the other.

To answer your question, though, I don't know of any way to just change an instance's class. You could however experiment with including different modules depending on whether the rectangle in question is a square or not. But that all seems pretty complex.

Finally, if you want a Square method, simply define one:

def Square(side)
  Rectangle.new(side, side)
end

Even if you have a Square class already, you can still have a method of the same name:

class Square
  # ...
end

def Square(side) # no naming conflict
  Square.new(side)
end
share|improve this answer
Because I'm studying inheritance and subclassing :-) – Duccio Armenise Oct 7 '12 at 13:07
1  
@DuccioArmenise Ah, well that explains it :) In that case, I'd definitely go with tokland's suggestion of a factory method on the class, or a Square() factory outside the class (like you see above), and/or have Rectangle#to_square (no bang) return a new Square instance. Don't start changing the class of an instantiated object, even if you can (and again, I doubt you can - it'd get confusing in a hurry). – Flambino Oct 7 '12 at 13:27
Ok, but what about if r, instance of Rectangle, can't be converted in a Square because it isn't a square. Conventionally speaking, it would be right to assume that r.to_square should return false while r.to_square! should raise an exception? – Duccio Armenise Oct 7 '12 at 13:40
@DuccioArmenise Depends on what you want to enforce. I imagine a to_square that returns a square with the same area as r, regardless of whether r is already a square. Same as calling to_i on a float returns an int even if the float can't be perfectly expressed as an int; to_i doesn't raise. Alternatively, you could have a to_square that raises an exception for a non-square r, while a to_square! forcibly returns a "squared square". However, in this context, I'd say that's a bad use of the bang-suffix. – Flambino Oct 7 '12 at 14:14
Just found this statement of Matz's about it: "The bang (!) does not mean "destructive" nor lack of it mean non destructive either. The bang sign means "the bang version is more dangerous than its non bang counterpart; handle with care" (wobblini.net/bang.txt) And, from the Pickaxe: "Methods that are dangerous, or that modify their receiver, may be named with a trailing exclamation mark" (ruby-doc.org/docs/ProgrammingRuby/html/tut_methods.html). :-) – Duccio Armenise Oct 7 '12 at 14:31
show 1 more comment

If you make the expression ClassName.new return something different than an instance of ClassName you will be breaking the programmer's expectations. The most idiomatic, and simple, way is writing a factory method:

class Rectangle
  # same code you have

  def self.with_sides(b, h)
    b == h ? Square.new(b) : Rectangle.new(b, h)
  end
end

Rectangle.with_sides(1, 2).to_s # Rectangle 1x2
Rectangle.with_sides(2, 2).to_s # Square of side 2

Of course, you can always do black magic with Ruby's new, but I'd advise against it.

share|improve this answer
Really interesting answer and article, thank you. But I didn't ask for ClassName.new to return something different from an instance of ClassName. Instead I asked if it's possible to change the class of an existing instance in Ruby (I guess it's not possible, as in Java, and for good reasons, but I don't know for sure yet). And I agree with @Flambino when he sais that even if it would be possible it would be a bad idea. – Duccio Armenise Oct 7 '12 at 13:35

Not the answer you're looking for? Browse other questions tagged or ask your own question.