A single class that supports multiple facets/interfaces

i want to have a class that can support multiple sets of methods, based
on what the client requests. so far i have written the following python
code. it’s ugly/not elegant and i haven’t managed to come up with a less
ugly ruby version :slight_smile:

<<EOF
class C:
def foo(self):
print "foo"
def bar1(self):
print "bar version 1"
def bar2(self, arg):
print “bar version 2, arg =”, arg
def facet(self, whichFacet=1):
if whichFacet == 1:
return C_facet1(self)
elif whichFacet == 2:
return C_facet2(self)
return

class C_facet1:
def init(self, c):
self.foo = c.foo
self.bar = c.bar1

class C_facet2:
def init(self, c):
self.foo = c.foo
self.bar = c.bar2

c1 = C().facet(1) # client1 picks interface1
c1.foo() # prints "foo"
c1.bar() # prints “bar version 1”

c2 = C().facet(2) # client2 picks interface2
c2.foo() # prints "foo"
c2.bar(123) # prints "bar version 2, arg = 123"
EOF

if a client wants interface 1, then she will get the methods foo and bar
(which is actually C#bar1). if he picks interface 2, then she will get
the methods foo and bar (which is actually C#bar2). for all she cares,
she just wants to know/deal with a single [versatile] class, C. and from
that single class, she can pick several sets of features/interfaces she
needs/wants.

i know that, in ruby, modules are usually used for things like this, but
in my case many of the method names are the same so i don’t think
modules are approriate for this. for example, interface1 consists of
foo, bar, baz, abc, and abd. interface2 consists of foo, bar, baz, bab,
bac. in most of the cases all of the interfaces provide the same set of
features, just in a different style or different revision.

any pointers of how i might do something like this in ruby?

···


dave

Elegant is often in the eye of the beholder. I have this …

class C
class << self
def facet(n)
facets = [C1, C2]
facets[n-1].new
end
end
def foo
“foo”
end
end

class C1 < C
def bar
“bar version 1”
end
end

class C2 < C
def bar(n)
“bar version 2, arg = #{n}”
end
end

class TestFacet < Test::Unit::TestCase
def test_foo
c = C.facet(1)
assert_equal “foo”, c.foo
assert_equal “bar version 1”, c.bar
end

def test_facet2_foo
c = C.facet(2)
assert_equal “foo”, c.foo
assert_equal “bar version 2, arg = 123”, c.bar(123)
end
end

But if the client knows enough to say “c = C.facet(2)”, it seems to me
that it wouldn’t be any harder to have the client say “c = C2.new”.
Then you can drop the the whole facet method in the Class, and the base
class no longer needs knowledge of its child classes (which is a good
thing).

···

On Mon, 2003-01-20 at 08:00, David Garamond wrote:

i want to have a class that can support multiple sets of methods, based
on what the client requests. so far i have written the following python
code. it’s ugly/not elegant and i haven’t managed to come up with a less
ugly ruby version :slight_smile:


– Jim Weirich jweirich@one.net http://w3.one.net/~jweirich

“Beware of bugs in the above code; I have only proved it correct,
not tried it.” – Donald Knuth (in a memo to Peter van Emde Boas)

David Garamond davegaramond@icqmail.com writes:

if a client wants interface 1, then she will get the methods foo and bar
(which is actually C#bar1). if he picks interface 2, then she will get
the methods foo and bar (which is actually C#bar2). for all she cares,
she just wants to know/deal with a single [versatile] class, C. and from
that single class, she can pick several sets of features/interfaces she
needs/wants.

i know that, in ruby, modules are usually used for things like this, but
in my case many of the method names are the same so i don’t think
modules are approriate for this. for example, interface1 consists of
foo, bar, baz, abc, and abd. interface2 consists of foo, bar, baz, bab,
bac. in most of the cases all of the interfaces provide the same set of
features, just in a different style or different revision.

any pointers of how i might do something like this in ruby?

I know that you’re avoiding inheritance on purpose, but I’d do it like
this:

class C
def self.new(facet = 1, *args)
if self == C
case facet
when 1 then C1.new(*args)
when 2 then C2.new(*args)
end
else
super(*args)
end
end

def foo
puts “foo”
end

def bar1
puts “bar version 1”
end

def bar2
puts “bar version 2”
end
end

class C1 < C
alias_method :bar, :bar1
end

class C2 < C
alias_method :bar, :bar2
end

c1 = C.new(1) # cilent1 picks interface as defined in C1
c1.foo # prints “foo”
c1.bar # prints “bar version 1”

c2 = C.new(2) # client2 picks interface as defined in C2
c2.foo # prints “foo”
c2.bar # prints “bar version 2”

You can define a class method in C named .facet if you don’t want to
touch .new for this purpose, just rename .new.

def self.facet(facet = 1, *args)
if self == C
case facet
when 1 then C1.new(*args)
when 2 then C2.new(*args)
end
else
super(*args)
end
end

And can easily take away the ugly case statement, too:

def self.facet(facet = 1, *args)
if self == C
Object.const_get(“#{self}#{facet}”).new(*args)
else
super(*args)
end
end

Finally, you might want to make it generally useful by stuffing it
into a mixin:

module Facetable
def facet(facet = 1, *args)
if self == C
Object.const_get(“#{self}#{facet}”).new(*args)
else
super(*args)
end
end
end

class C
extend Facetable


end

class C1 < C

end

class C_something < C

end

c1 = C.facet(1)
csth = C.facet(“_something”)

…and so on.

Massimiliano

Hi –

i want to have a class that can support multiple sets of methods, based
on what the client requests. so far i have written the following python
code. it’s ugly/not elegant and i haven’t managed to come up with a less
ugly ruby version :slight_smile:

[…]

i know that, in ruby, modules are usually used for things like this, but
in my case many of the method names are the same so i don’t think
modules are approriate for this. for example, interface1 consists of
foo, bar, baz, abc, and abd. interface2 consists of foo, bar, baz, bab,
bac. in most of the cases all of the interfaces provide the same set of
features, just in a different style or different revision.

any pointers of how i might do something like this in ruby?

I wouldn’t dismiss modules for this purpose. For example:

class C
module Facet1
def bar
puts “bar version 1”
end
end

module Facet2
  def bar(arg)
puts "bar version 2, arg=#{arg}"
  end
end

def foo
  print "foo"
end

def facet(f=1)
  extend self.class.const_get("Facet" + f.to_s)
end

end

C.new.facet(1).bar # bar version 1
C.new.facet(2).bar(“hi”) # bar version 1, arg=hi

Mind you, as Jim pointed out, once you have to know about facet(1) and
all that, you might as well bring it up front, which in my version
would mean doing:

c = C.new.extend(C::Facet1)
c.bar # bar version 1

or equivalent. (extend is very cool :slight_smile:

David

···

On Mon, 20 Jan 2003, David Garamond wrote:


David Alan Black
home: dblack@candle.superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

Quoteing davegaramond@icqmail.com, on Mon, Jan 20, 2003 at 10:00:51PM +0900:

i want to have a class that can support multiple sets of methods, based
on what the client requests. so far i have written the following python
code. it’s ugly/not elegant and i haven’t managed to come up with a less
ugly ruby version :slight_smile:

I’m just learning Ruby, but what about the really simple:

class C

class Ca
def foo

end
end

class Cb
def foo

end
end

def foo
@facet.foo
end

def initialize(type)
if(type)
@facet = Va.new
else
@facet = Vb.new
end

end

Completely untested, but that’s what I’d do in C++. Granted C::Ca#foo
isn’t really a method of C, so it can’t access instance variables of C,
but you could simulate this by having C#foo pass self as an arg to
@facet.foo.

Is this somehow fundamentally bad?

Sam

[x86.bison:695]gfb> ruby -v
ruby 1.6.7 (2002-03-01) [i386-solaris2.8]

I have a module that I will use as a mixin:

module Original
def foo
pust "Original#foo()"
end
end

include Original

foo # => Original#foo()

Everything is OK so far. Then I would like to mix another module into
Original that redefines method foo:

module Another
def foo
puts "Another#foo()"
end
end

module Original
include Another
end

foo # => Original#foo()

Here I have a problem – Original’s foo is still being called (and I expect
Another’s). The only way to achieve what I want is to redefine module
Another above to:

module Another
def self.append_features(mod)
super mod
mod.module_eval %Q{
def foo
puts "Another#foo()"
end
}
end
end

Am I missing something? Is there a way to go without eval?

Thanks in advance,
Gennady.

dblack@candle.superlink.net wrote:

c = C.new.extend(C::Facet1)
c.bar # bar version 1

or equivalent. (extend is very cool :slight_smile:

thanks. so as i see it, ‘extend’ is like ‘include’ but operates at the
object level (while ‘include’ usually works at the class level), right?
extend is cool :slight_smile:

btw, does ‘extend’ have a significant runtime cost?

···


dave

likewise untested:

class C

def C.factory(type)
	case type
	when :a
		return Ca
	else
		return Cb
	end
end

class Ca < self
	def foo
		puts 'override foo'
	end
end

class Cb < self
	def foo
		puts 'override foo'
	end
end

def foo
	puts 'default foo'
end

end

c = C.factory(:a).new

···

On Saturday 25 January 2003 01:56 pm, Sam Roberts wrote:

Quoteing davegaramond@icqmail.com, on Mon, Jan 20, 2003 at 10:00:51PM +0900:

i want to have a class that can support multiple sets of methods, based
on what the client requests. so far i have written the following python
code. it’s ugly/not elegant and i haven’t managed to come up with a less
ugly ruby version :slight_smile:


tom sawyer, aka transami
transami@transami.net

thanks. so as i see it, 'extend' is like 'include' but operates at the
object level (while 'include' usually works at the class level), right?

Well, it's perhaps best if you see that #extend work at singleton level

For example

   class A
      extend M
   end

is *like* if you write

   class A
      class << self
         include M
      end
   end

  a = A.new.extend(M)

  is *like*

  class << a
     include M
  end

btw, does 'extend' have a significant runtime cost?

no, why ?

Guy Decoux

Is

A.extend M

a shortcut for this? Practically it seems to be, and theoretically it
should be, since “class A” sets “self” to “A”.

So is there a shortcut for

class A
include M
end

?

The logic above appears to apply, but

A.include M

fails because “private method `include’ called for A:Class”.

Just wondering if I’ve got everything right, and if there’s anything
else to learn about it.

Gavin

···

On Wednesday, January 22, 2003, 9:30:52 PM, ts wrote:

thanks. so as i see it, ‘extend’ is like ‘include’ but operates at the
object level (while ‘include’ usually works at the class level), right?

Well, it’s perhaps best if you see that #extend work at singleton level

For example

class A
extend M
end

class A
extend M
end

Is

A.extend M

a shortcut for this? Practically it seems to be, and theoretically it
should be, since “class A” sets “self” to “A”.

So is there a shortcut for

class A
include M
end

?

The logic above appears to apply, but

A.include M

fails because “private method `include’ called for A:Class”.

You do probably already know it, but for the record, we have
Object#extend (public)
Module#include (private)

The question is why are Module#include and Module#append_features
private?

···

On Thu, Jan 23, 2003 at 01:47:26AM +0900, Gavin Sinclair wrote:

Just wondering if I’ve got everything right, and if there’s anything
else to learn about it.

Gavin


_ _

__ __ | | ___ _ __ ___ __ _ _ __
'_ \ / | __/ __| '_ _ \ / ` | ’ \
) | (| | |
__ \ | | | | | (| | | | |
.__/ _,
|_|/| || ||_,|| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

People disagree with me. I just ignore them.
– Linus Torvalds, regarding the use of C++ for the Linux kernel

The logic above appears to apply, but

A.include M

fails because “private method `include’ called for A:Class”.

You do probably already know it, but for the record, we have
Object#extend (public)
Module#include (private)

The question is why are Module#include and Module#append_features
private?

Well, here’s some cool code then:

module M; def hi; “hi”; end; end
=> nil

class A; end
=> nil

A.include M
NameError: private method `include’ called for A:Class
from (irb):3

class Module
def inc(*args)
include *args
end
end
=> nil

A.inc M
=> A

A.new.hi
=> “hi”

Gavin (who’s trying to get his Ruby-English ratio up…)

···

On Thursday, January 23, 2003, 3:57:28 AM, Mauricio wrote:

… and some more:

module M
def hi
“hi”
end
end

class B
end

B.include M # => fails: Class#include is private

class Class
def pub(*a) # Because public is private, too.
public *a
end
end

Class.pub :include

B.include M # => succeeds

B.new.hi # => “hi”

Gavin

PS. I think allowing “A.include M” would be nice. It jars that
“extend” is allowed but not “include”. I’m not going to make a big
deal of it though.

···

On Thursday, January 23, 2003, 4:07:14 AM, Gavin wrote:

On Thursday, January 23, 2003, 3:57:28 AM, Mauricio wrote:

The logic above appears to apply, but

A.include M

fails because “private method `include’ called for A:Class”.

You do probably already know it, but for the record, we have
Object#extend (public)
Module#include (private)

The question is why are Module#include and Module#append_features
private?

Well, here’s some cool code then:

Well, here’s some cool code then:

module M; def hi; “hi”; end; end
=> nil

class A; end
=> nil

A.include M
NameError: private method `include’ called for A:Class
from (irb):3

class Module
def inc(*args)
include *args
end
end
=> nil

A.inc M
=> A

A.new.hi
=> “hi”

Gavin (who’s trying to get his Ruby-English ratio up…)

Me too :slight_smile:

batsman@tux-chan:/tmp$ irb --prompt-mode simple

Module.instance_eval <<EOF
public :include # why not default?
EOF
=> Module
module M; def hi; “hi”; end; end
=> nil
class A; end
=> nil
A.new.hi
NameError: undefined method `hi’ for #<A:0x40272a44>
from (irb):6
A.include M
=> A
A.new.hi
=> “hi”

···

On Thu, Jan 23, 2003 at 02:07:14AM +0900, Gavin Sinclair wrote:


_ _

__ __ | | ___ _ __ ___ __ _ _ __
'_ \ / | __/ __| '_ _ \ / ` | ’ \
) | (| | |
__ \ | | | | | (| | | | |
.__/ _,
|_|/| || ||_,|| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Never make any mistaeks.
– Anonymous, in a mail discussion about to a kernel bug report

Well, here’s some cool code then:

… and some more:

… and finally (I just noticed this in fileutils.rb)

Ever find it annoying when you create a module which is basically
intended to be a namespace/container for a set of functions, and they
don’t work because you have to use “public_module_method” or whatever?

Here’s another way.

module M
def hi
“hi”
end
end

M.extend M # These

module M # three are
extend M
end

module M # equivalent
extend self
end

M.hi # => “hi”

The code I saw opted for “extend self” after all the methods had been
defined. This had me scratching my head wondering what it meant. A
comment up the top switched the light on. But it could have been
written “extend FileUtils”, which would have been a bit clearer, and
it could have been done up the top, with a comment saying all methods
are hearby public module methods.

That’s right, it could be up the top. Check this:

module M
def hi
“hi”
end
extend M
def lo
“lo”
end
end
=> nil

M.hi
=> “hi”

M.lo
=> “lo”

Apparently, using “extend” merely hooks something up behind the
scenes, rather than dumping a lot of information into the target
module. This is not particularly surprising, but it’s still nice to
have a language that lets you play around like this and experiment.

Gavin

···

On Thursday, January 23, 2003, 4:12:58 AM, Gavin wrote:

Apparently, using "extend" merely hooks something up behind the
scenes, rather than dumping a lot of information into the target
module.

Well, if you have

   module M end
   module N end
   class A
      include M
      extend N
   end

internally (if you don't modify what do ruby by default) you'll have

    A ===> [M] ===> Object
    > >
    > >
    v v
   (A) ==> [N] ===> (Object)

Where (A), (Object) are the singleton classes associated with A, Object
       [M], [N] are the proxy classes for M, N

This is the same for a module (rather than class A)

Guy Decoux