I have a generic Ruby OOP question. Which is the more "correct" way
to share methods between two similar classes that do not necessarily
need to inherit from anything else: inheritance or include (as a mix-
in)? I realize that this question may be subject to opinion; however,
I'm getting ready to formally release a library and I'd like to do the
"right" thing. I like the inheritance route but I'm not sure that
creating a "Base" class that will never be instantiated directly by
anything externally is "proper".
Here are examples of both approaches:
### EXAMPLE 1 (inheritance):
# Here Base.new will (should) never be called directly; only through
inheriting classes
class Base
def initialize() @records = {}
end
def get_record(key) ; @records[key] ; end
def set_record(key, value) ; @records[key] = value ; end
end
class RecType1 < Base ; end
class RecType2 < Base ; end
### EXAMPLE 2 (mix-in):
module Base
def init_record() @records = {}
end
def get_record(key) ; @records[key] ; end
def set_record(key, value) ; @records[key] = value ; end
end
class RecType1
include Base
def initialize()
init_record
end
end
class RecType2
include Base
def initialize()
init_record
end
end
When it comes to Ruby there is a huge consideration here: Do you or
will you ever wish to pass on class level methods? Or more technically
speaking, do you want class singleton methods to be in the inheritance
chain? If so then you have to use a class, since modules don't allow
it (to my eternal dismay). For example:
class X; def self.x; "x"; end; end
class Y < X; end
Y.x #=> x
module X; def self.x; "x"; end; end
class Y; include X; end
Y.x #=> error
IMHO It's unfortunate that this is the case --or at least that there's
not a method other than #include that can do it. I guess it's because
I wrote annotations, which turned out to be very tricky b/c of this.
T.
···
On Mar 15, 1:35 am, james.d.mast...@gmail.com wrote:
I have a generic Ruby OOP question. Which is the more "correct" way
to share methods between two similar classes that do not necessarily
need to inherit from anything else: inheritance or include (as a mix-
in)? I realize that this question may be subject to opinion; however,
I'm getting ready to formally release a library and I'd like to do the
"right" thing. I like the inheritance route but I'm not sure that
creating a "Base" class that will never be instantiated directly by
anything externally is "proper".
Here are examples of both approaches:
### EXAMPLE 1 (inheritance):
# Here Base.new will (should) never be called directly; only through
inheriting classes
class Base
def initialize() @records = {}
end
def get_record(key) ; @records[key] ; end
def set_record(key, value) ; @records[key] = value ; end
end
class RecType1 < Base ; end
class RecType2 < Base ; end
### EXAMPLE 2 (mix-in):
module Base
def init_record() @records = {}
end
def get_record(key) ; @records[key] ; end
def set_record(key, value) ; @records[key] = value ; end
end
class RecType1
include Base
def initialize()
init_record
end
end
class RecType2
include Base
def initialize()
init_record
end
end
Modules may not allow it directly, but it seems that you can work
around it easily enough:
module A
def self.included( other )
def other.foo
puts 'bar'
end
end
end
class B
include A
end
class C < B
end
#> B.foo
#=> bar
···
On Mar 15, 12:11 am, "Trans" <transf...@gmail.com> wrote:
On Mar 15, 1:35 am, james.d.mast...@gmail.com wrote:
> I have a generic Ruby OOP question. Which is the more "correct" way
> to share methods between two similar classes that do not necessarily
> need to inherit from anything else: inheritance or include (as a mix-
> in)?
When it comes to Ruby there is a huge consideration here: Do you or
will you ever wish to pass on class level methods? Or more technically
speaking, do you want class singleton methods to be in the inheritance
chain? If so then you have to use a class, since modules don't allow
it (to my eternal dismay). For example:
class X; def self.x; "x"; end; end
class Y < X; end
Y.x #=> x
module X; def self.x; "x"; end; end
class Y; include X; end
Y.x #=> error
#
#> C.foo
#=> bar
Or am I missing something important here?
Really, the choice of whether to use inheritance or module inclusion
comes down to the question of whether you are sharing identity or just
behavior. For instance, do you want:
class WalkingThing
def walk
puts 'walking'
end
end
class Human < WalkingThing
end
class Robot < WalkingThing
end
or:
module WalkingThing
def walk
puts 'walking'
end
end
class Human
include WalkingThing
end
class Robot
include WalkingThing
end
A bit contrived, sure -- but hopefully makes the point.
In the case of pulling in module methods as class (singleton) methods,
you can use extend instead of include. Your second example rewritten
with "extend" would work:
module X; def x; "x"; end; end
class Y; extend X; end
Y.x #=> "x"
Unfortunately, I'm fairly sure that would require separate modules for
class methods (through extend) and instance methods (through include).
···
On Mar 15, 12:11 am, "Trans" <transf...@gmail.com> wrote:
module X; def self.x; "x"; end; end
class Y; include X; end
Y.x #=> error
IMHO It's unfortunate that this is the case --or at least that there's
not a method other than #include that can do it. I guess it's because
I wrote annotations, which turned out to be very tricky b/c of this.
module A
def self.included(other)
super
other.extend(ClassMethods)
end
module ClassMethods
def foo
puts "bar"
end
end
end
Is a little cleaner I think. And you can do all kinds of other weird stuff in the self.included method.
Another consideration is that in a single inheritance language like Ruby, inheritance is valuable. I tend to use mixins until I'm forced to use inheritance. In a language like Ruby the 'type hierarchy' isn't all that important (duck typing makes it mostly irrelevant).
Cheers,
Bob
···
On 15-Mar-07, at 4:25 AM, John Wilger wrote:
On Mar 15, 12:11 am, "Trans" <transf...@gmail.com> wrote:
On Mar 15, 1:35 am, james.d.mast...@gmail.com wrote:
I have a generic Ruby OOP question. Which is the more "correct" way
to share methods between two similar classes that do not necessarily
need to inherit from anything else: inheritance or include (as a mix-
in)?
When it comes to Ruby there is a huge consideration here: Do you or
will you ever wish to pass on class level methods? Or more technically
speaking, do you want class singleton methods to be in the inheritance
chain? If so then you have to use a class, since modules don't allow
it (to my eternal dismay). For example:
class X; def self.x; "x"; end; end
class Y < X; end
Y.x #=> x
module X; def self.x; "x"; end; end
class Y; include X; end
Y.x #=> error
Modules may not allow it directly, but it seems that you can work
around it easily enough:
module A
def self.included( other )
def other.foo
puts 'bar'
end
end
end
> > I have a generic Ruby OOP question. Which is the more "correct" way
> > to share methods between two similar classes that do not necessarily
> > need to inherit from anything else: inheritance or include (as a mix-
> > in)?
> When it comes to Ruby there is a huge consideration here: Do you or
> will you ever wish to pass on class level methods? Or more technically
> speaking, do you want class singleton methods to be in the inheritance
> chain? If so then you have to use a class, since modules don't allow
> it (to my eternal dismay). For example:
> class X; def self.x; "x"; end; end
> class Y < X; end
> Y.x #=> x
> module X; def self.x; "x"; end; end
> class Y; include X; end
> Y.x #=> error
Modules may not allow it directly, but it seems that you can work
around it easily enough:
module A
def self.included( other )
def other.foo
puts 'bar'
end
end
end
class B
include A
end
class C < B
end
#> B.foo
#=> bar
#
#> C.foo
#=> bar
Or am I missing something important here?
Sure. You can do all sorts of tricks to get the desired behavior. The
point is simply "it ain't natural".
I don't really hold a theoretical view of classes and modules. I work
with this on strictly practical level. A simple example is
ActiveSupport's mattr methods. I haven't looked at them in a while so
maybe they've _hacked_ a solution since then, but if you use mattr in
a module it'll break if included in a class or other module.
Really, the choice of whether to use inheritance or module inclusion
comes down to the question of whether you are sharing identity or just
behavior. For instance, do you want:
There's a duck around here that doesn't know the difference
T.
···
On Mar 15, 4:25 am, "John Wilger" <johnwil...@gmail.com> wrote:
On Mar 15, 12:11 am, "Trans" <transf...@gmail.com> wrote:
> On Mar 15, 1:35 am, james.d.mast...@gmail.com wrote:
module X; def x; "x"; end; end
class Y; extend X; end
Y.x #=> "x"
Unfortunately, I'm fairly sure that would require separate modules for
class methods (through extend) and instance methods (through include).
Although the approach above is the one that I use, for the sake of
completenes there is also this...
irb(main):001:0> module X; def x; "x"; end; end
=> nil
irb(main):002:0> class Y
irb(main):003:1> class << self
irb(main):004:2> include X
irb(main):005:2> end
irb(main):006:1> end
=> #<Class:Y>
irb(main):007:0> Y.x
=> "x"
Have a look at module/class_extension in Facets. This sophisticated
code was developed by Daniel Schierbeck with the help of a number of
people including myself, Nobu Nakada, Ulysses and Matz.
On Mar 15, 11:25 am, james.d.mast...@gmail.com wrote:
On Mar 15, 12:11 am, "Trans" <transf...@gmail.com> wrote:
> module X; def self.x; "x"; end; end
> class Y; include X; end
> Y.x #=> error
> IMHO It's unfortunate that this is the case --or at least that there's
> not a method other than #include that can do it. I guess it's because
> I wrote annotations, which turned out to be very tricky b/c of this.
In the case of pulling in module methods as class (singleton) methods,
you can use extend instead of include. Your second example rewritten
with "extend" would work:
module X; def x; "x"; end; end
class Y; extend X; end
Y.x #=> "x"
Unfortunately, I'm fairly sure that would require separate modules for
class methods (through extend) and instance methods (through include).
#
# Normally when including modules, class/module methods are not
# extended. To achieve this behavior requires some clever
# Ruby Karate. Instead class_extension provides an easy to use
# and clean solution. Simply place the extending class methods
# in a block of the special module method #class_extension.
#
# module Mix
# def inst_meth
# puts 'inst_meth'
# end
#
# class_extension do
# def class_meth
# "Class Method!"
# end
# end
# end
#
# class X
# include Mix
# end
#
# X.class_meth #=> "Class Method!"
#
def class_extension(&block) @class_extension ||= Module.new do
def self.append_features(mod)
append_features_without_class_extension(mod)
end
end @class_extension.module_eval(&block) if block_given? @class_extension
end
private :class_extension
def append_features(mod)
append_features_without_class_extension(mod)
mod.extend(class_extension)
if mod.instance_of? Module
mod.__send__(:class_extension).__send__(:include,
class_extension)
end
end
Indeed. Both the code and the indentation (I swear I don't know where
all those spaces came from!) I suppose that's what I get for trying to
program in an email at the end of a very long day.
···
On Mar 15, 5:34 am, Bob Hutchison <h...@recursive.ca> wrote:
module A
def self.included(other)
super
other.extend(ClassMethods)
end
module ClassMethods
def foo
puts "bar"
end
end
end
Sure. You can do all sorts of tricks to get the desired behavior. The
point is simply "it ain't natural".
I guess that's subjective. I find it completely "natural".
> Really, the choice of whether to use inheritance or module inclusion
> comes down to the question of whether you are sharing identity or just
> behavior. For instance, do you want:
There's a duck around here that doesn't know the difference
The duck might not, but the programmer does. The difference between
identity and behavior can make a complex domain easier to grok if used
appropriately.
Note that I am /not/ a proponent of static typing, and /am/ a
proponent of duck typing. The issue of inheritance vs. inclusion that
I mentioned regards human understanding of the code rather than the
compiler's.
···
On Mar 15, 7:21 am, "Trans" <transf...@gmail.com> wrote: