Copyright 2001 Nikolas S. Boyd. All rights reserved.

Nik Boyd

Resource Manager Object Behavioral

Intent

Factor out resource lifecycle management into a separate class and focus client code on resource usage.

Motivation

Resource utilization usually has a consistent, often symmetrical, lifecycle. The lifecycle template for using a resource often includes the following steps:

  1. Obtain the resource.
  2. Prepare the resource for use (if needed).
  3. Use the resource.
  4. Restore the resource to its original state (if needed).
  5. Release the resource.

Steps 2 and 4 may or may not be present in a particular resource utilization lifecycle. However, whether they are present or not, the correct usage of a resource most often entails special code for ensuring that the lifecycle is correctly maintained. Some programming languages even provide special syntax for ensuring that a resource gets released after its use. For example, the JavaTM programming language includes the following special statement syntax:

try {
// ...
} catch( Exception e ) {
// ...
} finally {
// ...
}

Given such a code template, the typical resource utilization lifecycle will usually be coded in the following manner:

try {
// obtain the resource
// prepare the resource (if needed)
// use the resource
} catch( Exception e ) {
// handle the exception as appropriate
} finally {
// restore the resource (if needed)
// release the resource
}

To ensure the proper maintenance of the lifecycle, this code template will often be repeated in each place where a resource is used. Such repetition can be tedious and can often lead to coding errors, especially if such code segments are copied, pasted, and then revised. As the resource utilization lifecycle becomes more complex and as the repitition of the code template becomes more pervasive throughout a program, the cost of repairing and maintaining the repetitious code increases geometrically.

As a typical example of this problem, consider database connection management in JavaTM. When performing database operations using JDBCTM, the correct usage of a database connection invariably involves a try-catch-finally construction to ensure that the database connection is released after its usage, even after an exception occurs. However, instead of repeating the connection management code template pervasively throughout a JavaTM program, it is possible to factor the template out into a ConnectionManager class. The ConnectionManager is responsible for ensuring the correct lifecycle for a database connection, especially obtaining the connection and releasing the connection, but also handling any exceptions that arise during the use of the connection.

The ConnectionManager obtains a Connection from a connection resource registry and provides the Connection to an instance of a ConnectionUsage class. When using JDBCTM 1.0, the resource registry will be the DriverManager. When using JDBCTM 2.0, the resource registry should be a DataSource.

The ConnectionUsage class is a base class that is intended to be instantiated as an anonymous inner class by the Client. The Client overrides the default use( ) method to operate on the Connection supplied by the ConnectionManager.

Ultimately, the Client determines how to use the Connection, i.e., what operations are performed on the Connection. However, because the connection management code has been factored out into the ConnectionManager, the client can focus solely on the database operations. Repetitions of the lifecycle management code are prevented. Overall, the resulting database access layer becomes simpler and easier to maintain.

Applicability

Use the Resource Manager pattern when

Structure

Participants

Collaborations

Consequences

The Resource Manager pattern has the following consequences:

  1. Improved maintainability. The code for managing the overall resource usage lifecycle is separated out into a single place. This dramatically reduces the number of places that need to be changed if the lifecycle needs to change.

  2. Uniform exception handling. Resource usage exceptions can be handled in a uniform manner. If special cases are needed, the framework can be extended to support plug-in exception handlers. The mechanism for supporting such a plug-ins will be similar to that which supports resource usage.

  3. Simpler code. The code for performing operations on a resource need not be concerned with resource management. Such client code can focus solely on the correct usage of the resource, especially if transactional semantics must be maintained.

Implementation

  1. Supportive Programming Environments. The Resource Manager pattern works best with a programming language that has a well defined exception handling mechanism. Fortunately, most modern programming languages include some kind of support for exception handling. Both JavaTM and Smalltalk provide such mechanisms. The JavaTM mechanism was outlined above in the Motivation section. Smalltalk provides a similar mechanism for ensuring that resources can be managed correctly. Any freestanding Smalltalk block can be guarded by an ensure: message with another block that will always be executed whether an exception occurs or not.

    [ "..." ] ensure: [ "..." ]

    The C++ try-catch construction does not include a finally clause. So, implementing the Resource Manager pattern in C++ requires some additional work. A couple of possible solutions are:

    1. Place the allocated resource in an instance of a smart pointer class declared as a local stack variable in the scope of the enclosing method. That way, when the stack unwinds, the smart pointer class will have an opportunity to restore and release the resource even if a resource usage exception occurs.

    2. Alternatively, the class that serves as a ResourceManager can catch all exceptions. Then, the ResourceManager can restore and release the allocated Resource, handle any exceptions it can, and rethrow any exceptions it can't handle.

  2. Inner Classes vs. Nested Classes. JavaTM anonymous inner classes are especially convenient for defining ResourceUsage behaviors. The insertAccount() and queryAccount() methods in the ConnectionExample class serve as representative examples.

    Note that the arguments for these methods are both declared as final. Each of these example methods needs to reference the supplied method argument within the use( ) method in the anonymous inner class. Declaring the method arguments as final allows the anonymous inner class methods to reference and access the arguments declared in the outer method scopes. This rule also applies to variables declared in an outer method scope (i.e., they must be declared final if the inner method scope needs to access them).

    In some ways, the Smalltalk language makes defining the Resource Manager pattern even more convenient than JavaTM. Smalltalk allows the elimination of an explicit ResourceUsage class. Instead, the ResourceManager accessUsing: method can simply take a single argument block as a parameter.

    resourceManager accessUsing: [ :resource | "..." ]

    C++ complicates the implementation of the Resource Manager pattern because it does not have any kind of direct closure mechanism like those found in Smalltalk and JavaTM. However, it does support nested classes, which can be used to define ConcreteUsage classes close by to the location of their instantiation.

  3. Thread-Safety. In the ConnectionExample class, it's important to note that the anonymous inner ConnectionUsage class is instantiated each time the insertAccount() method is invoked, i.e., a new ConnectionUsage gets created each time. In addition to conforming to the syntax for instantiating an anonymous inner class, this ensures that the each usage is thread-safe, i.e., each thread has its own usage instance.

    The instantiation of each ResourceUsage will usually have a negligible impact on performance. However, if the kind of Resource being managed does not represent an external resource with a relatively large performance overhead (such as a database), it may improve performance if the usage can be pre-allocated, cached, and reused during each access. However, care must then be taken to ensure that each usage is thread-safe.

  4. Access Authentication. Some managed resources require authenticated access, e.g., database connections. It is relatively simple matter to extend the interface of a ResourceManager class to support authenticated access, either by supplying the authentication credentials during the instantiation of the ResourceManager, or by supplying the credentials as an additional parameter in the accessUsing() method.

  5. Access Variations. Depending on the context in which it is used, a ResourceManager class may need to provide multiple variations of the accessUsing() method. For example, if there are various ways to prepare a connection prior to its use, you may want to accomodate those variations with extra parameters. Also, if the programming language does not provide convenient access to outer scopes, you may need to pass some additional argument(s) into the ResourceManager, which may in turn need to pass the argument(s) in to the ResourceUsage class.

Sample Code

Recommended Uses

There are several components of the JavaTM platform for which the Resource Manager pattern may be used:

Related Patterns


JavaTM, JDBCTM and J2EETM are trademarks of Sun Microsystems, Inc.