ruby picture

RCR 187: Advanced Method Visibility

Submitted by minam (Fri Jan 09 00:01:05 UTC 2004)

Abstract

Ruby currently only provides 'public', 'protected', and 'private' feature visibility. This is sufficient for simple programs, but there are many times that a more advanced visiblity mechanism is desired. Eiffel, for instance, provides a powerful mechanism that allows for all traditional visibility levels (private, public, protected) as well as more elaborate ones. This RCR proposes an extended syntax for supporting a superset of Ruby's current visibility functionality, inspired by Eiffel's approach to the problem.

Problem

Ruby has no true "private" visibility; descendants of a class still have access to all of their ancestors' internal state. The "private" visibility in Ruby is more properly a "protected" visibility.

Also, there are times when you want a particular set of methods of a class to be private, and yet be accessible to members of a specific class (or set of classes). This is similar to the "friend" accessibility as implemented in C++, but more robust and with finer control over the granularity of the access.

Consider the "State" design pattern, as described in the "Design Patterns" book by Erich Gamma, et. al. You have some class A that has some internal state. At some point in your program, you want to take a snapshot of A's internal state, so that you can later restore A to that state. The State design pattern suggests creating some class B that encapsulates the internal state of A. B would have a "private" interface that A would be able to query, but no other class, allowing A to set and retrieve its internal state via B. Currently, there is no mechanism for doing this easily in Ruby.

Proposal

I propose that Ruby adopt a visibility mechanism similar to that used by Eiffel. The Module class would gain a new method (called feature in this RCR for compatibility with Eiffel, but it may be called anything in practice, perhaps allow or access). This method would act much like the private and public methods act in current versions of Ruby, but would instead accept a list of class names. All subsequently declared methods of the class would then be accessible only to the declaring class and to any descendent class of any of the classes given in the list.

The current private, protected, and public methods would remain, for backwards compatibility, since they are easily converted to the corresponding idioms in the new model.

Here is an example using the proposed syntax:


    
  class Demo
    feature :Demo  # grants visibility only to Demo and its descendents,
                   # i.e., "protected" visibility
      def method1
        ...
      end
    feature :Object # grants visibility to all classes, i.e., "public"
      def method2
        ...
      end
    feature :NilClass # grants visibility to no classes except the declaring
                      # class, i.e. "private" visibility
      def method3
        ...
      end
    feature :OtherClass, :YetAnotherClass
      # "friend" visibility, granting access only to the declaring class and
      # to the classes
      def method4
        ...
      end
  end

    

The usage of :Object and :NilClass to represent "everything" and "nothing" (respectively) correspond to Eiffel's use of Any and None. Perhaps a clearer system would be to have :Any and :None simply be aliases for :Object and :NilClass. Perhaps there's an even clearer way to represent the "None" scenario.

Analysis

Limiting the visibility of features will limit your ability to access certain pieces of code from outside of the declaring class. This is actually a desirable feature. The implementor of a class may declare many methods that he does not intend anyone else to call. Such functions are of use only internally, and as such their signatures, names, and return values are all liable to change. In order to prevent curious clients from accessing those methods (and potentially causing their code to break when a new version of the class is released), the implementor can declare them to be private (which is currently possible in Ruby).

It is also desirable from a design point-of-view. The whole point of the "encapsulation" aspect of object-oriented design and programming is to hide certain parts of a class from outside clients (and sometimes even from descendents of the class itself). By guaranteeing that clients cannot access a given method (or set of methods), you can more easily guarantee the consistent behavior of your class. Also, private, internal methods can forego parameter checking and other "sanity checks", since the implementor does not need to worry about a client passing incorrect data to them.

Adding this feature does not mean that every implementor of a class must use it. By default, all methods of a class will be public, just as they currently are in Ruby. But it does give the ability to those implementors that want it to use a more robust encapsulation mechanism.

Ruby already supports feature visibility, via the "public" and "private" methods. By increasing the power of this feature, more consistent programs may be written, without sacrificing the current Ruby feature visibility mechanism.

Implementation

There is a proof-of-concept implementation of this in the RAA, called Access+ (>). It approximates this behavior using standard Ruby, setting a trace function to keep track of who the current caller is.

This proof-of-concept, however, is not sufficent, since it is both easily circumvented (all it does is rename the original method and make a "wrapper" method that enforces the visibility) and fragile (it uses the set_trace method to detect when the calling method changes). The language itself needs to know about and be able to enforce the visibility of various method features.

ruby picture
Comments Current voting

When I was in school, I read Meyer's "Object-oriented Software Construction", and was influenced a lot. And this particular feature (selective export) is the feature I felt *agaist* most, after the research.

I mean, the basic concept of selective export is good, but the particular design in Eiffel seems ugly, because its against the policy of extensibility: the service provider should not restrict its clients, otherwise it would restrict the future use of the service. So, I designed the selective exporting feature in publish-and-subscribe model instead in the past.

Let me explain.

If the provider specifies classes for features to export, classes not known to the provider will never be able to access the "private" features. The workaround would be exporting features to (possibly) empty classes, and clients inherit these empty classes to "buy" features.

But why not be more direct? So I defined set of features, which I named "profile", and clients use profiles to gain visibility to the class.

  class Demo {
    method_1 {
      ...;
    }
    method_2 {
      ...;
    }
    profile DemoProfile1 {
      method_1;
    }
    profile DemoProfile2 {
      method_1;
      method_2;
    }
  }
  class Client {
    first(DemoProfile1 demo) {
      demo.method_1;  # valid
      demo.method_2;  # invalid
    }
    second(DemoProfile2 demo) {
      demo.method_1;  # valid
      demo.method_2;  # valid
    }
  }
  d = Demo.new
  d.method_1          # invalid; because default visibility is private
  Client.new.first(d) # DemoProfile1 = Demo assignment is valid

I designed a statically typed object-oriented language in my graduation thesis, which had this selective export (the above example in that language). Although it has not been published to anybody, I still think my design is superior than Meyer's in this aspect.

Note that the design is based on static typing, so that it cannot be directly applied to Ruby.

I am not sure whether Ruby needs this level of visibility control, but if it does, it should be much better than Eiffel's selective export.

- matz.


Matz: thanks for your excellent comment. It gave me a lot to consider, and after a lot of thought I can see that your approach is much more flexible than Eiffel's. To that end, I am going to withdraw this RCR and submit another one, with a proposed implementation for your publish/subscribe approach to selective export.

Thanks for helping me to "see the light." :)

- Jamis


Strongly opposed 0
Opposed 2
Neutral 0
In favor 1
Strongly advocate 2
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .