Skip to main content
Jump to: navigation, search

Difference between revisions of "OTJ Primer/Role Containment"

(New page: Role containment is a superior version of class containment in Java. Image:RoleContainment.png Just like role playing, role containment is a relation that...)
 
Line 52: Line 52:
 
</source>
 
</source>
 
With this design we clearly distinguish which properties of a role should be visible from the outside.
 
With this design we clearly distinguish which properties of a role should be visible from the outside.
 +
 +
:''<u>Further reading:</u> a severe restriction of inner classes in Java is the direct coupling of logical nesting and physical file structure. This simply doesn't scale for many tens of inner classes and perhaps nesting at more than two levels. To overcome these issues, OT/J supports an alternative file structure using "team packages" and "role files" ({{otjld|1|.2.5}}).''
  
 
==Data Flows==
 
==Data Flows==
Given that roles are normally protected, i.e., confined within their enclosing team instance, the question arises how information can be exchanged between a team and the outside. This is when the '''[[OTJ_Primer/Role_Playing|playedBy]]''' relation come back into focus.
+
Given that roles are normally protected, i.e., confined within their enclosing team instance, the question arises how information can be exchanged between a team and the outside. This is when the '''[[OTJ_Primer/Role_Playing|playedBy]]''' relation comes back into focus.
 +
 
 +
[[Image:TeamRoleBase.png]]
  
 
===Role-to-base===
 
===Role-to-base===

Revision as of 11:57, 8 July 2010

Role containment is a superior version of class containment in Java.

RoleContainment.png

Just like role playing, role containment is a relation that is declared between classes but essentially affects run time instances:

public team class Company {
    public class Employee { /* role details omitted */ }
    /* team details omitted */
}

Just as with inner classes in Java, such nesting of classes implies a connection between instances:

  • creating an inner instance requires an outer instance to be in scope, either:
    • within a non-static method of Company: writing new Employee() implicitly connects the new inner instance to the current this of type Company.
    • creating a role outside its team (which is actually discouraged in OT/J) can either use
      • the Java syntax thatCompany.new Employee(), or
      • the OT/J syntax new Employee<@thatCompany>()
The two forms are semantically equivalent and reasons for the second form a outside the scope of this text.
  • as a result of the rules for role instantiation, each role instance has an immutable non-null reference to its enclosing team, accessible using the Java syntax Company.this within the role

Role Visibility

So far, roles behave just like inner classes. Next the developer of a role class must decide whether a role should be public or protected (there are no other choices).

Protected roles

It should be the normal case that roles are protected. This gives a team full control over the role. Only the team and sibling roles will be able to communicate with a protected role. To be more precise, it is the team instance that owns a role. So, the following method call, even when it occurs in a method of Company, is illegal if Employee is protected:

Company otherCompany = ...
Employee leader = otherCompany.getTeamLeader();

The compiler will complain about an illegal attempt to "externalize" (OTJLD §1.2.2) a protected role. Specifically, if getTeamLeader() returns an object of type Employee, this method can only be called with this as the receiver, ensuring that nobody outside the owning team instance will ever get a reference to the team's role instances.

Further reading: with just the rules above an Employee instance could still leak out off the team by upcasting the role to some non-role supertype, ultimately to Object. The new top-level superclass Team.Confined provides means to prohibit upcasting even to Object (see OTJLD §7.2).

Public roles

By declaring a role public a pre-condition is met that instance of this type can actually be seen outside the enclosing team instance. Howeverm the type rules for "externalized roles" (OTJLD §1.2.2) still distinguish, e.g., the types Employee<@microsoft> and Employee<@canonical>, and consider both types as incompatible (in this example, microsoft and canonical are assumed to be instances of Company).

The usage of "externalized roles" using type syntax like Employee<@canonical> is actually not recommended for the novice. If, despite all recommendations, a role shall still be accessed from the outside of the team, it might be a good idea to simply declare an interface outside the team, let the role implement the interface and access the role via its non-role interface:

public interface IEmployee { String getName(); }
public team class Company {
    protected class Employee implements IEmployee {
        getName -> getName; // declaration for this method is inherited from IEmployee
    }
    private Employee leader;
    public IEmployee getTeamLeader() {
        return this.leader;
    }
}
...
    Company thatCompany = ...
    IEmployee leader = thatCompany.getTeamLeader();

With this design we clearly distinguish which properties of a role should be visible from the outside.

Further reading: a severe restriction of inner classes in Java is the direct coupling of logical nesting and physical file structure. This simply doesn't scale for many tens of inner classes and perhaps nesting at more than two levels. To overcome these issues, OT/J supports an alternative file structure using "team packages" and "role files" (OTJLD §1.2.5).

Data Flows

Given that roles are normally protected, i.e., confined within their enclosing team instance, the question arises how information can be exchanged between a team and the outside. This is when the playedBy relation comes back into focus.

TeamRoleBase.png

Role-to-base

Given that a role and its base are two sides of the same coin, the compiler can easily help with a little translation:

public team class Company {
    protected class Employee playedBy Person {}
    private Employee leader;
    public Person getTeamLeader() {
        return this.leader;
    }
}
...
    Company thatCompany = ...
    Person leader = thatCompany.getTeamLeader();

Only one thing has changed wrt the previous example: getTeamLader() now declares to return a Person. Well, the actual returned value this.leader isn't exactly of type Person, and Employee actually is not a subtype of Person, but the compiler can easily extract the underlying Person from any Employee instance and return this instead of the role.

This translation from a role instance to its base instance is called lowering. It is applied implicitly in all program locations where a role instance is provided which does not conform to the required type of the expression, but where extracting the base instance actually yields a value of the required type.

Base-to-role

The opposite direction is a bit more challenging: assume you have a base instance of type Person and want to use this as the handle refering to a role. The team could have multiple role types for the same base type, like when we introduce a second role Client playedBy Person to our running example.

In order to help the compiler here, the developer can use the following kind of method signature:

  protected class Employee playedBy Person {
    protected void acceptRequest(String request) { /* */ }
  }
  public void speakToClark(Person as Employee someone, String request) {
      someone.acceptRequest(request);
  }

Here the argument someone has a two-sided type: the client has to provide an instance of type Person, but inside the method we want to speak to the Employee role of the person, since method acceptRequest is only defined in Employee. The translation from a base (Person) to a matching role (Employee) is called lifting, which has the following properties:

  • lifting requires three inputs
    • a base instance (explicitly provided by the client)
    • a team instance (implicit in non-static team methods)
    • a role type (here declared after as)
  • if all three inputs are identical across multiple invocations, lifting will always find the same role instance.
  • if lifting finds no existing role matching all three inputs a new role will be created on-demand and internally cached for later use.
  • lifting does not interfere with garbage collection.

Translations in method bindings

The most convenient way to use lifting and lowering is in method bindings.

public class Person {
    public Person getSpouse() { /* */ }
}
public class Company {
    protected class EmployeeSpouse playedBy Person { }
    protected class Employee playedBy Person {
        EmployeeSpouse getSpouse() -> Person getSpouse();
    }
}

Here the callout binding interprets the return value from Person.getSpouse() as an EmployeeSpouse, performing the necessary lifting fully automatic behind the scenes.

This is a summary of how lifting/lowering are supported in method bindings:

kind return argument
callout lifting lowering
callin lowering lifting

Team Activation

...

Back to the top