|Copyright 2003 Nikolas S. Boyd. All rights reserved.||Nik Boyd|
|Promised Operations||Object Behavioral|
Separate and control access to operations promised between close collaborators without exposing those operations to the general public.
Programming languages that support explicit access controls often make operations available based on the relationship a client class has to the server class. The access models supported by programming languages have typically been limited to the following:
These models expose access to a spectrum of narrower or broader client audiences. Given the nature of collaborative designs often developed in object-oriented software, there is clearly a significant omission from these models: promised access, i.e., specific behaviors offered solely to specific kind(s) of collaborators that are unrelated by inheritance. Some may claim that both the friend (C++) and packaged (Java) models offer this kind of access. However, both the friend and packaged access models compromise encapsulation. While the friend and packaged access models may be appropriate for especially intimate relationships between classes, due to their extensive exposure of server internals, their use in object-oriented designs should be eschewed in favor of more controlled interfaces.
The concept of promised access was originally introduced in 1996.1 Promised access differs from both friend and packaged access in that encapsulation is not compromised and only specific behaviors are exposed. Promised access formalizes a notion that has been discussed repeatedly in the literature on object-oriented design. Collaborative classes often promise behaviors to each other within the confines of a subsystem. However, by virtue of the limited access models offered by programming languages, such designs are often forced to expose promised behaviors to a broader range of other clients (often all). Thus, they are no longer merely promised, but also compromised, usually as public operations.
Fortunately, the previously discussed programming language limitations can be overcome with the judicious application of design patterns. For example, consider a kind of Resource with a limited number of available instances. As such, the Resources will likely be maintained in a Registry, which is made available to the clients that want to access the registered Resources. So, the Registry offers methods for enumerating and accessing the registered Resources. However, the StorageManager needs to load the Resources to be registered from a backing store (and possibly save them again sometime later, e.g., during shutdown). So, the Registry must also provide a mechanism for the maintenance (esp. registration) of the Resources. But, it would not be appropriate for all clients to have access to the maintenance behaviors. By combining a variation of the Selective Visitor pattern2 and the Facet pattern,3 the Registry can separate these maintenance behaviors into a Registrar facet that it offers solely to the StorageManager.
Use the Promised Operations pattern when
- publishes some of its more sensitive operations in a separate PromisedFacet.
- only provides access to the PromisedFacet to selected clients.
- provides promised operations on the FacetedObject to some privileged kind(s) of clients.
- uses the PromisedFacet to manipulate the FacetedObject.
The Promised Operations pattern has the following consequences:
Separating promised operations from public operations. Operations promised between close collaborators can be separated from public operations. A server with distinct facets can control access to its promised operations and grant access to those operations based on various criteria, but especially client conformance to some known type.
Separating inspection operations from mutation operations. Object inspection operations are usually public while mutation operations should often be privileged. However, both kinds of operations can be offered through the promised facets of a server. The server can control which kinds of clients have access to its various operations without requiring the clients to be related by inheritance and without the operations being made fully public.
Responsibility-Driven Design.4 The designer starts with a good object-oriented subsystem design whose classes are properly decomposed according to their responsibilities.
Privilege Determination. For each class in the design, the designer determines which operations are privileged and which (if any) are public. Which kind(s) of clients will have access to each server operation needs to be made explicit.
Facet Separation. Those operations promised to a specific kind of client should be collected into a distinct PromisedFacet. Those operations that are private, protected and public should be declared so, as usual.
Client Authentication. Consideration should be given as to whether some PromisedFacet requires client authentication beyond mere conformance to a known type signature. If stronger client qualification is needed, provision should be made in the handshake (where the server accepts an instance of the client) to accept the requisite client authentication credentials for validation.
Client Acceptance. Each kind of client accepted by the server needs its own public accept() method. If the programming language used supports double-dispatch, the accept() protocol can be kept simple. However, the type name of the accepted client can also be included in the method name if needed to distinguish multiple accept() methods.
FacetUsage Abstraction. In one variation of the Promised Operations pattern, the FacetedObject publishes a FacetUsage abstraction, either as an abstract class (C++) or as an interface (Java). This approach provides a convenient way for the FacetedObject to support more controlled but still more public access to some privileged operations.
Resource.java - shows an example of a registered Resource.
Registry.java - shows an example of a Resource Registry with a managed Registrar facet.
StorageManager.java - shows an example usage of the Registrar facet.
Selective Visitor.2 The Promised Operations pattern uses some of the same notions used in the Selective Visitor pattern, especially when a FacetedObject publishes a FacetUsage abstraction which it accepts as the only means of accessing a PromisedFacet.
Extension Object (aka Facet).3 While the Facet pattern exposes some interface(s) for general public usage, the Promised Operations pattern exposes interface(s) only to selected clients, usually on the basis of type conformance. However, with the Promised Operations pattern, even stronger qualifications can be imposed on clients if needed.