When first coding in Java in 1996, it took me a while to appreciate the newest access modifier – default or package. Then the norm was exposing almost everything as public, including fields, unless there was a good reason to restrict. Joshua Bloch’s Effective Java later reversed that, utilizing access modifiers appropriately to enhance security and scope API’s.
Default vs. public access was particularly important. A component of functionality (i.e., Java package) allowed all the containing classes to access the equivalent of their public methods to accomplish its responsibility solely within the package. However, it could prevent access to all these from the outside world and limit clients strictly to an API. This API was designated by applying the public access modifier to a limited subset of these classes and methods. This provided a number of benefits:
- Changing the underlying implementation without backward compatibility concerns, including consumers coupling their code to an internal implementation detail. Not because consumers don’t use restricted items, but because they are unable to access them
- Explicit API classes and methods which are more easily JavaDoc’d
- Restricting sub-classing only within the package
- Enhanced security
This was a powerful encapsulation capability, and one that seems to be sorely missing in Ruby with its limited three access modifiers: public, protected, and private.
Ruby protected and private modifiers are radically different than Java.
Like Java, a private method can only be invoked within the methods defined in the class it is declared in.
In Ruby, however, private access is less restrictive. The methods can also be accessed by any sub-class, not just the class defined within. In addition, the private methods can be overridden in sub-classes. This means that I cannot enforce the functionality of any method for any class remaining the same; it can always be altered by sub-classes, circumventing my attempts to enforce invariants in my system. In addition, all functionality can be directly accessed (and coupled) by clients via sub-classing. Effectively, all functionality can be directly accessed and altered in Ruby, and there is nothing you can do to prevent it.
Protected methods can invoked and overridden by sub-classes. Like private methods, protected methods can only be invoked within the methods defined in the class it is declared in. However, they can also be invoked on other instances of the same class or sub-class only. Note that public methods fully lend themselves to being overridden as well.
Member Fields Automatically Private
One thing I do like about Ruby is that member fields are automatically private; the accessors are used to expose the equivalent of getters and setters.
One thing I am not wild about is how the methods always default to public.
I am still trying to find something that is equivalent to Java’s final keyword in Ruby. Not only do I not see a way to lock down the behavior of public methods in sub-classes, even private and protected methods can be overridden with no ability to limit this.
Package Level Containment
I do not see a Ruby construct equivalent to the Java package. That is, something that can contain other classes and limit what is exposed outside of it.
I tried an experiment nesting sub-classes within another class, treating that class as a package. By placing some of the sub-classes beneath the private keyword of the containing class, I had hoped that classes could not be directly accessed and instantiated outside of the containing class. Unfortunately, this does not work. Furthermore, I see no mechanism to apply access modifiers to classes.
In my opinion, too much is exposed in Ruby, but perhaps as I learn more about the Ruby and Rails philosophy I will better understand why.
The following is the quick and dirty code I threw together to experiment with the access modifier behavior:
class A def a_public_method_invoking_private a_private_method end def a_public_method_invoking_protected a_protected_method end def a_public_method_invoking_other_protected a_other = A.new a_other.a_protected_method end def a_public_method_invoking_other_private a_other = A.new a_other.a_private_method end def a_public_method puts "a_public_method_invoked" end protected def a_protected_method puts "a_protected_method_invoked" end private def a_private_method puts "a_private_method_invoked" end end class B < A def a_public_method_invoking_parent_private_method puts "surprisingly, your can invoke superclass private methods." a_private_method end def a_public_method_invoking_parent_protected_method puts "You can invoke superclass protected methods." a_protected_method end def a_public_method_invoking_other_protected_in_superclass a_other = A.new a_other.a_protected_method end end class C < A public :a_public_method protected :a_protected_method private :a_private_method def a_public_method super puts "a_public_method_invoked - subclass C - allowed for public" end def a_protected_method super puts "a_protected_method - overridden extra behavior in C" end def a_private_method super puts "a_private_method - overridden extra behavior in C" end end class FooPackage class PublicClass def public_method puts "PublicClass public_method invoked. About to invoke own private method, then public method of private class." private_method p = PrivateClass.new p.public_method end private def private_method puts "PublicClass private_method invoked" end end private class PrivateClass def public_method puts "PrivateClass public_method invoked. About to invoke own private method." private_method end private def private_method puts "PrivateClass private_method invoked" end end end a1 = A.new #a.a_private_method - this is not legal #a1.a_public_method_invoking_private - this is legal #a1.a_public_method_invoking_protected - this is legal #a1.a_public_method_invoking_other_protected - this is legal #a1.a_public_method_invoking_other_private - this is not legal b1 = B.new #b1.a_public_method_invoking_private - this is legal #b1.a_public_method_invoking_parent_private_method - this is legal #b1.a_public_method_invoking_parent_protected_method - this is legal b1.a_public_method_invoking_other_protected_in_superclass c1 = C.new #c1.a_public_method_invoking_protected - this is legal #c1.a_public_method_invoking_private - this is legal #c1.a_public_method - this is legal fp = FooPackage::PublicClass.new #fp.public_method - this is legal and works its way through fprivate = FooPackage::PrivateClass.new # this is legal, despite my hopes it would not be fprivate.public_method