The implementation of a particular behaviour (collaboration, use case ...) should be separated from how it is bound to the application.
Implementing a collaboration is easier when doing this in a completely self-contained fashion, so that the implementation can concentrate on only those abstractions that are intrinsically relevant for the collaboration. However, in a real application, which has more use cases to serve, each use case will naturally have (indirect) interdependencies with other use cases or with the core model underlying the application. This tension should be resolved in a way that imposes no compromise on any of these desirable properties:
- The collaboration is easy to read and modify.
- The collaboration can be type-checked independently so that any inconsistencies can be detected as early as possible.
- The collaboration can easily be integrated into an existing application core.
- The collaboration and the rest of the application can each be evolved with minimal risk of invalidating the other parts of the application respectively.
- The collaboration should be re-usable for the integration into similar applications, by some notion of similarity.
The Connector pattern uses a team for implementing the Collaboration in a self-contained way. Part of the behaviour is implemented by a set of contained roles. Some of these roles define the required and provided interfaces of the collaboration (see below).
The actual Connector will be implemented as a specialization of the (abstract) team. This specialization should not contain significant chunks of implementation (except for as little glue code as possibly) but only add bindings to the roles it inherits. These bindings will connect the required and provided interfaces towards the application.
The Collaboration is a team with Abstract Roles. It may coordinate the behaviour of its roles as a fassade and/or mediator. Typically, a Collaboration will be marked abstract to express the intention that sub-classing is required in order to create instances of the Collaboration. Abstractness may actually be necessary if the Collaboration contains instantiation expressions for any of its Abstract Roles.
An Abstract Role implements part of the behaviour while also defining required and/or provided interfaces:
- It may declare abstract methods, i.e., methods whose existence is essential for the collaboration, but where an implementation cannot be given without knowledge about other parts of the application. These methods contribute to the required interface of the collaboration.
- Some methods will be defined that are not invoked from within the collaboration. Some of these may be API of the collaboration, intended for explicit invocation, while other methods are intended to be triggered more implicitly. All these methods define the provided interface of the collaboration.
The Connector is a non-abstract sub-class of the Collaboration (and thus again a team). Its main purpose is to override and enrich the Abstract Roles it inherits.
A Concrete Role declares a playedBy binding to an existing application class, and connects required and provided interfaces using method bindings:
- A callout binding (OTJLD §3) binds an abstract method (required interface) to an existing method of the bound base class.
- A callin binding (OTJLD §4) binds a non-abstract method (provided interface) to a trigger in the bound base class.
Additionally, the Connector may define top-level entries into the Collaboration to be invoked explicitly from some independent/top-level module of the application.
The Connector pattern defines two dimensions of interaction:
- The Abstract Roles interact with each other, perhaps mediated by the enclosing Collaboration.
- Each Concrete Role defines how the role interacts with its underlying base. This role-to-base interaction is completely defined by the role's callout and callin method bindings.
As the final step of integration the Connector has to be instantiated and activated. This can typically be done using extra-program configuration (OTJLD §5.5). Other Connectors may be explicitly known to other parts of the system, that explicitly perform instantiation and activation, but basically all existing styles for instantiation and activation can be applied to Connectors just as to any team.
It is a declared goal of the Connector pattern to establish a two-way independence between application and Collaboration. As a consequence, many changes in either part can be completely absorbed by just changing the connector. This is key for the maintainability of Connector-based architectures.
Given that a Connector contains no implementation (or only small chunks of glue-code), it lends itself to manipulation by tools other than typical source-code editors. The Object Teams Development Environment features a dedicated binding editor that provides an editable table of all class bindigs (playedBy) and method bindings (callout/callin) of a given team. An ideal Connector can be created an modified in a very efficient way using only this binding editor.
The Connector pattern is used, e.g., in
- OTExample Observer
- OTExample FlightBonus
(see Object Teams: Improving Modularity for Crosscutting Collaborations -- (Net.ObjectDays, 2002))
- OTExample OrderSystem