Written August 18, 2013. Tagged Ruby.
Inheriting from String
as a shortcut for classes that initialize with strings isn't necessarily a great idea.
I've seen it used most recently to define a DNA
class on exercism.io, e.g.
class DNA < String
def to_rna
gsub("T", "U")
end
end
dna = DNA.new("GATTACA")
dna.to_rna # => "GAUUACA"
This is often misguided for much the same reasons that inheriting from Struct
is.
Your subclass will happily do this:
dna = DNA.new
Unlike with Struct
inheritance, a missing argument won't cause a method like #to_rna
to explode with nil
errors, as self
will still be an empty string. Instead of exceptions you risk unexpected behavior, like empty strands of DNA kicking around your application. Exploding would be preferable.
By subclassing String
you suggest that DNA
is a specialized string.
It will get a ton of methods in its API, such as #upcase!
, #match
, #valid_encoding?
and #each_line
. Do those make sense for your class?
With the DNA
class above, it's impossible to tell what subset of methods you intended to inherit and think make sense for this class. Only the initializer? Also #to_s
? You should only inherit a class when all its methods make sense for the subclass.
If it's the initializer you want, just write your own or use some library to reduce boilerplate. If it's some other method, delegate those to a string – composition instead of inheritance. It will make the API of your class clearer.
With DNA
, it's probably reasonable that DNA.new("GATTACA") == DNA.new("GATTACA")
. If that's not how you want identity to work, though, be aware that your class will inherit this behavior.
Steve Klabnik mentions some gotchas of inheriting a core class like String
.
#to_s
is not called implicitly with interpolation. Your initializer won't always be called.
You won't trigger these gotchas most of the time, but some time you might, and it's easily avoided by not inheriting from String
.