OTPattern/TeamComponent

From Eclipsepedia

< OTPattern
Revision as of 10:51, 12 June 2010 by Stephan.cs.tu-berlin.de (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Intent

A team should fully encapsulate its roles and provide an API for clients.

Motivation

The benefits of encapsulation are well-known. Using protected roles (OTJLD §1.2.3) in OT/J, it is easy to completely hide roles from access by clients external to the enclosing team.

Difficulties might arise, though, when defining the public interface of the team, because now data flows need to be defined without mentioning any role types. On the other hand, clients may need to refer somehow to individual role instances, when invoking methods of the team component.

A conventional solution might use surrogate identifiers for the communication between the component and its clients. All interface methods would then be required to translate surrogates to roles and back to surrogates. Also, when using String identifiers as surrogates no type safety is given, i.e., a surrogate being passed into an interface method may refer to an object of the wrong type.

Structure

As mentioned all roles of the team are declared as protected to ensure encapsulation. Next, roles are bound to globally visible classes using playedBy.

Public methods of the team component are defined with signatures that exploit lifting (OTJLD §2.3) and lowering (OTJLD §2.2) as the desired translations.

public class BaseA { /* details omitted */ }
 
public team class TeamComponent {
    protected class RoleA playedBy BaseA { /* details omitted */ }
 
    /** First API method with a data flow out off the team. */
    public BaseA outputMethod() {
        RoleA a= ... // find a suitable role instance for this request
        return a;    // return `a` as a BaseA (by means of lowering)
    }
 
    /** Second API method with a data flow into the team. */
    public void inputMethod(BaseA as RoleA a) { 
        // perform operations with `a` as a RoleA (thanks to lifting)
    }
}
 
public class Client {
    void clientMethod(TeamComponent t1) {
        BaseA a1= t1.outputMethod(); // retrieve an instance from the team
        // more operations here
        t1.inputMethod(a1);          // pass the stored reference back to the team
    }
}

Participants

Team TeamComponent contains one or more roles classes RoleA etc, and protects its instances against external access. Through the playedBy declaration RoleA and BaseA form one conceptual entity. Of this conceptual entity the BaseA part is publically visible.

Collaborations

When the Client invokes an outputMethod() the TeamComponent may return a role instance, but the role RoleA is first lowered to BaseA before being passed to the Client. Lowering happens transparently in order to resolve the typing conflict between a provided RoleA instance and the required return type BaseA.

Conversely, when the Client invokes an inputMethod() the public signature requires an argument of type BaseA, which can easily be provided by the Client. The team, however, needs a RoleA for its operation. This translation, lifting, is explicitly declared using the as keyword (OTJLD §2.3.2). Inside the method the argument indeed has the type RoleA.

Example

Consider a component for managing orders, OrderSystem. Internally the component handles OrderItems and Customers. Outside the team, a general warehouse module provides a class StockItem and a class Person is globally available. To create an order, a team method order() is called, passing the Person placing the order and a number of StockItems to be ordered. Internally, the Person becomes a Customer role and the StockItem becomes an OrderItem role.

Availability of specific items of a pending order can be queried using the same base objects (Person, StockItem) into the team. The lifting translation retrieves the same Customer and StockItem roles that were used when creating the order, so any properties of these roles (like the number of ordered pieces) can easily be retrieved.

public class StockItem { }
public class Person {}
 
public team class OrderSystem {
    protected class OrderItem playedBy StockItem {
        int numberOrdered;
        boolean isAvailable;
    }
    protected class Order { // unbound, fully internal class
        List<OrderItem> items;
        Customer customer;
        Order(OrderItem[] items) {
            this.items= new LinkedList(items);
        }
    }
    protected class Customer playedBy Person {
        Order pendingOrder;
    }
    /** Create a new order
     * @param cust   the person placing the order
     * @param items  array of stock items to be ordered.
     */
    public void createOrder(Person as Customer cust, StockItem as OrderItem items[])  {
        cust.pendingOrder= new Order(items);
    }
    /** Answer whether the given item is currently available.
     * @param item   the stock item to ask about.
     */
    public boolean isAvailable(StockItem as OrderItem item) {
        return item.isAvailable;
    }
    /** Get all items for which a given customer has an order pending.
     * @param cust   the person asking
     * @return       an array of stock items.
     */
    public StockItem[] getPendingItems(Person as Customer cust) {
        List<OrderItem> items= cust.pendingOrder.items;
        return items.toArray(new OrderItem[items.size()]); // return applies array lowering
    }
}

Consequences

In order to fully support lifting and lowering even for collections, arrays can be used because lifting and lowering transparently work for arrays, too (OTJLD §2.2.e, OTJLD §2.3.d). Automatic translation of generic collections is unfortunately not possible.

Known uses

Two student assignments were based on this pattern: OrderSystem (from which the above example is an extract) and SportCourseManagement.