Achieve pure object oriented design in Ruby

Hi,

I have 3 classes A, B and C; I want to achieve following behavior (I am
calling it “pure object oriented designing”) with them.

  1.   An instance of Class C should not exist without instance of 
    

Class B
and instance of Class B should not exist without instance of Class A.

  1.   Class A should be the only class accessible externally, i.e. 
    

one
should not be able to create/modify/delete instances of class B and
class C
outside the scope of class A.

  1.   For modifying member variables of class B and class C, an 
    

external
client should use class A.

  1.   class A has multiple instances of class B associated with it 
    

and
similarly class B has multiple instances of class C associated with it.

Following are the classes with required methods and variables;

class A

#array of objects of class B associated with this instance

 @b_objs

#method to create instance of B and add it to above array

#methods to modify member variables of an instance of B and C (class C
is
defined below)

end

class B

@b1

@b2

#array of objects of class C

 @c_objs

#method to create instance of C and add it to above array

#methods to modify member variables of an instance of C

end

class C

@c1

        @c2

end

How would I achieve 4 points listed above?

One solution I thought was to have nested classes as below,

Class A

Class B

        Class C

        end

end

end

above approach satisfies all the points, but for point 3 above(For
modifying
member variables of class B and class C, an external client should use
class
A), there would be an overhead of adding a large number of wrapper
methods
for members of classes B and C.

Is there any cleaner solution in ruby by which I can achieve above
requirements?

On Jun 19, 2006, at 9:00 AM, NAYAK wrote:

  1.   Class A should be the only class accessible externally,  
    

it and
similarly class B has multiple instances of class C associated with
it.

[snip: example code]

Is there any cleaner solution in ruby by which I can achieve above
requirements?

I’m a little bummed no-one’s tried to answer this. I have no idea
how to do it, but the big question in my head is “why?”.
From a theoretical perspective, encapsulation like this does seems
kind of neat. But it sounds like it would make unit testing nearly
impossible, which causes some practical issues.

For my own curiosity, is this just something you’re trying as an
exercise? Are you mimicking something that’s common practice in
another language?

-Mat

On 6/20/06, Mat S. [email protected] wrote:

On Jun 19, 2006, at 9:00 AM, NAYAK wrote:

Hi,

---- SNIP

I resisted, but as you insist I will explain why I did not answer.

I think, but I do not have the time to verify, that it is
theoretically
impossible.

I was sure to give a convenient solution where one can still create B
and C
objects but only difficultly, kind of assuring that nobody will create
them
by error, and…

… failed to come up with a sensible solution.

As yourself, I would like to see some nice meta trick here, I did not
come
up with it :frowning:

Robert

From a theoretical perspective, encapsulation like this does seems

kind of neat. But it sounds like it would make unit testing nearly
impossible, which causes some practical issues.

For my own curiosity, is this just something you’re trying as an
exercise? Are you mimicking something that’s common practice in
another language?

-Mat


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

NAYAK wrote:

A), there would be an overhead of adding a large number of wrapper
methods
for members of classes B and C.

I haven’t had time to think through it completely, but what if you
overrode method_missing in A? Your method_missing could find out if
B.responds_to? the message, and if so, send() it the message along with
the original arguments.

Just a thought.

Jeff

This is still very abstract. For instance, you’ll need to program some

logic for deciding which children get forwarded requests, etc. Also,

I’ve

used ( *args, &block ) as parameter/argument lists a lot. You’ll want

to

specialize those to your actual usage.

class A
def initialize
@b_objs = []
end

def add_b( *args, &block )
@b_objs << B.new( *args, &block )
end

delegation to one of the b_objs

def b1( *args, &block )
choose_b( *args, &block ).b1
end

def b2( *args, &block )
choose_b( *args, &block ).b2
end

def add_c( *args, &block )
choose_b_for_c( *args, &block ).add_c( *args, &block )
end

def c1( *args, &block )
choose_b_for_c( *args, &block ).c1
end

def c2( *args, &block )
choose_b_for_c( *args, &block ).c2
end

protected
def choose_b( *args, &block )
# put logic here for determining which b_obj matches the parameters.
# Example:
@b_objs.first
end

def choose_b_for_c( *args, &block )
# put logic here for determining which b_obj contains the c_obj that
# matches the parameters. Example:
@b_objs.first
end
end

class B
attr_reader :b1, :b2

def initialize
@c_objs = []
end

def add_c( *args, &block )
@c_objs << C.new( *args, &block )
end

delegation to one of the c_objs

def c1( *args, &block )
choose_c( *args, &block ).c1
end

def c2( *args, &block )
choose_c( *args, &block ).c2
end

protected
def choose_c( *args, &block )
# put logic here for determining which c_obj matches the parameters.
# Example:
@c_objs.first
end
end

class C
attr_accessor :c1, c2
end

Jacob F.

SNIP

Fortunately I did not bet, OP does this pattern fit your needs?

class A
@@b = Class.new {
def initialize name; @name=name; end
def to_s; “#{@name}:#{self.class}”; end
}

class << self
    def factory( name );
        @@b.new( name )
    end
end

end

x = A.factory( “hi” )
puts x

Hope this helps

Cheers
Robert

I don’t think it’s possible without redefining instance_eval and
class_eval. You could make B an anonymous class assigned to a class
variable, though. That at least provides security through obscurity.

On 6/20/06, Jeff C. [email protected] wrote:

NAYAK wrote:

A), there would be an overhead of adding a large number of wrapper
methods
for members of classes B and C.

I haven’t had time to think through it completely, but what if you
overrode method_missing in A? Your method_missing could find out if
B.responds_to? the message, and if so, send() it the message along with
the original arguments.

The delegation is not a problem, though an elegant way to do it might
require some skills.
The real problem is requirement 2) of the OP, instanciation of class B
shall only be possible in class A, e.g. in an instance of A, I do not
think
this is possible, but I would not bet on it (not much).
It would already be nice to have some code which defends against the
vanilla
instanciation of B outside of an A instance.
I did not yet come up with such a solution though.
Any ideas?

Cheers
Robert

Just a thought.

Jeff
softiesonrails.com


Posted via http://www.ruby-forum.com/.


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

On 6/21/06, Jacob F. [email protected] wrote:

Any ideas?

One way would be to make B.new protected/private (similar to the way
it’s done in the Singleton mixin). But once you’ve done that, I’m not
sure if there’s a clean way to allow A to use that method – Ruby
doesn’t have “friends” ala C++ (and I’m glad for it). Of course, once
it’s protected you could have A use send explicitly:

as can anyone else :frowning:

class B

a = A.new
a.create_b # works fine
B.new # raises NoMethodError

B.send(:new) works
But I reasoned along the same lines as you, eventually I came up with an
anonymous class for B inside A
see my next post please.
If that pleases the OP I do not think the anonymous class is easily
accessible, of course one can always create an accessor to the anonymous
class inside A, :frowning:

Cheers
Robert

This works, but it doesn’t feel very clean :confused:

Jacob F.


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

On 6/20/06, Robert D. [email protected] wrote:

The real problem is requirement 2) of the OP, instanciation of class B
shall only be possible in class A, e.g. in an instance of A, I do not think
this is possible, but I would not bet on it (not much).
It would already be nice to have some code which defends against the vanilla
instanciation of B outside of an A instance.
I did not yet come up with such a solution though.
Any ideas?

One way would be to make B.new protected/private (similar to the way
it’s done in the Singleton mixin). But once you’ve done that, I’m not
sure if there’s a clean way to allow A to use that method – Ruby
doesn’t have “friends” ala C++ (and I’m glad for it). Of course, once
it’s protected you could have A use send explicitly:

class B
class << self
protected :new
end
end

class A
def create_b
@b_objs << B.send(:new)
end
end

a = A.new
a.create_b # works fine
B.new # raises NoMethodError

This works, but it doesn’t feel very clean :confused:

Jacob F.

On 6/20/06, Robert D. [email protected] wrote:

On 6/21/06, Jacob F. [email protected] wrote:

One way would be to make B.new protected/private (similar to the way
it’s done in the Singleton mixin). But once you’ve done that, I’m not
sure if there’s a clean way to allow A to use that method – Ruby
doesn’t have “friends” ala C++ (and I’m glad for it). Of course, once
it’s protected you could have A use send explicitly:

as can anyone else :frowning:

If that pleases the OP I do not think the anonymous class is easily
accessible, of course one can always create an accessor to the anonymous
class inside A, :frowning:

Exactly. With a language like Ruby, it’s really hard to not let
someone do something. I simply went for removing the obvious path, but
not making it impossible.

Jacob F.

On Wed, Jun 21, 2006 at 08:48:38AM +0900, Jacob F. wrote:

If that pleases the OP I do not think the anonymous class is easily
accessible, of course one can always create an accessor to the anonymous
class inside A, :frowning:

Exactly. With a language like Ruby, it’s really hard to not let
someone do something. I simply went for removing the obvious path, but
not making it impossible.

Jacob F.

In no language private/protected (or final) is a fool proof security
measure. It is there to provide some safety nets against errors, and
to help design, not for security. It is quite easy in C++ to get at
any function in memory via pointers. In Java people can use AOP to
accomplish this.

Your most economical bet is to just document the class (or whatever)
to be “private, not to be used directly” in Ruby. Sometimes the
low-tech effort is simply the best, especially if any high-tech effort
is going to put you to much more troubles than the attacker, who will
circumvent it fast any way.

Jürgen

On Jun 20, 2006, at 6:02 PM, Robert D. wrote:

The real problem is requirement 2) of the OP, instanciation of
class B
shall only be possible in class A, e.g. in an instance of A, I do
not think
this is possible, but I would not bet on it (not much).

Why not just make B an anonymous class:

class A
def initialize
@bclass = Class.new do
# class definition here
end
@bs = []
end

def another_b_please
@bs << @bclass.new
@bs.last
end
end

Gary W.

On 6/21/06, [email protected] [email protected] wrote:

  @bs.last

end
end

Gary that is what I did, and discussed above. ( I used an anonymous
class
inside A, you use one inside an instance of A, which might be quite
disturbing unless A is a singleton itself!!)

One can create an accessor to @b at any time

class A
attr_accessor :bclass
end
A.new.bclass.new #voilÃ

cheers
Robert

Gary W.


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

On 6/21/06, Juergen S. [email protected] wrote:

In no language private/protected (or final) is a fool proof security

I doubt this strongly, Ada springs into mind.

measure. It is there to provide some safety nets against errors, and

to help design, not for security. It is quite easy in C++ to get at
any function in memory via pointers. In Java people can use AOP to
accomplish this.

In theory I hate it, in practice it is frequently sufficent.
Ruby is not doing that bad either, in practice I will often not care if
somebody redefines the access rights of my methods, I feel that this is
a
theoretical discussion, though, right ?

Your most economical bet is to just document the class (or whatever)

to be “private, not to be used directly” in Ruby. Sometimes the
low-tech effort is simply the best, especially if any high-tech effort
is going to put you to much more troubles than the attacker, who will
circumvent it fast any way.

I appreciate your defence of ruby :wink: but the ability to completely hide
implementation from the interface user is a feature not a bug.

It does not seem a good idea to me to allow everything with the perfect
excuse of following the “enabeling approach”, because it does not allow
me
to implement some of the more basic principles of modern Software Design
(Data Hiding for instance).

All this is on a philosophical base, in practice Ruby gives me what I
need
However, I is not the world, as has been pointed out to me frequently
and
recently, quite a rude message BTW :wink:

Cheers
Robert

Jürgen

tlrbkG0eXroEYvJnfNGkHAk4UEP11CA/gy6cSzioHRxHtQo8Gt5ktf+uThhHn7+Z
ybSJoMNRltsK/UXc4nxQgw9g8YkNVWooo6QWYf+oqyuNenpE8NoHz5u52CrUUzx0
AoKjR9EVjZeucV5HgmF2Mgfe039A68esCSZ4Xi/3DU1rTfqlIKOaxhDrreBvgoZv
ETANSNrWAX1TBe1Q/jua11nA/3tS2E/8O6oho7NJIeiAiThb20+aOQ==
=G/Vr
-----END PGP SIGNATURE-----


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

[email protected] schrieb:

def another_b_please
inside A, you use one inside an instance of A, which might be quite
Yeah, I shouldn’t have written it as creating a new class for each
to @bclass but that is a general characteristic of any Ruby code. I
the absence of some appropriate documentation seems even more far

Gary W.

I agree…

I think it’s kind of old fashioned to hide all and everything from
hostile coders doing development in your team (sounds strange anyway…)

My experience is that hiding stuff causes problems when you try to
extend existing object / classes. You then have to break everything just
to get your thing going. Even the best gurus oversee possible uses of
their libraries…

Hiding methods, etc. never in my career saved me from doing the wrong
thing. The only thing that does is good documentation (rare thing!)
and talkative names…

On Jun 21, 2006, at 12:51 PM, Robert D. wrote:

  @bs.last

One can create an accessor to @b at any time

class A
attr_accessor :bclass
end
A.new.bclass.new #voilà

Yeah, I shouldn’t have written it as creating a new class for each
instance of A. Something like:

class A
@bclass = Class.new {
# anonymous class def here
}

details left for the reader…

end

would be better. Yes, you can always reopen A to create an accessor
to @bclass but that is a general characteristic of any Ruby code. I
think tucking the reference to the class in an instance variable
hides it a ‘little’ better than having the reference be a constant in
A. A::B.new is pretty accessible as is changing the visibility of B.new.

Still, I’m not sure that it makes sense to go to all the trouble to
‘hide’ these other classes. Whenever I see this sort of thing I say
‘why bother?’. The idea that you can programatically protect
yourself against some hostile coder who can modify the source code
(or link into your library in a static language) just seems like a
waste of time. And the idea that you can enforce correct calls to
your API in the absence of some appropriate documentation seems even
more far fetched. People don’t just guess randomly at the arguments
and sequence of calls to a class/library. They are looking at
examples or documentation. If they want to experiment with calls
that are undocumented or that explicitly violate a documented pre-
condition of the API, why do I care?

If you really want to protect yourself against hostile client code, I
think you have to go to an inter-process communication interface to
your library/service.

Gary W.

[email protected] wrote:

On Jun 21, 2006, at 12:51 PM, Robert D. wrote:

‘little’ better than having the reference be a constant in A. A::B.new
is pretty accessible as is changing the visibility of B.new.

You can hide it a bit better using a closure, and then attr_accessor
won’t expose it. (Sorry if someone suggested this already…I haven’t
been following closely.)

class A
class << self
bclass = Class.new do
def foo; puts “foo in bclass”; end
end

define_method :new do |*args|
  bclass.new(*args)
end

end
end

A.new.foo # ==> foo in bclass

On 6/21/06, Joel VanderWerf [email protected] wrote:

define_method :new do |*args|
  bclass.new(*args)
end

end
end

A.new.foo # ==> foo in bclass

I think you rather meant:

class A
def initialize
@b_objs = []
end

bclass = Class.new do
  def initialize
    puts "creating an instance of bclass"
  end
end

define_method :gimme_another_b do |*args|
  @b_objs << bclass.new(*args)
end

end

a = A.new
a.gimme_another_b # ==> creating an instance of bclass

It looks to me that as long as there is no access to @b_objs, this
successfully hides bclass. But as soon as @b_objs is accessible:

class A
attr_reader :b_objs
end
bclass = a.b_objs.first.class
bclass.new # ==> creating an instance of bclass

Not meant to be nitpicking you specifically Joel, just continuing to
demonstrate that one really can’t prevent things like this in a
language with open and first-class Classes. :slight_smile:

Jacob F.

Jacob F. wrote:

class A
attr_reader :b_objs
end
bclass = a.b_objs.first.class
bclass.new # ==> creating an instance of bclass

We can just keep playing the closure game:

class A
def initialize
class << self
b_objs = []

    bclass = Class.new do
      def initialize
        puts "creating an instance of bclass"
      end
    end

    define_method :gimme_another_b do |*args|
      b = bclass.new(*args)
      b_objs << b
      b # return b instead of b_objs
    end

    define_method :secret_method_to_get_b_objs do
      b_objs
    end
  end
end

end

a = A.new
p a.secret_method_to_get_b_objs
p a.gimme_another_b
p a.secret_method_to_get_b_objs

END

output:

[]
creating an instance of bclass
#<#Class:0xb7dc4aa0:0xb7dc453c>
[#<#Class:0xb7dc4aa0:0xb7dc453c>]