Forum: Ruby General Ruby OOP Question - using inheritance or include for

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
unknown (Guest)
on 2007-03-15 07:36
(Received via mailing list)
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


Thanks in advance,
-James
Trans (Guest)
on 2007-03-15 09:12
(Received via mailing list)
On Mar 15, 1:35 am, removed_email_address@domain.invalid wrote:
>
>
>     @records = {}
>     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.
John W. (Guest)
on 2007-03-15 10:25
(Received via mailing list)
On Mar 15, 12:11 am, "Trans" <removed_email_address@domain.invalid> wrote:
> 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?

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.

--
Regards,

John W.
http://johnwilger.com
Bob H. (Guest)
on 2007-03-15 14:35
(Received via mailing list)
On 15-Mar-07, at 4:25 AM, John W. wrote:

>> technically
>>   class Y; include X; end
>     end
>   #=> bar
>   #
>   #> C.foo
>   #=> bar

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

----
Bob H.                  -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc.          -- <http://www.recursive.ca/>
xampl for Ruby                 -- <http://rubyforge.org/projects/xampl/>
Trans (Guest)
on 2007-03-15 16:22
(Received via mailing list)
On Mar 15, 4:25 am, "John W." <removed_email_address@domain.invalid> wrote:
> > When it comes to Ruby there is a huge consideration here: Do you or
> >   class Y; include X; end
>                 end
>         #=> 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.
unknown (Guest)
on 2007-03-15 17:25
(Received via mailing list)
On Mar 15, 12:11 am, "Trans" <removed_email_address@domain.invalid> 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).
Ilan B. (Guest)
on 2007-03-15 22:20
unknown 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"
Trans (Guest)
on 2007-03-16 00:20
(Received via mailing list)
On Mar 15, 11:25 am, removed_email_address@domain.invalid wrote:
> 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).

Have a look at module/class_extension in Facets. This sophisticated
code was developed by Daniel S. with the help of a number of
people including myself, Nobu Nakada, Ulysses and Matz.


class Module


alias_method :append_features_without_class_extension, :append_features

  # = class_extension
  #
  # 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

end

class Class
  undef_method :class_extension
end


T.
John W. (Guest)
on 2007-03-16 03:51
(Received via mailing list)
On Mar 15, 5:34 am, Bob H. <removed_email_address@domain.invalid> wrote:
>    end
> end
>
> Is a little cleaner I think.

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. :-(
John W. (Guest)
on 2007-03-16 04:01
(Received via mailing list)
On Mar 15, 7:21 am, "Trans" <removed_email_address@domain.invalid> wrote:
> 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.
This topic is locked and can not be replied to.