I fell into these pitfalls yesterday… that a child was modifying a parent!
How do one protect (readonly) the parent A, from the derivived class B ???
What is your thoughts about writing ‘safe’ code ?
ruby tst.rb
Watch out, we have been modified
Watch out, we have been modified
expand -t2 tst.rb
class A
def initialize @value = [1, 2, 3]
end
attr_reader :value
def selftest
if value.size == 3
puts "OK"
else
puts "Watch out, we have been modified"
end
end
end
class B < A
def test1
objs = value
objs << 4
selftest
end
def test2 @value = [5, 6]
selftest
end
end
I fell into these pitfalls yesterday… that a child was modifying a parent!
How do one protect (readonly) the parent A, from the derivived class B ???
What is your thoughts about writing ‘safe’ code ?
Do you talk about such as the followings?
···
From: “Simon Strandgaard” 0bz63fz3m1qt3001@sneakemail.com
Subject: protect parents from children
Date: Wed, 25 Jun 2003 19:38:00 +0900
[ruby]$ /usr/local/bin/irb
irb(main):001:0> class AAA
irb(main):002:1> def self.foo
irb(main):003:2> p 'foo’
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> class BBB < AAA; end
=> nil
irb(main):007:0> class CCC < AAA; end
=> nil
irb(main):008:0> class << CCC
irb(main):009:1> def c_bar
irb(main):010:2> self.ancestors[1].class_eval{
irb(main):011:3* def self.bar
irb(main):012:4> p ‘bar’
irb(main):013:4> end
irb(main):014:3> }
irb(main):015:2> end
irb(main):016:1> def c_baz
irb(main):017:2> self.ancestors[1].class_eval{
irb(main):018:3* def self.baz
irb(main):019:4> p ‘baz’
irb(main):020:4> end
irb(main):021:3> }
irb(main):022:2> end
irb(main):023:1> end
=> nil
irb(main):024:0> AAA.foo
"foo"
=> nil
irb(main):025:0> BBB.foo
"foo"
=> nil
irb(main):026:0> AAA.bar
NoMethodError: undefined method bar' for AAA:Class from (irb):26 from :0 irb(main):027:0> BBB.bar NoMethodError: undefined methodbar’ for BBB:Class
from (irb):27
from :0
irb(main):028:0> CCC.c_bar
=> nil
irb(main):029:0> AAA.bar
"bar"
=> nil
irb(main):030:0> BBB.bar
"bar"
=> nil
irb(main):031:0> AAA.freeze
=> AAA
irb(main):032:0> CCC.c_baz
TypeError: can’t modify frozen object
from (irb):18:in c_baz' from (irb):17:inclass_eval’
from (irb):17:in class_eval' from (irb):17:inc_baz’
from (irb):32
from :0
irb(main):033:0> AAA.bar
"bar"
=> nil
irb(main):034:0> AAA.baz
NoMethodError: undefined method baz' for AAA:Class from (irb):34 from :0 irb(main):035:0> BBB.baz NoMethodError: undefined methodbaz’ for BBB:Class
from (irb):35
from :0
irb(main):036:0>
I fell into these pitfalls yesterday… that a child was modifying a parent!
You make a confusion : the child was not modifying the parent.
An instance variable belongs to an object (i.e. B.new in your case).
class A
[…]
B.new.test1
B.new.test2
add this
p B.new.value
and you’ll have [1, 2, 3]
Guy Decoux
Me don’t understand. If I add debug-print’s to selftest then I see that
the parent has changed ?? Can you enligthen me ?
ruby tst.rb
Watch out, we have been modified
[1, 2, 3, 4]
Watch out, we have been modified
[5, 6]
To me it looks like the parent has been modified by the child.
expand -t2 tst.rb
class A
def initialize @value = [1, 2, 3]
end
attr_reader :value
def selftest
if @value.size == 3
puts "OK"
else
puts "Watch out, we have been modified"
p @value
end
end
end
class B < A
def test1
objs = value
objs << 4
selftest
end
def test2 @value = [5, 6]
selftest
end
end
I cannot easily find a way around your problem, if all you want is
protection of an instance variable you could create access method for
that variable (you could declare those as private if you want) and then
have all the access controls you could desire in those access methods.
There is, of cause, no way to prevent that those access methods are
overloaded in the child class.
class A
def var @var
end
def var= (v)
# control here @var = v
end
end
there is, tho, no way to prevent a child from declaring it’s own var=()
method.
···
–
dc -e
4ddod3dddn1-89danrn10-dan3+ann6dan2an13dn1+dn2-dn3+5ddan2/9+an13nap
Me don't understand. If I add debug-print's to selftest then I see that
the parent *has* changed ?? Can you enligthen me ?
svg% cat b.rb
#!/usr/bin/ruby
class A
def initialize @value = [1, 2, 3]
puts "#{self} -- #{@value.inspect}"
end
attr_reader :value
def selftest
if @value.size == 3
puts "OK"
else
puts "Watch out, we have been modified"
p @value
end
puts "#{self} -- #{@value.inspect}"
end
end
class B < A
def test1
puts "#{self} -- #{@value.inspect}"
objs = value
objs << 4
selftest
end
def test2
puts "#{self} -- #{@value.inspect}" @value = [5, 6]
selftest
end
end
B.new.test1
B.new.test2
svg%
svg% b.rb
#<B:0x400992a4> -- [1, 2, 3]
#<B:0x400992a4> -- [1, 2, 3]
Watch out, we have been modified
[1, 2, 3, 4]
#<B:0x400992a4> -- [1, 2, 3, 4]
#<B:0x4009922c> -- [1, 2, 3]
#<B:0x4009922c> -- [1, 2, 3]
Watch out, we have been modified
[5, 6]
#<B:0x4009922c> -- [5, 6]
svg%
I don't see a parent nor a child : I just see an object which modify one
of these instance variable.
Okay, class A defines an instance variable @value and initializes it to
[1,2,3].
attr_reader :value
def selftest
if @value.size == 3
puts "OK"
else
puts "Watch out, we have been modified"
p @value
end
end
end
and tests to see if @value has changed since its initialization.
class B < A
B inherits from A, and therefore inherits the methods initialize and
test, which have not been overridden, and the instance variable @value.
def test1
objs = value
objs << 4
selftest
end
def test2 @value = [5, 6]
selftest
end
end
and adds two new methods, available to objects of class B, but not of
class A.
However, the terms ‘parent’ and ‘child’ refer to classes, whereas @value
is an instance variable. You’re modifying an object of class B, which
contains an attribute @value that B just happens to have inherited from
its superclass, A. The values of instance variables are part of the
object, not the class.
Furthermore, the whole point of inheritance is that you can override
methods defined in the base class, so in that sense, a base class can
and usually does change the bits it inherited from the parent (which is
not the same thing as changing the parent).
However, the terms ‘parent’ and ‘child’ refer to classes, whereas @value
is an instance variable. You’re modifying an object of class B, which
contains an attribute @value that B just happens to have inherited from
its superclass, A. The values of instance variables are part of the
object, not the class.
Yes… I found out it works like that. What I want is know: Is it possible
to make A#value private, so that it appears to be readonly within B ??
···
On Wed, 25 Jun 2003 12:28:29 +0000, Martin DeMello wrote:
No, because an instance variable has no concept of “which class it came
from”.
Suppose I write:
class A
def initialize(x=nil) @var = x if x
end
end
class B < A
def reset(x) @var = x
end
end
Now I do:
a = A.new(99) # @value=99
b = B.new
b.reset(99) # @value=99
Notice that @value was set from class A in the first example, and from class
B in the second example. The compiler cannot know that @value “belongs” to
class A or class B, because it doesn’t; it belongs to the object (a or b),
not the class.
I think you will find Ruby encourages a different type of programming than
you may be used to. Instead of expending effort putting up firewalls so that
other pieces of code cannot do what they want, you expend effort writing
programs that work instead
The whole ‘subclassing’ model is pretty flawed anyway. I find now that I
almost never use it; rather, I use the delegator pattern (an object of class
X contains an instance of class Y, rather than is subclassed from it).
Problems seem to decompose much more naturally this way, and it’s much
easier to make use of the individual parts and bolt them together in
different ways.
Regards,
Brian.
···
On Wed, Jun 25, 2003 at 08:38:57PM +0900, Simon Strandgaard wrote:
However, the terms ‘parent’ and ‘child’ refer to classes, whereas @value
is an instance variable. You’re modifying an object of class B, which
contains an attribute @value that B just happens to have inherited from
its superclass, A. The values of instance variables are part of the
object, not the class.
Yes… I found out it works like that. What I want is know: Is it possible
to make A#value private, so that it appears to be readonly within B ??
Me don’t understand. If I add debug-print’s to selftest then I
see that
the parent has changed ?? Can you enligthen me ?
[snip]
I don’t see a parent nor a child : I just see an object which modify
one
of these instance variable.
OK. I think im to c++bias’ed (assuming too much).
No, it’s just an OO thingy. Even in C++ there is no parent or child: just
a single instance with several instance variables. The only difference
between C++ and Ruby with respect to this issue are the scoping and
visibility rules. But the basic principle is the same.
Is it possible to make the variable readonly to the child ?
Not without complicated tricks. Typically you want to avoid outside access
to instance variables but not access through child class methods.
I have seen that example before…But I cannot use it.
In C++ private members is inacessable to derivatived classes.
I want the same, but with a extra twist.
Instances declared in the parent should be readonly(frozen) to the
subclass.
So that the subclass (B):
is allowed to read the variable.
is disallowed to write to the variable (raise exception).
···
On Wed, 25 Jun 2003 21:41:17 +0900, ts wrote:
Is it possible to make the variable readonly to the child ?
I still don’t know what is a child, and what you want to do
Offhand, I’d say it wasn’t even hackable in pure ruby, because
variables are not first-class values in ruby
instance variables are always in scope in a class, so you can’t do
something in a setter method, e.g., that prevents access.
assigning to a variable is simply syntax, so for instance, you could
trap a.a = 1, but not @a = 1.
even if you were willing to make @a an object, and access it via @a.value and @a.value=, the message passed to @a wouldn’t get the value
of ‘self’ from the object that sent the message, i.e. you can’t use
accessor methods to enforce scoping.
This is the closest I can think of at the moment:
def a=(n)
p "In class #{self.class}"
if self.class == A @a = n
else
raise "protected!"
end
end
and all it’ll protect you from is
class B < A; end
b = B.new
b.a #returns the value
b.a=1 #raises ‘protected’
In C++ private members is inacessable to derivatived classes.
I want the same, but with a extra twist.
Instances declared in the parent should be readonly(frozen) to the
subclass.
So that the subclass (B):
is allowed to read the variable.
is disallowed to write to the variable (raise exception).
In C++ private members is inacessable to derivatived classes.
I want the same, but with a extra twist.
Instances declared in the parent should be readonly(frozen) to the
subclass.
So that the subclass (B):
is allowed to read the variable.
is disallowed to write to the variable (raise exception).
Offhand, I’d say it wasn’t even hackable in pure ruby, because