Encapsulation issue

Hi,

Is there any way of providing read only access to an array? (problem shown by code below).

class A

  def initialize
    @dependencies = []
  end

  # intended to be read only access
  def dependencies
    @dependencies
  end

  def addDependency(d)
    @dependencies << d
    puts "adding #{d}"
  end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

Any suggestions appreciated,
James

Hi --

···

On Fri, 6 Nov 2009, James French wrote:

Hi,

Is there any way of providing read only access to an array? (problem shown by code below).

class A

def initialize
   @dependencies =
end

# intended to be read only access
def dependencies
   @dependencies
end

def addDependency(d)
   @dependencies << d
   puts "adding #{d}"
end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

You can freeze the array:

   a = .freeze
   a << 1 # TypeError: can't modify frozen array

David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

James French wrote:

Hi,

Is there any way of providing read only access to an array?

I was going to suggest attr_reader :dependencies but a quick test showed
that there's more there than meets the eye.. Oddly.

···

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

James,

It appears, also, that changing @dependencies to a constant also does
not help. Strange.

Ralph

Friday, November 6, 2009, 6:50:30 AM, you wrote:

···

Hi,

Is there any way of providing read only access to an array? (problem shown by code below).

class A

  def initialize
    @dependencies =
  end

  # intended to be read only access
  def dependencies
    @dependencies
  end

  def addDependency(d)
    @dependencies << d
    puts "adding #{d}"
  end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

Any suggestions appreciated,
James

--
Best regards,
Ralph mailto:ralphs@dos32.com

Ask yourself if it's really necessary to implement #dependencies.
(The answer may be 'yes'; I'm just saying don't assume it is.)

For instance, you could provide access to the dependencies like this

  class A
    # initialise and add_dependency as before

    include Enumerable
    def each(&block); @dependencies.each(&block); end

    def (n)
      @dependencies[n] # possibly .dup this
    end
  end

  a = A.new
  a.add_dependency "foo"
  a.each do ... end
  a.map { ... }
  a.grep(...)
  a[5]

I've often found, when wrapping a basic data type, that I don't need
access to the 'guts' of that type; I only need to do a certain set of
things, so I implement them and leave it at that.

···

On Nov 7, 12:50 am, James French <James.Fre...@naturalmotion.com> wrote:

Is there any way of providing read only access to an array? [...]

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

--
Gavin Sinclair

Ruby is a dynamic language, as David pointed out, you can always get around
whatever someone does. I think the point isn't so much to make it impossible
for them to do something, but rather to make it clear how it was anticipated
that it would be used. If they want to go so far as to override the methods,
or perform instance_eval to get at the variable, then I'd take that as a
very deliberate effort, so due to their determination to get around your
design, I would just assume that they had a legitimate reason to do so, or
at least if it blows up, they'll have no cause to be upset with you for it.

I don't know how you are trying to use the class, that you feel the need to
return an array that cannot be altered, but you could define methods which
give this functionality without ever exposing the array itself, something
like this:

# consider defining methods to give the functionality you might want
# without exposing the guts of your class to the world
class Configuration
  def initialize() @dependencies = end
  def add_dependency(d) @dependencies << d end

  def each_dependency
    @dependencies.each { |dep| yield dep }
  end
end

config = Configuration.new
config.add_dependency :dependency1
config.add_dependency :dependency2
config.add_dependency :dependency3

config.each_dependency do |dependency|
  puts "I am accessing #{dependency}, " \
       "without ever seeing the array."
end

# Of course, they can always get around whatever you have done,
# in the above, there is no way to get at @dependencies from the
# outside world, but in their code they could just add the code below
class Configuration
  attr_accessor :dependencies
end

p config.dependencies

__END__
Ruby is not about strict rigid code, or forcing people to use it
a certain way, it is dynamic. You have made your intentions clear,
at this point, if the user is not satisfied with your intentions,
then you must trust them to know what is best for their needs.
Something like this will not occur by accident, they have quite
deliberately decided that they need to break the encapsulation.

···

On Fri, Nov 6, 2009 at 7:50 AM, James French <James.French@naturalmotion.com > wrote:

Hi,

Is there any way of providing read only access to an array? (problem shown
by code below).

class A

def initialize
   @dependencies =
end

# intended to be read only access
def dependencies
   @dependencies
end

def addDependency(d)
   @dependencies << d
   puts "adding #{d}"
end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

Any suggestions appreciated,
James

You could also dup it:

class A
  def dependencies
    @dependencies.dup
  end
end

a = A.new
a.addDependency "foo"
a.dependencies << "bar"
puts a.dependencies # => ["foo"]

···

2009/11/6 David A. Black <dblack@rubypal.com>:

Hi --

On Fri, 6 Nov 2009, James French wrote:

Hi,

Is there any way of providing read only access to an array? (problem shown
by code below).

class A

def initialize
@dependencies =
end

# intended to be read only access
def dependencies
@dependencies
end

def addDependency(d)
@dependencies << d
puts "adding #{d}"
end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

You can freeze the array:

a = .freeze
a << 1 # TypeError: can't modify frozen array

But then I can't legitimately push to it in addDependency...

···

-----Original Message-----
From: David A. Black [mailto:dblack@rubypal.com]
Sent: 06 November 2009 14:01
To: ruby-talk ML
Subject: Re: encapsulation issue

Hi --

On Fri, 6 Nov 2009, James French wrote:

> Hi,
>
> Is there any way of providing read only access to an array? (problem
shown by code below).
>
> class A
>
> def initialize
> @dependencies =
> end
>
> # intended to be read only access
> def dependencies
> @dependencies
> end
>
> def addDependency(d)
> @dependencies << d
> puts "adding #{d}"
> end
> end
>
>
> a = A.new
> a.addDependency("foo")
> a.dependencies << "bar" # encapsulation subverted
>
> puts a.dependencies # foo and bar both in array

You can freeze the array:

   a = .freeze
   a << 1 # TypeError: can't modify frozen array

David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

Thanks for that. I think I may go that way.

···

-----Original Message-----
From: Gavin Sinclair [mailto:gsinclair@gmail.com]
Sent: 06 November 2009 23:56
To: ruby-talk ML
Subject: Re: encapsulation issue

On Nov 7, 12:50 am, James French <James.Fre...@naturalmotion.com> > wrote:
>
> Is there any way of providing read only access to an array? [...]
>
>
> a = A.new
> a.addDependency("foo")
> a.dependencies << "bar" # encapsulation subverted

Ask yourself if it's really necessary to implement #dependencies.
(The answer may be 'yes'; I'm just saying don't assume it is.)

For instance, you could provide access to the dependencies like this

  class A
    # initialise and add_dependency as before

    include Enumerable
    def each(&block); @dependencies.each(&block); end

    def (n)
      @dependencies[n] # possibly .dup this
    end
  end

  a = A.new
  a.add_dependency "foo"
  a.each do ... end
  a.map { ... }
  a.grep(...)
  a[5]

I've often found, when wrapping a basic data type, that I don't need
access to the 'guts' of that type; I only need to do a certain set of
things, so I implement them and leave it at that.

--
Gavin Sinclair

Thanks for the answer - appreciate it, and have taken it on board.

···

-----Original Message-----
From: Josh Cheek [mailto:josh.cheek@gmail.com]
Sent: 07 November 2009 02:43
To: ruby-talk ML
Subject: Re: encapsulation issue

On Fri, Nov 6, 2009 at 7:50 AM, James French > <James.French@naturalmotion.com > > wrote:

> Hi,
>
> Is there any way of providing read only access to an array? (problem
shown
> by code below).
>
> class A
>
> def initialize
> @dependencies =
> end
>
> # intended to be read only access
> def dependencies
> @dependencies
> end
>
> def addDependency(d)
> @dependencies << d
> puts "adding #{d}"
> end
> end
>
>
> a = A.new
> a.addDependency("foo")
> a.dependencies << "bar" # encapsulation subverted
>
> puts a.dependencies # foo and bar both in array
>
>
> Any suggestions appreciated,
> James
>
>
Ruby is a dynamic language, as David pointed out, you can always get
around
whatever someone does. I think the point isn't so much to make it
impossible
for them to do something, but rather to make it clear how it was
anticipated
that it would be used. If they want to go so far as to override the
methods,
or perform instance_eval to get at the variable, then I'd take that as
a
very deliberate effort, so due to their determination to get around
your
design, I would just assume that they had a legitimate reason to do so,
or
at least if it blows up, they'll have no cause to be upset with you for
it.

I don't know how you are trying to use the class, that you feel the
need to
return an array that cannot be altered, but you could define methods
which
give this functionality without ever exposing the array itself,
something
like this:

# consider defining methods to give the functionality you might want
# without exposing the guts of your class to the world
class Configuration
  def initialize() @dependencies = end
  def add_dependency(d) @dependencies << d end

  def each_dependency
    @dependencies.each { |dep| yield dep }
  end
end

config = Configuration.new
config.add_dependency :dependency1
config.add_dependency :dependency2
config.add_dependency :dependency3

config.each_dependency do |dependency|
  puts "I am accessing #{dependency}, " \
       "without ever seeing the array."
end

# Of course, they can always get around whatever you have done,
# in the above, there is no way to get at @dependencies from the
# outside world, but in their code they could just add the code below
class Configuration
  attr_accessor :dependencies
end

p config.dependencies

__END__
Ruby is not about strict rigid code, or forcing people to use it
a certain way, it is dynamic. You have made your intentions clear,
at this point, if the user is not satisfied with your intentions,
then you must trust them to know what is best for their needs.
Something like this will not occur by accident, they have quite
deliberately decided that they need to break the encapsulation.

I don't think it solves it that nicely because as well as duplicating data which doesn't feel good, you've then got a line that looks as though it does something but doesn't. I guess it could raise an error somehow.

···

-----Original Message-----
From: Tom Stuart [mailto:tom@therye.org]
Sent: 06 November 2009 14:06
To: ruby-talk ML
Subject: Re: encapsulation issue

2009/11/6 David A. Black <dblack@rubypal.com>:
> Hi --
>
> On Fri, 6 Nov 2009, James French wrote:
>
>> Hi,
>>
>> Is there any way of providing read only access to an array? (problem
shown
>> by code below).
>>
>> class A
>>
>> def initialize
>> @dependencies =
>> end
>>
>> # intended to be read only access
>> def dependencies
>> @dependencies
>> end
>>
>> def addDependency(d)
>> @dependencies << d
>> puts "adding #{d}"
>> end
>> end
>>
>>
>> a = A.new
>> a.addDependency("foo")
>> a.dependencies << "bar" # encapsulation subverted
>>
>> puts a.dependencies # foo and bar both in array
>
> You can freeze the array:
>
> a = .freeze
> a << 1 # TypeError: can't modify frozen array
>
>

You could also dup it:

class A
  def dependencies
    @dependencies.dup
  end
end

a = A.new
a.addDependency "foo"
a.dependencies << "bar"
puts a.dependencies # => ["foo"]

No, but you could dup-and-append when you hit that.

I think you pretty much have to dup the object in this case.

Hmm. Okay, imagine this. Define a new Array-like class, which has an
Array as an internal member, but overrides all the things which modify that
array, then return one wrapped around the object. At least you're then
limiting the amount of duplication you do.

A question naturally arises: What do you want to happen if I try to
send a modify-yourself message to a member of the array?

-s

···

On 2009-11-06, James French <James.French@naturalmotion.com> wrote:

But then I can't legitimately push to it in addDependency...

--
Copyright 2009, all wrongs reversed. Peter Seebach / usenet-nospam@seebs.net
| Seebs.Net <-- lawsuits, religion, and funny pictures
Fair game (Scientology) - Wikipedia <-- get educated!

Hi --

From: David A. Black [mailto:dblack@rubypal.com]
Sent: 06 November 2009 14:01
To: ruby-talk ML
Subject: Re: encapsulation issue

Hi --

Hi,

Is there any way of providing read only access to an array? (problem

shown by code below).

class A

def initialize
   @dependencies =
end

# intended to be read only access
def dependencies
   @dependencies
end

def addDependency(d)
   @dependencies << d
   puts "adding #{d}"
end
end

a = A.new
a.addDependency("foo")
a.dependencies << "bar" # encapsulation subverted

puts a.dependencies # foo and bar both in array

You can freeze the array:

   a = .freeze
   a << 1 # TypeError: can't modify frozen array

David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

But then I can't legitimately push to it in addDependency...

Whoops. I had a nagging feeling I wasn't giving the question enough
attention when I answered :slight_smile:

I would probably favor the dup approach for the reader. In the end,
someone can get around it:

   a.instance_eval { @dependencies << "bar" }

But it would at least keep away casual and accidental appenders.

David

···

On Sat, 7 Nov 2009, James French wrote:

-----Original Message-----
On Fri, 6 Nov 2009, James French wrote:

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)