Recipe20.2.Overriding the Class Instantiated on a Call to a Constructor


Recipe 20.2. Overriding the Class Instantiated on a Call to a Constructor

Problem

You want to be able to control the actual classes that are instantiated on a constructor call.

Solution

Use the Cuckoo's Egg aspect-oriented design pattern to create an aspect that modularizes the override of a constructor call to be able to vary the implementation returned at runtime. Use the call(Signature) pointcut to capture calls to a classes constructor and then use around( ) advice to return a different object.

Discussion

It can be useful to be able to migrate to different implementations of an interface or specialization of a base class without affecting the code that uses that interface or base class. Traditionally, deciding the class selection is performed at object construction time, as shown in Example 20-4.

Example 20-4. Hardcoding the selection of the MyClass implementation of MyInterface
public static void traditionalObjectOrientedImplementationSelection( ) {    MyInterface myObject = new MyClass( ); // Specifies the MyClass                                            // implementation of the                                           // MyInterface interface        System.out.println(myObject);    myObject.foo( ); // Calls the MyClass implementation of the foo( ) method }

This code produces the following output:

com.oreilly.aspectjcookbook.MyClass@cf8583 foo( ) called on MyClass

To change the implementation of MyInterface, the code would need to be changed to:

public static void traditionalObjectOrientedImplementationSelection( ) {    MyInterface myObject = new AnotherClass( );  // Specifies the                                                 // AnotherClass                                                 // implementation  of the                                                // MyInterface interface        System.out.println(myObject);    myObject.foo( ); // Calls the AnotherClass                      // implementation of the foo( ) method }

This will produce the following output:

com.oreilly.aspectjcookbook.AnotherClass@dbe178 foo( ) called on AnotherClass

The client code doesn't care what the implementation of the MyInterface interface is when making calls to the foo( ) method since, in Java, the method is called dynamically at runtime based on the object type. The disadvantage of this approach is that the implementation class (MyClass or AnotherClass) is exposed when it is instantiated. Anywhere this interface is used, at least one line of implementation-specific code can occur and if you wanted to change the implementation, this would result in an error-prone ripple of changes across your application to migrate to the new class.

A common way around this problem is to use a factory method or an abstract factory design pattern. Aspect-oriented techniques offer advantages to both of these approaches, as shown in Recipes Recipe 13.3 and Recipe 13.4 respectively. However, things can be made simpler and neater by using AspectJ's call(Signature) pointcut and around( ) advice, as shown in Example 20-5.

Example 20-5. Using an aspect to override instantiation of a class
package com.oreilly.aspectjcookbook; public aspect ControlClassSelectionAspect  {    public pointcut myClassConstructor( ) : call(MyClass.new( ));        Object around( ) : myClassConstructor( )    {       return new AnotherClass( );    } }

The ControlClassSelectionAspect declares the myClassConstructor() pointcut that intercepts calls to instantiate a MyClass object. The corresponding around() advice then returns a new instance of the overriding AnotherClass class.

The class that is instantiated and returned from your around( ) advice must be a subtype of the type expected on the overridden constructor call. For instance, in Example 20-5, AnotherClass must extend MyClass as the constructor call being overridden specified that it is creating a new MyClass( ). If the around( ) advice returned an object that was not a subtype of MyClass, you'd get ClassCastException errors at runtime wherever the around( ) advice was applied.


Using this technique, the code in Example 20-4 does not need to be changed at all to use the AnotherClass implementation of the MyInterface interface. If the ControlClassSelectionAspect is woven into the application, the AnotherClass implementation automatically overrides the MyClass implementation.

You could refine the myClassConstructor( ) pointcut further so the MyClass constructor could be overridden in certain circumstances. You could, for example, add within(TypePattern) or withincode(Signature) pointcut definitions to declare the overriding should only occur within a designated scope.

You could examine a user-defined runtime parameter to vary the implementation that is instantiated within the around() advice:

Object around( ) : myClassConstructor( ) &&                    if (System.getProperty("select_class").equals("AnotherClass")) {    return new AnotherClass( ); }

This variation would allow you to change the class instantiated at runtime by changing the value of your application's select_class runtime parameter without being forced to recompile your application.

Looking ahead, Recipe 23.1 shows a typical use of the Cuckoo's Egg aspect-oriented design pattern. This pattern provides a common solution to the problem of overriding a call to a constructor to return a different object than expected.


See Also

The call(Signature) pointcut when used to capture calls on constructors is described in Recipe 7.1; the around( ) form of advice is discussed in Recipe 13.4; the within(TypePattern) and withincode(Signature) pointcuts are described in Recipes Recipe 9.1 and Recipe 9.3 respectively; the Cuckoo's Egg aspect-oriented design pattern is discussed in Recipe 23.1.



AspectJ Cookbook
Aspectj Cookbook
ISBN: 0596006543
EAN: 2147483647
Year: 2006
Pages: 203
Authors: Russ Miles

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net