Home > Articles > Programming > Java

📄 Contents

  1. Design Patterns in General / The Command Pattern and Java 8
  2. Hiding Complexity with the Adaptor Pattern / Installing Java 8
  • Print
  • + Share This
Like this article? We recommend

Hiding Complexity with the Adaptor Pattern

Returning to the device management domain introduced above, suppose I want to model another set of behaviors and attributes. In this case, I want to model accessing and modifying some device attributes. However, my requirement is to decouple this code as far as possible from any low-level device code. Sounds complicated, but again patterns come to the rescue!

Listing 6 illustrates an interface for interacting with a low-level device.

Listing 6: Low-level device access interface

public interface DeviceAccess {
    public String getdeviceName();
    public void setdeviceName(String deviceName);
    public String getdeviceSoftwareVersion();
    public void backupDeviceConfig();
}

Such a device might be a network router or a switch. Typically, when the interface methods in Listing 6 are implemented, the actual device interaction will use complex device-specific technology such as SNMP and even piped command lines. It's a good design principle to decouple your application code as far as possible from such low-level code. The Adaptor pattern helps to decouple these codebases.

Listing 7 illustrates a fictitious implementation of the interface.

Listing 7: Low-level device access implementation

public class DeviceAccessImpl implements DeviceAccess {

    private String deviceName = null;

    @Override
    public String getdeviceName() {
        // Make calls into SNMP and/or other domain languages
        if (deviceName == null) {
            return "Super New Device";
        } else {
            return deviceName;
        }
    }

    @Override
    public void setdeviceName(String deviceName) {
        // Make calls into SNMP etc. to set the device name
        this.deviceName = deviceName;
    }

    @Override
    public String getdeviceSoftwareVersion() {
        // Make calls into SNMP and/or other domain languages
        return "Super New Device Version 1.0.1";
    }

    @Override
    public void backupDeviceConfig() {
        System.out.println("Now starting device backup procedure");
    }
}

Notice the implementation code for the getdeviceName() method:

@Override
public String getdeviceName() {
    // Make calls into SNMP and/or other domain languages
    if (deviceName == null) {
        return "Super New Device";
    } else {
        return deviceName;
    }
}

Typically, this method will do a lot of complicated domain-specific code, calling SNMP libraries or other technologies. Notice also the method name is a little odd: getdeviceName(). Strictly speaking, this should really be getDeviceName(), with a capital D. As this code will generally be provided to you by a device programmer, it's unlikely that you'll be able to apply the above correction. Instead, you can hide the inconsistency using the Adaptor interface as shown in Listing 8.

Listing 8: An Adaptor interface

public interface DeviceManagement {
    public String getDeviceName();
    public void setdeviceName(String deviceName);
    public String getDeviceSoftwareVersion();
}

The purpose of the interface in Listing 8 is to hide the complexity of Listing 6 and Listing 7. To see how this is done, look at the implementation in Listing 9.

Listing 9: An Adaptor implementation

public class DeviceManagementImpl implements DeviceManagement {

    private DeviceAccess deviceAccess;

    public DeviceManagementImpl() {
        deviceAccess = new DeviceAccessImpl();
    }

    @Override
    public String getDeviceName() {
        return deviceAccess.getdeviceName();
    }

    @Override
    public void setdeviceName(String deviceName) {
        deviceAccess.setdeviceName(deviceName);
    }

    @Override
    public String getDeviceSoftwareVersion() {
        return deviceAccess.getdeviceSoftwareVersion();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        DeviceManagementImpl deviceManagement = new DeviceManagementImpl();
        System.out.println("Device name: " + deviceManagement.getDeviceName());
        deviceManagement.setdeviceName("Souped up device");
        System.out.println("Device name: " + deviceManagement.getDeviceName());
    }
}

The important part of Listing 9 is the private member that stores an instance of the device:

private  DeviceAccess deviceAccess;

The methods in DeviceManagementImpl hide the complexity of the DeviceAccess interface. Listing 10 illustrates a simple program run showing the device details before and after a name-change operation:

Listing 10: An Adaptor implementation run

Device name: Super New Device
Device name: Souped up device

We use the Adaptor pattern to hide the complexity of the device access code. Using the Java 8 features, we can also extend the Adaptor interface in exactly the same way as we did for the Command pattern.

Installing Java 8

Getting up and running with Java 8 is easy. If you're running Ubuntu, just run the following commands:

sudo apt-get purge openjdk*

sudo apt-get install python-software-properties
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
java -version

The java -version command should produce output similar to the following:

java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

If you see this output, you're good to go with Java 8.

Conclusion

Patterns remain an extremely useful addition to the programmer toolkit. The generic nature of design patterns makes them pretty easy to include in most programming domains—telecommunications, financial services, energy management systems, and so on.

The Command pattern provides a simple model for implementing instruction-style object interactions such as switch on/off, do/undo, and so forth. By storing the current state, the command implementation allows for any state changes to be reversed. Obviously, this state storage introduces potential concurrency issues, but this is a small price to pay for the rich design semantics that come with the Command pattern. In any case, there are good concurrency patterns that help reduce issues of state contention and deadlocks.

The Java 8 facilities and the Command pattern make a good team, because Java 8 provides the ability to add default methods to interfaces. This in turn obviates the need to change the clients or implementers of those interfaces.

The Adaptor pattern allows for easy modeling of one of the pillars of programming: encapsulation. Third-party code can be easily decoupled from application code by putting the former inside an Adaptor class. This serves two purposes: Separation of Concerns and easier upgrades. The third-party code can then be changed without necessarily breaking the application code. The application code also can be upgraded more easily when it's not directly dependent on the third-party code.

Just as is the case for the Command pattern, the Adaptor pattern can be accommodated inside Java 8 features.

Will I continue to encourage the use of design patterns? You can count on it!

  • + Share This
  • 🔖 Save To Your Account