Alternatives To Hierarchical Encapsulation

Reading EncapsulationIsHierarchical got me thinking about standard assumptions of many mainstream OO languages.

There is a concept of a class. Classes store data, and implement methods. Some languages allow that data (and/or the functions) to be private

There is a concept of an interface. Interfaces group functions. Classes may implement the functions defined in the interface.

In explicit typing, clients can refer either to classes or interfaces. Because a client can refer to classes, we need a concept of "private:" to block access to some parts of the class.

Now imagine a different model. Imagine clients cannot refer to a class. Ever. They can't even inherit from a class! All access must be through interfaces. Even creation has to go through an interface (a class can be selected via traits). However, clients can have a reference to a set of interfaces: a set of interfaces might be declared as a "type". Implementation-inheritance would be via delegation.

In order for anything to get done, each class would need to implement many interfaces. One interface might be named "Foo"; another: "PrivateFoo?". The language would provide casting facilities, so I could attempt to cast a "Foo" to a "PrivateFoo?": The implementing class gets to decide if the cast is allowed (possibly at compile/load time).

Why would anyone want to create a language like this? Well, for a start it makes clear the distinction between "types", "classes" and "interfaces". It does not constrain us to think of a class being its own type. We are no longer constrained to thinking in terms of hierarchical encapsulation. Concepts of "private", "protected", "public" are not part of the language, but are easily implemented within it. Issues of "friendship" are no longer a language issue.

The "EncapsulationIsHierarchical" page discusses the String class. So how does this suggestion change it? Here is the class from that page:

  class String {
    struct Storage {
      char  *string;
      int    referenceCount;
    };
    ...
  };
a client would presumably say

  String foo = String("hello, world").
Creation is the most difficult part of the problem. The issue is that the client has to request the features that are required: and so is implicitly (or explicitly) asking for a specific implementation. In the above case, the client is asking for a mutable, reference counted, single-array based thing that provides certain other operations (e.g. substr, compare).

So the client is saying: create me a something with the following characteristics:

  foo = global_classes.find(
     interfaces => {substr, compare, append, assign, new},
     traits => {share=>true, storage=>"simple_array"}
  ).new("hello, world");
Alternatively

  String = global_classes.typedef(
     interfaces => {substr, compare, append, assign, new},
     default_traits => {share=>true, storage=>"simple_array"}
  );

foo = String.new("hello, world");
The language runtime chooses a class to create based on matching the required traits and interfaces. If it's ambiguous, the client must supply more information to disambiguate. I'm sure my syntax is a bit off, but the basic concept is visible: to strongly separate classes from interfaces/types.

Somehow I doubt the GranularityOfVariation will be at the class level. In practice it's often pretty damned fine, and is one of the trickiest problems in biz-apps. IF-statements are ugly, but at least you can stick them just about anywhere without much code re-arrangement. (Although a pattern of similar IF's may suggest a different approach.) --top

[Variation is indeed not typically at the class level. In business applications, the main point of variation tends to be the underlying database model and business rules. Generic 'Form', 'Report', 'ComboBox', 'DatabaseConnection' or 'ResultSet' classes are not likely to change much, if at all.]

[A (say) 'Customer' table in the database may change frequently, but it's only seen in the business application's front-end (typically written in an OO language) as a collection of generic, dynamic rows -- with column values retrieved via dynamic lookup from a container. The row container doesn't change, though its run-time contents may, and of course the code that uses its values may change. But that would be true no matter what client-side language or paradigm is used.]

[Polymorphism, which I presume you mean to counter with IF-statements, is likely to be prevalent in the generic class hierarchy of generic computational machinery, i.e., in the Form, Report, etc., classes. IF-statements are still commonly and appropriately found in the implementation of business rules, even if (say) the generic BusinessRule class employs polymorphism as part of its computational mechanisms.]

[Be sure not to conflate the systems-level machinery (primarily implemented via classes) to implement a generic application environment with the problem-specific machinery that implements a given database and associated business rules.]

If we take the OopNotForDomainModeling viewpoint, then I generally agree with you. However, what would your suggested technique provide to typical computation-space objects beyond a typical "attribute switch" approach?

  x = new object();
  x.featureA = off;
  x.featureB = on;
  x.featureC_algorithm = new algFoo();
  x.etc...
[Not following your question, sorry.]


DirkRiehle has described this well in a pattern called ProductTrader? .


CategoryInterface CategoryHierarchy


EditText of this page (last edited March 18, 2010) or FindPage with title or text search