Controlling acces to object instantiation?

(Andrew S. Townley) #1

Hello everyone,

Trying to get my head around some of the subtleties of Ruby. I've done
quite a bit of C/C++/Java and some Python programming, and the normal
way that I'd solve this problem (in pseudo-Java) would be:

interface SomeFeature
{
  void execute();
}

public class FeatureFactory
{
  private static class FeatureImpl implements SomeFeature
  {
    public FeatureImpl(String initParam1,
        String initParam2)
    {
    ...
    }
    
    public void execute() { ... }
  }

  public SomeFeature createFeature()
  {
    return new FeatureImpl("param1", "param2");
  }
}

I've seen some of the previous threads on the list (starting from here:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/60616), but
I don't see anything immediately obvious as to how to encapsulate the
creation of my concrete implementations of a common interface (even with
duck typing, there's an implied interface).

The main issue here is that I want to prevent the client code from
instantiating their own objects. The objects should be created only by
a factory of some sort (who has implementation-specific configuration
information about how to bootstrap them).

It would appear that if you have 'require'd the appropriate files (or
can), you can create any instances you want. Is there a way to
accomplish what I'm trying to do beyond saying "don't do that" in the
documentation?

Any assistance would be appreciated.

Thanks in advance,

ast

···

***************************************************************************************************
The information in this email is confidential and may be legally privileged. Access to this email by anyone other than the intended addressee is unauthorized. If you are not the intended recipient of this message, any review, disclosure, copying, distribution, retention, or any action taken or omitted to be taken in reliance on it is prohibited and may be unlawful. If you are not the intended recipient, please reply to or forward a copy of this message to the sender and delete the message, any attachments, and any copies thereof from your system.
***************************************************************************************************

(Gavin Kistner) #2

class Foo
     def self.make
         self.new( rand )
     end

     def initialize( id )
         @id = id
     end

     class << self
         protected :new
     end
end

p f1 = Foo.make
# => #<Foo:0x340858 @id=0.452814124524593>

p f2 = Foo.new( 12 )
# => /Users/gkistner/Desktop/tmp.rb:16: protected method `new' called for Foo:Class (NoMethodError)

···

On Aug 19, 2005, at 9:03 AM, Andrew S. Townley wrote:

The main issue here is that I want to prevent the client code from
instantiating their own objects. The objects should be created only by
a factory of some sort (who has implementation-specific configuration
information about how to bootstrap them).

(Ara.T.Howard) #3

harp:~ > cat a.rb
     module Factory
       @classes = {
         'a' => (
             Class::new do
               class << self
                 def foo; 42; end
               end
               def bar; 42.0; end
             end
         ),
         'b' => (
             Class::new do
               class << self
                 def foo; 'FORTY-TWO'; end
               end
               def bar; 'forty-two'; end
             end
         ),
       }
       def self::new(*a, &b)
         class_for(a.shift)::new(*a, &b)
       end
       def self::class_for typename
         @classes[typename]
       end
     end

     a_obj = Factory::new 'a'
     b_obj = Factory::new 'b'

     a_class = Factory::class_for 'a'
     b_class = Factory::class_for 'b'

     p a_obj.bar
     p b_obj.bar

     p a_class::foo
     p b_class::foo

     harp:~ > ruby a.rb
     42.0
     "forty-two"
     42
     "FORTY-TWO"

obviously you would want 'class_for' to be private - but this illustrates the
general idea.

hth.

-a

···

On Sat, 20 Aug 2005, Andrew S. Townley wrote:

Hello everyone,

Trying to get my head around some of the subtleties of Ruby. I've done
quite a bit of C/C++/Java and some Python programming, and the normal
way that I'd solve this problem (in pseudo-Java) would be:

interface SomeFeature
{
  void execute();
}

public class FeatureFactory
{
  private static class FeatureImpl implements SomeFeature
  {
    public FeatureImpl(String initParam1,
        String initParam2)
    {
    ...
    }

    public void execute() { ... }
  }

  public SomeFeature createFeature()
  {
    return new FeatureImpl("param1", "param2");
  }
}

I've seen some of the previous threads on the list (starting from here:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/60616), but
I don't see anything immediately obvious as to how to encapsulate the
creation of my concrete implementations of a common interface (even with
duck typing, there's an implied interface).

The main issue here is that I want to prevent the client code from
instantiating their own objects. The objects should be created only by
a factory of some sort (who has implementation-specific configuration
information about how to bootstrap them).

It would appear that if you have 'require'd the appropriate files (or
can), you can create any instances you want. Is there a way to
accomplish what I'm trying to do beyond saying "don't do that" in the
documentation?

Any assistance would be appreciated.

Thanks in advance,

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================

(Andrew S. Townley) #4

Cheers, Gavin. Thanks for the quick reply. I figured there had to be a
way, but it wasn't something I remember reading in the pickaxe book or
anywhere else that I'd seen so far.

Thanks again,

ast

···

On Fri, 2005-08-19 at 16:10, Gavin Kistner wrote:

On Aug 19, 2005, at 9:03 AM, Andrew S. Townley wrote:
> The main issue here is that I want to prevent the client code from
> instantiating their own objects. The objects should be created
> only by
> a factory of some sort (who has implementation-specific configuration
> information about how to bootstrap them).

class Foo
     def self.make
         self.new( rand )
     end

     def initialize( id )
         @id = id
     end

     class << self
         protected :new
     end
end

p f1 = Foo.make
# => #<Foo:0x340858 @id=0.452814124524593>

p f2 = Foo.new( 12 )
# => /Users/gkistner/Desktop/tmp.rb:16: protected method `new' called
for Foo:Class (NoMethodError)

***************************************************************************************************
The information in this email is confidential and may be legally privileged. Access to this email by anyone other than the intended addressee is unauthorized. If you are not the intended recipient of this message, any review, disclosure, copying, distribution, retention, or any action taken or omitted to be taken in reliance on it is prohibited and may be unlawful. If you are not the intended recipient, please reply to or forward a copy of this message to the sender and delete the message, any attachments, and any copies thereof from your system.
***************************************************************************************************

(Andrew S. Townley) #5

Hi Ara,

Hmmm... interesting approach. I'm not sure I quite need the indirection
you've illustrated, but it's interesting to see all the different ways
you can solve the problem in Ruby. The passing the class declaration as
a block to the Class:new method will require me to do some more
reading. I haven't looked into the reflection/class operations much
yet. I want to make sure that I understand exactly what's happening
here. Instinctively, I think I understand what's going on, but need to
double-check. Some things are still Ruby-101 to me at this stage.

Also, I have a question further down...

     harp:~ > cat a.rb
     module Factory
       @classes = {
         'a' => (
             Class::new do
               class << self
                 def foo; 42; end
               end
               def bar; 42.0; end
             end
         ),
         'b' => (
             Class::new do
               class << self
                 def foo; 'FORTY-TWO'; end
               end
               def bar; 'forty-two'; end
             end
         ),
       }
       def self::new(*a, &b)
         class_for(a.shift)::new(*a, &b)

I'm assuming you're doing the 'a.shift' call here to allow you to
specify all of the arguments you might need to the constructor. Is that
correct? Thus you could potentially write something like:

  ref = Factory::new 'a', 'param1', 'param2'

which would allow you to have the class 'a' support a constructor taking
the two parameters (and, of course, the optional block).

Anyway, thanks for the neat example! :slight_smile:

ast

···

On Fri, 2005-08-19 at 16:20, Ara.T.Howard wrote:

***************************************************************************************************
The information in this email is confidential and may be legally privileged. Access to this email by anyone other than the intended addressee is unauthorized. If you are not the intended recipient of this message, any review, disclosure, copying, distribution, retention, or any action taken or omitted to be taken in reliance on it is prohibited and may be unlawful. If you are not the intended recipient, please reply to or forward a copy of this message to the sender and delete the message, any attachments, and any copies thereof from your system.
***************************************************************************************************

(Ara.T.Howard) #6

Hmmm... interesting approach. I'm not sure I quite need the indirection
you've illustrated, but it's interesting to see all the different ways you
can solve the problem in Ruby. The passing the class declaration as a block
to the Class:new method will require me to do some more reading. I haven't
looked into the reflection/class operations much yet. I want to make sure
that I understand exactly what's happening here. Instinctively, I think I
understand what's going on, but need to double-check. Some things are still
Ruby-101 to me at this stage.

i agree it's overkill - but i does prevent the classes from being introduced
as valid constants when the file is required and it allows you you basically
have the classes themselves as private instance variables. this is about as
protected as you can get in ruby - since there's always instance_eval...
nevertheless it does afford total control over how these objects get created.

Also, I have a question further down...

     harp:~ > cat a.rb
     module Factory
       @classes = {
         'a' => (
             Class::new do
               class << self
                 def foo; 42; end
               end
               def bar; 42.0; end
             end
         ),
         'b' => (
             Class::new do
               class << self
                 def foo; 'FORTY-TWO'; end
               end
               def bar; 'forty-two'; end
             end
         ),
       }
       def self::new(*a, &b)
         class_for(a.shift)::new(*a, &b)

I'm assuming you're doing the 'a.shift' call here to allow you to specify
all of the arguments you might need to the constructor. Is that correct?
Thus you could potentially write something like:

  ref = Factory::new 'a', 'param1', 'param2'

which would allow you to have the class 'a' support a constructor taking the
two parameters (and, of course, the optional block).

exactly - in reality you'd probably determine which class to do based on the
args alone, or something else...

cheers.

-a

···

On Sat, 20 Aug 2005, Andrew S. Townley wrote:

On Fri, 2005-08-19 at 16:20, Ara.T.Howard wrote:

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================